diff options
| -rw-r--r-- | bragi.nix | 49 | ||||
| -rw-r--r-- | custom/mpd.nix | 199 |
2 files changed, 224 insertions, 24 deletions
| @@ -10,6 +10,7 @@ in rec { | |||
| 10 | ./users.nix | 10 | ./users.nix |
| 11 | ./custom/unit-status-mail.nix | 11 | ./custom/unit-status-mail.nix |
| 12 | ./custom/trivmix-service.nix | 12 | ./custom/trivmix-service.nix |
| 13 | ./custom/mpd.nix | ||
| 13 | ./utils/nix/module.nix | 14 | ./utils/nix/module.nix |
| 14 | ]; | 15 | ]; |
| 15 | 16 | ||
| @@ -84,27 +85,29 @@ in rec { | |||
| 84 | }; | 85 | }; |
| 85 | 86 | ||
| 86 | nixpkgs.config = { | 87 | nixpkgs.config = { |
| 87 | packageOverrides = oldPkgs: with oldPkgs; { | 88 | overlays = [ |
| 88 | haskellPackages = haskellPackages.override { | 89 | (selfPkgs: superPkgs: with superPkgs; { |
| 89 | overrides = self: super: with self; { | 90 | haskellPackages = haskellPackages.override { |
| 90 | encoding = haskell.lib.overrideCabal encoding ( oldAttrs: { | 91 | overrides = self: super: with self; { |
| 91 | src = fetchFromGitHub { owner = "pngwjpgh"; repo = "encoding"; rev = "extended-version-bounds"; sha256 = "0pzxixp384a1ywzj56pl7xc4ln7i9x6mq8spqjwcs80y0pgfpp9s"; }; | 92 | encoding = haskell.lib.overrideCabal encoding ( oldAttrs: { |
| 92 | }); | 93 | src = fetchFromGitHub { owner = "pngwjpgh"; repo = "encoding"; rev = "extended-version-bounds"; sha256 = "0pzxixp384a1ywzj56pl7xc4ln7i9x6mq8spqjwcs80y0pgfpp9s"; }; |
| 93 | inherit | 94 | }); |
| 94 | (lib.mapAttrs (name: haskell.lib.dontCheck) super) | 95 | inherit |
| 95 | Glob filelock hedgehog; | 96 | (lib.mapAttrs (name: haskell.lib.dontCheck) super) |
| 96 | inherit | 97 | Glob filelock hedgehog; |
| 97 | (callPackage ./custom/thermoprint { inherit runCommand makeWrapper; extraPackages = (p: with p; [ persistent-postgresql ]); }) | 98 | inherit |
| 98 | thermoprint-spec thermoprint-bbcode thermoprint-client thermoprint-server thermoprint-webgui tprint bbcode; | 99 | (callPackage ./custom/thermoprint { inherit runCommand makeWrapper; extraPackages = (p: with p; [ persistent-postgresql ]); }) |
| 100 | thermoprint-spec thermoprint-bbcode thermoprint-client thermoprint-server thermoprint-webgui tprint bbcode; | ||
| 101 | }; | ||
| 99 | }; | 102 | }; |
| 100 | }; | ||
| 101 | 103 | ||
| 102 | jack2Full = jack2Full.override { dbus = null; }; | 104 | jack2Full = jack2Full.override { dbus = null; }; |
| 103 | 105 | ||
| 104 | mpd = mpd.override { gmeSupport = false; pulseaudioSupport = false; }; | 106 | mpd = mpd.override { gmeSupport = false; pulseaudioSupport = false; }; |
| 105 | 107 | ||
| 106 | inherit (pkgs.haskellPackages) thermoprint-server thermoprint-webgui tprint; | 108 | inherit (pkgs.haskellPackages) thermoprint-server thermoprint-webgui tprint; |
| 107 | }; | 109 | }) |
| 110 | ]; | ||
| 108 | 111 | ||
| 109 | allowUnfree = true; | 112 | allowUnfree = true; |
| 110 | }; | 113 | }; |
| @@ -225,14 +228,12 @@ in rec { | |||
| 225 | services.mpd = { | 228 | services.mpd = { |
| 226 | enable = true; | 229 | enable = true; |
| 227 | musicDirectory = "smb://odin.asgard.yggdrasil/media/music"; | 230 | musicDirectory = "smb://odin.asgard.yggdrasil/media/music"; |
| 228 | network.listenAddress = "any"; # Just so the module won't produce a bind_to_adress line | 231 | listenAddresses = [ |
| 232 | { address = "any"; } | ||
| 233 | "/var/lib/mpd/socket" | ||
| 234 | ]; | ||
| 235 | startWhenNeeded = true; | ||
| 229 | extraConfig = '' | 236 | extraConfig = '' |
| 230 | bind_to_address "bragi.bragisheimr.yggdrasil" | ||
| 231 | bind_to_address "bragi.asgard.yggdrasil" | ||
| 232 | bind_to_address "localhost" | ||
| 233 | |||
| 234 | bind_to_address "/var/lib/mpd/socket" | ||
| 235 | |||
| 236 | audio_output { | 237 | audio_output { |
| 237 | name "JACK" | 238 | name "JACK" |
| 238 | type "jack" | 239 | type "jack" |
diff --git a/custom/mpd.nix b/custom/mpd.nix new file mode 100644 index 00000000..8df2a955 --- /dev/null +++ b/custom/mpd.nix | |||
| @@ -0,0 +1,199 @@ | |||
| 1 | { config, lib, pkgs, ... }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | |||
| 7 | name = "mpd"; | ||
| 8 | |||
| 9 | uid = config.ids.uids.mpd; | ||
| 10 | gid = config.ids.gids.mpd; | ||
| 11 | cfg = config.services.mpd; | ||
| 12 | |||
| 13 | mpdConf = pkgs.writeText "mpd.conf" '' | ||
| 14 | music_directory "${cfg.musicDirectory}" | ||
| 15 | playlist_directory "${cfg.playlistDirectory}" | ||
| 16 | db_file "${cfg.dbFile}" | ||
| 17 | state_file "${cfg.dataDir}/state" | ||
| 18 | sticker_file "${cfg.dataDir}/sticker.sql" | ||
| 19 | log_file "syslog" | ||
| 20 | user "${cfg.user}" | ||
| 21 | group "${cfg.group}" | ||
| 22 | |||
| 23 | ${optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''} | ||
| 24 | ${optionalString (cfg.network.port != 6600) ''port "${toString cfg.network.port}"''} | ||
| 25 | |||
| 26 | ${cfg.extraConfig} | ||
| 27 | ''; | ||
| 28 | |||
| 29 | tcpAddress = { | ||
| 30 | options = { | ||
| 31 | address = mkOption { | ||
| 32 | type = with types; either (enum ["any"]) str; | ||
| 33 | example = "localhost"; | ||
| 34 | }; | ||
| 35 | |||
| 36 | port = mkOption { | ||
| 37 | type = types.int; | ||
| 38 | default = 6600; | ||
| 39 | }; | ||
| 40 | }; | ||
| 41 | }; | ||
| 42 | |||
| 43 | listenStream = arg: if isString arg then arg else (optionalString (arg.address != "any") (arg.address + ":")) + toString arg.port; | ||
| 44 | bindAddress = arg: if isString arg then arg | ||
| 45 | else arg.address + ":" + toString arg.port; | ||
| 46 | |||
| 47 | in { | ||
| 48 | |||
| 49 | disabledModules = [ "services/audio/mpd.nix" ]; | ||
| 50 | |||
| 51 | ###### interface | ||
| 52 | |||
| 53 | options = { | ||
| 54 | |||
| 55 | services.mpd = { | ||
| 56 | |||
| 57 | enable = mkOption { | ||
| 58 | type = types.bool; | ||
| 59 | default = false; | ||
| 60 | description = '' | ||
| 61 | Whether to enable MPD, the music player daemon. | ||
| 62 | ''; | ||
| 63 | }; | ||
| 64 | |||
| 65 | startWhenNeeded = mkOption { | ||
| 66 | type = types.bool; | ||
| 67 | default = false; | ||
| 68 | description = '' | ||
| 69 | If set, <command>mpd</command> is socket-activated; that | ||
| 70 | is, instead of having it permanently running as a daemon, | ||
| 71 | systemd will start it on the first incoming connection. | ||
| 72 | ''; | ||
| 73 | }; | ||
| 74 | |||
| 75 | musicDirectory = mkOption { | ||
| 76 | type = with types; either path str; | ||
| 77 | default = "${cfg.dataDir}/music"; | ||
| 78 | defaultText = ''''${dataDir}/music''; | ||
| 79 | description = '' | ||
| 80 | The directory where mpd reads music from. | ||
| 81 | ''; | ||
| 82 | }; | ||
| 83 | |||
| 84 | playlistDirectory = mkOption { | ||
| 85 | type = types.path; | ||
| 86 | default = "${cfg.dataDir}/playlists"; | ||
| 87 | defaultText = ''''${dataDir}/playlists''; | ||
| 88 | description = '' | ||
| 89 | The directory where mpd stores playlists. | ||
| 90 | ''; | ||
| 91 | }; | ||
| 92 | |||
| 93 | extraConfig = mkOption { | ||
| 94 | type = types.lines; | ||
| 95 | default = ""; | ||
| 96 | description = '' | ||
| 97 | Extra directives added to to the end of MPD's configuration file, | ||
| 98 | mpd.conf. Basic configuration like file location and uid/gid | ||
| 99 | is added automatically to the beginning of the file. For available | ||
| 100 | options see <literal>man 5 mpd.conf</literal>'. | ||
| 101 | ''; | ||
| 102 | }; | ||
| 103 | |||
| 104 | dataDir = mkOption { | ||
| 105 | type = types.path; | ||
| 106 | default = "/var/lib/${name}"; | ||
| 107 | description = '' | ||
| 108 | The directory where MPD stores its state, tag cache, | ||
| 109 | playlists etc. | ||
| 110 | ''; | ||
| 111 | }; | ||
| 112 | |||
| 113 | user = mkOption { | ||
| 114 | type = types.str; | ||
| 115 | default = name; | ||
| 116 | description = "User account under which MPD runs."; | ||
| 117 | }; | ||
| 118 | |||
| 119 | group = mkOption { | ||
| 120 | type = types.str; | ||
| 121 | default = name; | ||
| 122 | description = "Group account under which MPD runs."; | ||
| 123 | }; | ||
| 124 | |||
| 125 | listenAddresses = mkOption { | ||
| 126 | type = with types; listOf (either (submodule tcpAddress) str); | ||
| 127 | default = [{ address = "127.0.0.1"; }]; | ||
| 128 | }; | ||
| 129 | |||
| 130 | dbFile = mkOption { | ||
| 131 | type = types.str; | ||
| 132 | default = "${cfg.dataDir}/tag_cache"; | ||
| 133 | defaultText = ''''${dataDir}/tag_cache''; | ||
| 134 | description = '' | ||
| 135 | The path to MPD's database. | ||
| 136 | ''; | ||
| 137 | }; | ||
| 138 | }; | ||
| 139 | |||
| 140 | }; | ||
| 141 | |||
| 142 | |||
| 143 | ###### implementation | ||
| 144 | |||
| 145 | config = mkIf cfg.enable { | ||
| 146 | |||
| 147 | systemd.sockets.mpd = mkIf cfg.startWhenNeeded { | ||
| 148 | description = "Music Player Daemon Socket"; | ||
| 149 | wantedBy = [ "sockets.target" ]; | ||
| 150 | listenStreams = map listenStream cfg.listenAddresses; | ||
| 151 | socketConfig = { | ||
| 152 | Backlog = 5; | ||
| 153 | KeepAlive = true; | ||
| 154 | PassCredentials = true; | ||
| 155 | }; | ||
| 156 | }; | ||
| 157 | |||
| 158 | systemd.services.mpd = { | ||
| 159 | after = [ "network.target" "sound.target" ]; | ||
| 160 | description = "Music Player Daemon"; | ||
| 161 | wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; | ||
| 162 | |||
| 163 | preStart = '' | ||
| 164 | mkdir -p "${cfg.dataDir}" && chown -R ${cfg.user}:${cfg.group} "${cfg.dataDir}" | ||
| 165 | mkdir -p "${cfg.playlistDirectory}" && chown -R ${cfg.user}:${cfg.group} "${cfg.playlistDirectory}" | ||
| 166 | ''; | ||
| 167 | serviceConfig = { | ||
| 168 | User = "${cfg.user}"; | ||
| 169 | PermissionsStartOnly = true; | ||
| 170 | ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}"; | ||
| 171 | Type = "notify"; | ||
| 172 | LimitRTPRIO = 50; | ||
| 173 | LimitRTTIME = "infinity"; | ||
| 174 | ProtectSystem = true; | ||
| 175 | NoNewPrivileges = true; | ||
| 176 | ProtectKernelTunables = true; | ||
| 177 | ProtectControlGroups = true; | ||
| 178 | ProtectKernelModules = true; | ||
| 179 | RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK"; | ||
| 180 | RestrictNamespaces = true; | ||
| 181 | }; | ||
| 182 | }; | ||
| 183 | |||
| 184 | users.extraUsers = optionalAttrs (cfg.user == name) (singleton { | ||
| 185 | inherit uid; | ||
| 186 | inherit name; | ||
| 187 | group = cfg.group; | ||
| 188 | extraGroups = [ "audio" ]; | ||
| 189 | description = "Music Player Daemon user"; | ||
| 190 | home = "${cfg.dataDir}"; | ||
| 191 | }); | ||
| 192 | |||
| 193 | users.extraGroups = optionalAttrs (cfg.group == name) (singleton { | ||
| 194 | inherit name; | ||
| 195 | gid = gid; | ||
| 196 | }); | ||
| 197 | }; | ||
| 198 | |||
| 199 | } | ||
