Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/snapserver: init #55936

Merged
merged 1 commit into from
Mar 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
./services/audio/mpd.nix
./services/audio/mopidy.nix
./services/audio/slimserver.nix
./services/audio/snapserver.nix
./services/audio/squeezelite.nix
./services/audio/ympd.nix
./services/backup/bacula.nix
Expand Down
217 changes: 217 additions & 0 deletions nixos/modules/services/audio/snapserver.nix
Original file line number Diff line number Diff line change
@@ -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.
tobim marked this conversation as resolved.
Show resolved Hide resolved
'';
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";
tobim marked this conversation as resolved.
Show resolved Hide resolved
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 ];
};

}