nixpkgs/nixos/modules/services/audio/snapserver.nix

217 lines
5.3 KiB
Nix
Raw Normal View History

{ config, lib, pkgs, ... }:
with lib;
let
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 ];
};
}