about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/audio
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services/audio')
-rw-r--r--nixpkgs/nixos/modules/services/audio/alsa.nix134
-rw-r--r--nixpkgs/nixos/modules/services/audio/icecast.nix130
-rw-r--r--nixpkgs/nixos/modules/services/audio/liquidsoap.nix74
-rw-r--r--nixpkgs/nixos/modules/services/audio/mopidy.nix108
-rw-r--r--nixpkgs/nixos/modules/services/audio/mpd.nix202
-rw-r--r--nixpkgs/nixos/modules/services/audio/slimserver.nix70
-rw-r--r--nixpkgs/nixos/modules/services/audio/snapserver.nix217
-rw-r--r--nixpkgs/nixos/modules/services/audio/squeezelite.nix50
-rw-r--r--nixpkgs/nixos/modules/services/audio/ympd.nix57
9 files changed, 1042 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/audio/alsa.nix b/nixpkgs/nixos/modules/services/audio/alsa.nix
new file mode 100644
index 000000000000..376aad66e236
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/alsa.nix
@@ -0,0 +1,134 @@
+# ALSA sound support.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) alsaUtils;
+
+  pulseaudioEnabled = config.hardware.pulseaudio.enable;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    sound = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable ALSA sound.
+        '';
+      };
+
+      enableOSSEmulation = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          defaults.pcm.!card 3
+        '';
+        description = ''
+          Set addition configuration for system-wide alsa.
+        '';
+      };
+
+      mediaKeys = {
+
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable volume and capture control with keyboard media keys.
+
+            You want to leave this disabled if you run a desktop environment
+            like KDE, Gnome, Xfce, etc, as those handle such things themselves.
+            You might want to enable this if you run a minimalistic desktop
+            environment or work from bare linux ttys/framebuffers.
+
+            Enabling this will turn on <option>services.actkbd</option>.
+          '';
+        };
+
+        volumeStep = mkOption {
+          type = types.string;
+          default = "1";
+          example = "1%";
+          description = ''
+            The value by which to increment/decrement volume on media keys.
+
+            See amixer(1) for allowed values.
+          '';
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.sound.enable {
+
+    environment.systemPackages = [ alsaUtils ];
+
+    environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
+      [
+        { source = pkgs.writeText "asound.conf" config.sound.extraConfig;
+          target = "asound.conf";
+        }
+      ];
+
+    # ALSA provides a udev rule for restoring volume settings.
+    services.udev.packages = [ alsaUtils ];
+
+    boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
+
+    systemd.services."alsa-store" =
+      { description = "Store Sound Card State";
+        wantedBy = [ "multi-user.target" ];
+        unitConfig.RequiresMountsFor = "/var/lib/alsa";
+        unitConfig.ConditionVirtualization = "!systemd-nspawn";
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+          ExecStart = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
+          ExecStop = "${alsaUtils}/sbin/alsactl store --ignore";
+        };
+      };
+
+    services.actkbd = mkIf config.sound.mediaKeys.enable {
+      enable = true;
+      bindings = [
+        # "Mute" media key
+        { keys = [ 113 ]; events = [ "key" ];       command = "${alsaUtils}/bin/amixer -q set Master toggle"; }
+
+        # "Lower Volume" media key
+        { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
+
+        # "Raise Volume" media key
+        { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
+
+        # "Mic Mute" media key
+        { keys = [ 190 ]; events = [ "key" ];       command = "${alsaUtils}/bin/amixer -q set Capture toggle"; }
+      ];
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/icecast.nix b/nixpkgs/nixos/modules/services/audio/icecast.nix
new file mode 100644
index 000000000000..6a8a0f9975b3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/icecast.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.icecast;
+  configFile = pkgs.writeText "icecast.xml" ''
+    <icecast>
+      <hostname>${cfg.hostname}</hostname>
+
+      <authentication>
+        <admin-user>${cfg.admin.user}</admin-user>
+        <admin-password>${cfg.admin.password}</admin-password>
+      </authentication>
+
+      <paths>
+        <logdir>${cfg.logDir}</logdir>
+        <adminroot>${pkgs.icecast}/share/icecast/admin</adminroot>
+        <webroot>${pkgs.icecast}/share/icecast/web</webroot>
+        <alias source="/" dest="/status.xsl"/>
+      </paths>
+
+      <listen-socket>
+        <port>${toString cfg.listen.port}</port>
+        <bind-address>${cfg.listen.address}</bind-address>
+      </listen-socket>   
+
+      <security>
+        <chroot>0</chroot>
+        <changeowner>
+            <user>${cfg.user}</user>
+            <group>${cfg.group}</group>
+        </changeowner>
+      </security>
+
+      ${cfg.extraConf}
+    </icecast>
+  '';
+in {
+
+  ###### interface
+
+  options = {
+
+    services.icecast = {
+
+      enable = mkEnableOption "Icecast server";
+
+      hostname = mkOption {
+        type = types.str;
+        description = "DNS name or IP address that will be used for the stream directory lookups or possibily the playlist generation if a Host header is not provided.";
+        default = config.networking.domain;
+      };
+
+      admin = {
+        user = mkOption {
+          type = types.str;
+          description = "Username used for all administration functions.";
+          default = "admin";
+        };
+
+        password = mkOption {
+          type = types.str;
+          description = "Password used for all administration functions.";
+        };
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        description = "Base directory used for logging.";
+        default = "/var/log/icecast";
+      };
+      
+      listen = {
+        port = mkOption {
+          type = types.int;
+          description = "TCP port that will be used to accept client connections.";
+          default = 8000;
+        };
+
+        address = mkOption {
+          type = types.str;
+          description = "Address Icecast will listen on.";
+          default = "::";
+        };
+      };
+
+      user = mkOption {
+        type = types.str;
+        description = "User privileges for the server.";
+        default = "nobody";
+      };
+
+      group = mkOption {
+        type = types.str;
+        description = "Group privileges for the server.";
+        default = "nogroup";
+      };
+
+      extraConf = mkOption {
+        type = types.lines;
+        description = "icecast.xml content.";
+        default = "";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.icecast = {
+      after = [ "network.target" ];
+      description = "Icecast Network Audio Streaming Server";
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = "mkdir -p ${cfg.logDir} && chown ${cfg.user}:${cfg.group} ${cfg.logDir}";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.icecast}/bin/icecast -c ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/liquidsoap.nix b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
new file mode 100644
index 000000000000..66f84ef20762
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  streams = builtins.attrNames config.services.liquidsoap.streams;
+
+  streamService =
+    name:
+    let stream = builtins.getAttr name config.services.liquidsoap.streams; in
+    { inherit name;
+      value = {
+        after = [ "network-online.target" "sound.target" ];
+        description = "${name} liquidsoap stream";
+        wantedBy = [ "multi-user.target" ];
+        path = [ pkgs.wget ];
+        preStart =
+          ''
+            mkdir -p /var/log/liquidsoap
+            chown liquidsoap -R /var/log/liquidsoap
+          '';
+        serviceConfig = {
+          PermissionsStartOnly="true";
+          ExecStart = "${pkgs.liquidsoap}/bin/liquidsoap ${stream}";
+          User = "liquidsoap";
+        };
+      };
+    };
+in
+{
+
+  ##### interface
+
+  options = {
+
+    services.liquidsoap.streams = mkOption {
+
+      description =
+        ''
+          Set of Liquidsoap streams to start,
+          one systemd service per stream.
+        '';
+
+      default = {};
+
+      example = {
+        myStream1 = literalExample "\"/etc/liquidsoap/myStream1.liq\"";
+        myStream2 = literalExample "./myStream2.liq";
+        myStream3 = literalExample "\"out(playlist(\\\"/srv/music/\\\"))\"";
+      };
+
+      type = types.attrsOf (types.either types.path types.str);
+    };
+
+  };
+  ##### implementation
+
+  config = mkIf (builtins.length streams != 0) {
+
+    users.users.liquidsoap = {
+      uid = config.ids.uids.liquidsoap;
+      group = "liquidsoap";
+      extraGroups = [ "audio" ];
+      description = "Liquidsoap streaming user";
+      home = "/var/lib/liquidsoap";
+      createHome = true;
+    };
+
+    users.groups.liquidsoap.gid = config.ids.gids.liquidsoap;
+
+    systemd.services = builtins.listToAttrs ( map streamService streams );
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/mopidy.nix b/nixpkgs/nixos/modules/services/audio/mopidy.nix
new file mode 100644
index 000000000000..e2f4ec39f94c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/mopidy.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+  uid = config.ids.uids.mopidy;
+  gid = config.ids.gids.mopidy;
+  cfg = config.services.mopidy;
+
+  mopidyConf = writeText "mopidy.conf" cfg.configuration;
+
+  mopidyEnv = buildEnv {
+    name = "mopidy-with-extensions-${mopidy.version}";
+    paths = closePropagation cfg.extensionPackages;
+    pathsToLink = [ "/${python.sitePackages}" ];
+    buildInputs = [ makeWrapper ];
+    postBuild = ''
+      makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \
+        --prefix PYTHONPATH : $out/${python.sitePackages}
+    '';
+  };
+in {
+
+  options = {
+
+    services.mopidy = {
+
+      enable = mkEnableOption "Mopidy, a music player daemon";
+
+      dataDir = mkOption {
+        default = "/var/lib/mopidy";
+        type = types.str;
+        description = ''
+          The directory where Mopidy stores its state.
+        '';
+      };
+
+      extensionPackages = mkOption {
+        default = [];
+        type = types.listOf types.package;
+        example = literalExample "[ pkgs.mopidy-spotify ]";
+        description = ''
+          Mopidy extensions that should be loaded by the service.
+        '';
+      };
+
+      configuration = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          The configuration that Mopidy should use.
+        '';
+      };
+
+      extraConfigFiles = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        description = ''
+          Extra config file read by Mopidy when the service starts.
+          Later files in the list overrides earlier configuration.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.mopidy = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "sound.target" ];
+      description = "mopidy music player daemon";
+      preStart = "mkdir -p ${cfg.dataDir} && chown -R mopidy:mopidy  ${cfg.dataDir}";
+      serviceConfig = {
+        ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
+        User = "mopidy";
+        PermissionsStartOnly = true;
+      };
+    };
+
+    systemd.services.mopidy-scan = {
+      description = "mopidy local files scanner";
+      preStart = "mkdir -p ${cfg.dataDir} && chown -R mopidy:mopidy  ${cfg.dataDir}";
+      serviceConfig = {
+        ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)} local scan";
+        User = "mopidy";
+        PermissionsStartOnly = true;
+        Type = "oneshot";
+      };
+    };
+
+    users.users.mopidy = {
+      inherit uid;
+      group = "mopidy";
+      extraGroups = [ "audio" ];
+      description = "Mopidy daemon user";
+      home = "${cfg.dataDir}";
+    };
+
+    users.groups.mopidy.gid = gid;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/mpd.nix b/nixpkgs/nixos/modules/services/audio/mpd.nix
new file mode 100644
index 000000000000..5bfe2b6a22ad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/mpd.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "mpd";
+
+  uid = config.ids.uids.mpd;
+  gid = config.ids.gids.mpd;
+  cfg = config.services.mpd;
+
+  mpdConf = pkgs.writeText "mpd.conf" ''
+    music_directory     "${cfg.musicDirectory}"
+    playlist_directory  "${cfg.playlistDirectory}"
+    ${lib.optionalString (cfg.dbFile != null) ''
+      db_file             "${cfg.dbFile}"
+    ''}
+    state_file          "${cfg.dataDir}/state"
+    sticker_file        "${cfg.dataDir}/sticker.sql"
+    log_file            "syslog"
+    user                "${cfg.user}"
+    group               "${cfg.group}"
+
+    ${optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
+    ${optionalString (cfg.network.port != 6600)  ''port "${toString cfg.network.port}"''}
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.mpd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable MPD, the music player daemon.
+        '';
+      };
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If set, <command>mpd</command> is socket-activated; that
+          is, instead of having it permanently running as a daemon,
+          systemd will start it on the first incoming connection.
+        '';
+      };
+
+      musicDirectory = mkOption {
+        type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
+        default = "${cfg.dataDir}/music";
+        defaultText = ''''${dataDir}/music'';
+        description = ''
+          The directory or NFS/SMB network share where mpd reads music from.
+        '';
+      };
+
+      playlistDirectory = mkOption {
+        type = types.path;
+        default = "${cfg.dataDir}/playlists";
+        defaultText = ''''${dataDir}/playlists'';
+        description = ''
+          The directory where mpd stores playlists.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra directives added to to the end of MPD's configuration file,
+          mpd.conf. Basic configuration like file location and uid/gid
+          is added automatically to the beginning of the file. For available
+          options see <literal>man 5 mpd.conf</literal>'.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/${name}";
+        description = ''
+          The directory where MPD stores its state, tag cache,
+          playlists etc.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = name;
+        description = "User account under which MPD runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = name;
+        description = "Group account under which MPD runs.";
+      };
+
+      network = {
+
+        listenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          example = "any";
+          description = ''
+            The address for the daemon to listen on.
+            Use <literal>any</literal> to listen on all addresses.
+          '';
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 6600;
+          description = ''
+            This setting is the TCP port that is desired for the daemon to get assigned
+            to.
+          '';
+        };
+
+      };
+
+      dbFile = mkOption {
+        type = types.nullOr types.str;
+        default = "${cfg.dataDir}/tag_cache";
+        defaultText = ''''${dataDir}/tag_cache'';
+        description = ''
+          The path to MPD's database. If set to <literal>null</literal> the
+          parameter is omitted from the configuration.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.sockets.mpd = mkIf cfg.startWhenNeeded {
+      description = "Music Player Daemon Socket";
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [
+        "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}"
+      ];
+      socketConfig = {
+        Backlog = 5;
+        KeepAlive = true;
+        PassCredentials = true;
+      };
+    };
+
+    systemd.services.mpd = {
+      after = [ "network.target" "sound.target" ];
+      description = "Music Player Daemon";
+      wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
+
+      preStart = ''
+        mkdir -p "${cfg.dataDir}" && chown -R ${cfg.user}:${cfg.group} "${cfg.dataDir}"
+        mkdir -p "${cfg.playlistDirectory}" && chown -R ${cfg.user}:${cfg.group} "${cfg.playlistDirectory}"
+      '';
+      serviceConfig = {
+        User = "${cfg.user}";
+        PermissionsStartOnly = true;
+        ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}";
+        Type = "notify";
+        LimitRTPRIO = 50;
+        LimitRTTIME = "infinity";
+        ProtectSystem = true;
+        NoNewPrivileges = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
+        RestrictNamespaces = true;
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == name) (singleton {
+      inherit uid;
+      inherit name;
+      group = cfg.group;
+      extraGroups = [ "audio" ];
+      description = "Music Player Daemon user";
+      home = "${cfg.dataDir}";
+    });
+
+    users.groups = optionalAttrs (cfg.group == name) (singleton {
+      inherit name;
+      gid = gid;
+    });
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/slimserver.nix b/nixpkgs/nixos/modules/services/audio/slimserver.nix
new file mode 100644
index 000000000000..640403d2c97d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/slimserver.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.slimserver;
+
+in {
+  options = {
+
+    services.slimserver = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable slimserver.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.slimserver;
+        defaultText = "pkgs.slimserver";
+        description = "Slimserver package to use.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/slimserver";
+        description = ''
+          The directory where slimserver stores its state, tag cache,
+          playlists etc.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.slimserver = {
+      after = [ "network.target" ];
+      description = "Slim Server for Logitech Squeezebox Players";
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = "mkdir -p ${cfg.dataDir} && chown -R slimserver:slimserver ${cfg.dataDir}";
+      serviceConfig = {
+        User = "slimserver";
+        PermissionsStartOnly = true;
+        # Issue 40589: Disable broken image/video support (audio still works!)
+        ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
+      };
+    };
+
+    users = {
+      users.slimserver = {
+        description = "Slimserver daemon user";
+        home = cfg.dataDir;
+        group = "slimserver";
+      };
+      groups.slimserver = {};
+    };
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/audio/snapserver.nix b/nixpkgs/nixos/modules/services/audio/snapserver.nix
new file mode 100644
index 000000000000..f709dd7fe16b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/snapserver.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  package = "snapcast";
+  name = "snapserver";
+
+  cfg = config.services.snapserver;
+
+  # Using types.nullOr to inherit upstream defaults.
+  sampleFormat = mkOption {
+    type = with types; nullOr str;
+    default = null;
+    description = ''
+      Default sample format.
+    '';
+    example = "48000:16:2";
+  };
+
+  codec = mkOption {
+    type = with types; nullOr str;
+    default = null;
+    description = ''
+      Default audio compression method.
+    '';
+    example = "flac";
+  };
+
+  streamToOption = name: opt:
+    let
+      os = val:
+        optionalString (val != null) "${val}";
+      os' = prefixx: val:
+        optionalString (val != null) (prefixx + "${val}");
+      flatten = key: value:
+        "&${key}=${value}";
+    in
+      "-s ${opt.type}://" + os opt.location + "?" + os' "name=" name
+        + concatStrings (mapAttrsToList flatten opt.query);
+
+  optionalNull = val: ret:
+    optional (val != null) ret;
+
+  optionString = concatStringsSep " " (mapAttrsToList streamToOption cfg.streams
+             ++ ["-p ${toString cfg.port}"]
+             ++ ["--controlPort ${toString cfg.controlPort}"]
+             ++ optionalNull cfg.sampleFormat "--sampleFormat ${cfg.sampleFormat}"
+             ++ optionalNull cfg.codec "-c ${cfg.codec}"
+             ++ optionalNull cfg.streamBuffer "--streamBuffer ${cfg.streamBuffer}"
+             ++ optionalNull cfg.buffer "-b ${cfg.buffer}"
+             ++ optional cfg.sendToMuted "--sendToMuted");
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.snapserver = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable snapserver.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 1704;
+        description = ''
+          The port that snapclients can connect to.
+        '';
+      };
+
+      controlPort = mkOption {
+        type = types.port;
+        default = 1705;
+        description = ''
+          The port for control connections (JSON-RPC).
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to automatically open the specified ports in the firewall.
+        '';
+      };
+
+      inherit sampleFormat;
+      inherit codec;
+
+      streams = mkOption {
+        type = with types; attrsOf (submodule {
+          options = {
+            location = mkOption {
+              type = types.path;
+              description = ''
+                The location of the pipe.
+              '';
+            };
+            type = mkOption {
+              type = types.enum [ "pipe" "file" "process" "spotify" "airplay" ];
+              default = "pipe";
+              description = ''
+                The type of input stream.
+              '';
+            };
+            query = mkOption {
+              type = attrsOf str;
+              default = {};
+              description = ''
+                Key-value pairs that convey additional parameters about a stream.
+              '';
+              example = literalExample ''
+                # for type == "pipe":
+                {
+                  mode = "listen";
+                };
+                # for type == "process":
+                {
+                  params = "--param1 --param2";
+                  logStderr = "true";
+                };
+              '';
+            };
+            inherit sampleFormat;
+            inherit codec;
+          };
+        });
+        default = { default = {}; };
+        description = ''
+          The definition for an input source.
+        '';
+        example = literalExample ''
+          {
+            mpd = {
+              type = "pipe";
+              location = "/run/snapserver/mpd";
+              sampleFormat = "48000:16:2";
+              codec = "pcm";
+            };
+          };
+        '';
+      };
+
+      streamBuffer = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          Stream read (input) buffer in ms.
+        '';
+        example = 20;
+      };
+
+      buffer = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          Network buffer in ms.
+        '';
+        example = 1000;
+      };
+
+      sendToMuted = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Send audio to muted clients.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.snapserver = {
+      after = [ "network.target" ];
+      description = "Snapserver";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "mpd.service" "mopidy.service" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.snapcast}/bin/snapserver --daemon ${optionString}";
+        Type = "forking";
+        LimitRTPRIO = 50;
+        LimitRTTIME = "infinity";
+        NoNewPrivileges = true;
+        PIDFile = "/run/${name}/pid";
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+        RestrictNamespaces = true;
+        RuntimeDirectory = name;
+        StateDirectory = name;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = optionals cfg.openFirewall [ cfg.port cfg.controlPort ];
+  };
+
+  meta = {
+    maintainers = with maintainers; [ tobim ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/squeezelite.nix b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
new file mode 100644
index 000000000000..05506f5bcc7a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  dataDir = "/var/lib/squeezelite";
+  cfg = config.services.squeezelite;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.squeezelite= {
+
+      enable = mkEnableOption "Squeezelite, a software Squeezebox emulator";
+
+      extraArguments = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Additional command line arguments to pass to Squeezelite.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.squeezelite= {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "sound.target" ];
+      description = "Software Squeezebox emulator";
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.squeezelite}/bin/squeezelite -N ${dataDir}/player-name ${cfg.extraArguments}";
+        StateDirectory = builtins.baseNameOf dataDir;
+        SupplementaryGroups = "audio";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/ympd.nix b/nixpkgs/nixos/modules/services/audio/ympd.nix
new file mode 100644
index 000000000000..919b76622510
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/ympd.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ympd;
+in {
+
+  ###### interface
+
+  options = {
+
+    services.ympd = {
+
+      enable = mkEnableOption "ympd, the MPD Web GUI";
+
+      webPort = mkOption {
+        type = types.either types.str types.port; # string for backwards compat
+        default = "8080";
+        description = "The port where ympd's web interface will be available.";
+        example = "ssl://8080:/path/to/ssl-private-key.pem";
+      };
+
+      mpd = {
+        host = mkOption {
+          type = types.string;
+          default = "localhost";
+          description = "The host where MPD is listening.";
+          example = "localhost";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = config.services.mpd.network.port;
+          description = "The port where MPD is listening.";
+          example = 6600;
+        };
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.ympd = {
+      description = "Standalone MPD Web GUI written in C";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.ympd}/bin/ympd --host ${cfg.mpd.host} --port ${toString cfg.mpd.port} --webport ${toString cfg.webPort} --user nobody";
+    };
+
+  };
+
+}