217 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			217 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ 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 ];
 | 
						|
  };
 | 
						|
 | 
						|
}
 |