218 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
		
		
			
		
	
	
			218 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| 
								 | 
							
								{ 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 ];
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 |