Help getting `shairport-sync` up and running

I am trying (struggling) to get shairport-sync up and running on my server. At first I had huge ambitions of this elaborate config where I am running two airplay2 enabled instances with my own systemd units and including their configurations and alsa resampling config files all declaratively… but couldn’t get it to work (that’s the setup I had on Debian where I came from).

Anyway after stripping my project back further and further to just at least get it running, I have found that I still can’t. Even with the premade module. Has anyone gotten this working themselves?

The shell of what was once a great project that I still can’t get working:

{ config, pkgs, ... }:

{
  # enable pipewire with alsa and pulse support
  security.rtkit.enable = true;
  services.pipewire = {
    enable = true;
    alsa.enable = true;
    alsa.support32Bit = true;
    pulse.enable = true;
  };
  
  # enable shairport-sync module
  services.shairport-sync.enable = true;
  services.shairport-sync.openFirewall = true;
}

and the errors I’m getting, at least with this iteration:

Jan 15 09:30:50 morgoth shairport-sync[286202]:          0.000427655 "shairport.c:2285" Startup in classic Airplay (aka "AirPlay 1") mode.
Jan 15 09:30:50 morgoth shairport-sync[286202]:          0.000046585 "shairport.c:2321" Version String: "4.3.5-alac-OpenSSL-Avahi-ALSA-sndio-jack-ao-pa-pw-soundio-stdout-pipe-soxr-convolution-metadata-mqtt-dbus-mpris-sysconfdir:/etc"
Jan 15 09:30:50 morgoth shairport-sync[286202]:          0.000015270 "shairport.c:2340" Command Line: "/nix/store/hqspw09ipa7fgf7c08668dh3rif9ynfs-shairport-sync-4.3.5/bin/shairport-sync -v -o pa".
Jan 15 09:30:50 morgoth shairport-sync[286202]:          0.000007166 "shairport.c:2376" Log Verbosity is 1.
Jan 15 09:30:50 morgoth shairport-sync[286202]:          0.004660177 "audio_pa.c:222" *fatal error: failed to connect to the pulseaudio context -- the error message is "Connection refused".
Jan 15 09:30:50 morgoth shairport-sync[286202]:          0.000028655 "shairport.c:1736" emergency exit
Jan 15 09:30:50 morgoth systemd[1]: shairport-sync.service: Main process exited, code=exited, status=1/FAILURE

I the error gives a failure to connect to pulseaudio, I have tried both pipewire with pulseaudio support, as well as using actual puleaudio and no pipewire. When doing the latter I add the shairport user to the audio group. Still didn’t work.

And in case anyone is interested in seeing what my original idea was, I’ll include that here. Since I want to run two instances using two usb dacs, I couldn’t use the module.

{ config, pkgs, ... }:

{
  # add shairport-sync user
    users.users.shairport = {
      description = "Shairport user";
      isSystemUser = true;
      createHome = true;
      home = "/var/lib/shairport-sync";
      group = "shairport";
      extraGroups = [ "pulse-access" ];
    };
    users.groups.shairport = {};
  
  # open firewall ports
  networking.firewall = {
    interfaces."enp2s0" = {
      allowedTCPPorts = [
        3689
        5353
        5000
      ];
      allowedUDPPorts = [
        5353
      ];
      allowedTCPPortRanges = [
        { from = 7000; to = 7001; }
        { from = 32768; to = 60999; }
      ];
      allowedUDPPortRanges = [
        { from = 319; to = 320; }
        { from = 6000; to = 6009; }
        { from = 32768; to = 60999; }
      ];
    };
  };

  # packages
  environment = {
    systemPackages = with pkgs; [
      alsa-utils
      nqptp
      shairport-sync-airplay2
    ];
  };

  # enable pipewire with alsa support
  security.rtkit.enable = true;
  services.pipewire = {
    enable = true;
    alsa.enable = true;
    alsa.support32Bit = true;
    pulse.enable = true;
  };

  # enable avahi
  services.avahi = {
    enable = true;
    allowInterfaces = [ "enp2s0" ];
  };

  systemd.services = {
    nqptp = {
      description = "Network Precision Time Protocol for Shairport Sync";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      serviceConfig = {
        ExecStart = "${pkgs.nqptp}/bin/nqptp";
        Restart = "always";
        RestartSec = "5s";
      };
    };
    outdoor-speakers = {
      description = "Outdoor speakers shairport-sync instance";
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        User = "root";
        Group = "root";
        # ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync -c /srv/shairport-sync/outdoor_speakers.conf";
        ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync -v -o alsa";

      };
    };
    # dining-room = {
    #   description = "Dining room shairport-sync instance";
    #   wantedBy = [ "multi-user.target" ];
    #   serviceConfig = {
    #     User = "root";
    #     Group = "root";
    #     ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync -c /srv/shairport-sync/dining_room.conf";
    #   };
    # };
  };
}

Well. I ended up getting it all working after all. So if you’re here from google or looking for how to setup a multi instance shairport-sync nix config, you’re in the right place.

{ config, pkgs, lib, ... }:

{
  # user and group
  users = {
    users.shairport = {
      description    = "Shairport user";
      isSystemUser   = true;
      createHome     = true;
      home           = "/var/lib/shairport-sync";
      group          = "shairport";
      extraGroups    = [ "pulse-access" ];
    };
    groups.shairport = {};
  };

  # open firewall ports
  networking.firewall = {
    interfaces."enp2s0" = {
      allowedTCPPorts = [
        3689
        5353
        5000
      ];
      allowedUDPPorts = [
        5353
      ];
      allowedTCPPortRanges = [
        { from = 7000; to = 7001; }
        { from = 32768; to = 60999; }
      ];
      allowedUDPPortRanges = [
        { from = 319; to = 320; }
        { from = 6000; to = 6009; }
        { from = 32768; to = 60999; }
      ];
    };
  };

  # packages
  environment = {
    systemPackages = with pkgs; [
      alsa-utils
      nqptp
      shairport-sync-airplay2
    ];
  };

  # enable pulseaudio
  services.pipewire.enable = false;
  hardware = {
    pulseaudio = {
      enable = true;
      support32Bit = true;
      systemWide = true;
    };
  };
  
  # enable Avahi
  services.avahi = {
    enable = true;
    publish.enable = true;
    publish.userServices = true;
    allowInterfaces = [ "enp2s0" ];
  };

  # systemd services
  systemd.services = {
    nqptp = {
      description = "Network Precision Time Protocol for Shairport Sync";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      serviceConfig = {
        ExecStart = "${pkgs.nqptp}/bin/nqptp";
        Restart = "always";
        RestartSec = "5s";
      };
    };
    dining-room = {
      description = "Dining room speakers shairport-sync instance";
      wantedBy = [ "multi-user.target" ];
      after       = [ "network.target" "avahi-daemon.service" ];
      serviceConfig = {
        User             = "shairport";
        Group            = "shairport";
        ExecStart = "${pkgs.shairport-sync-airplay2}/bin/shairport-sync -c /etc/dining_room.conf";
        Restart          = "on-failure";
        RuntimeDirectory = "shairport-sync";
      };
    };
    outdoor-speakers = {
      description = "Outdoor speakers shairport-sync instance";
      wantedBy = [ "multi-user.target" ];
      after       = [ "network.target" "avahi-daemon.service" ];
      serviceConfig = {
        User             = "shairport";
        Group            = "shairport";
        ExecStart = "${pkgs.shairport-sync-airplay2}/bin/shairport-sync -c /etc/outdoor_speakers.conf";
        Restart          = "on-failure";
        RuntimeDirectory = "shairport-sync";
      };
    };
  };

  # write shairport-sync configs
  environment.etc."dining_room.conf".text = ''
    general =
    {
      name = "Dining Room";
      output_backend = "pa";
      port = 7000;
      airplay_device_id_offset = 0;
    };

    pa =
    {
      sink = "alsa_output.usb-Generic_USB_Audio_20210726905926-00.analog-stereo";
    };
  '';
  environment.etc."outdoor_speakers.conf".text = ''
    general =
    {
      name = "Outdoor Speakers";
      output_backend = "pa";
      port = 7001;
      airplay_device_id_offset = 1;
    };

    pa =
    {
      sink = "alsa_output.usb-Generic_USB_Audio_20210726905926-00.analog-stereo.2";
    };
  '';
}

# run `sudo -u pulse PULSE_RUNTIME_PATH=/run/pulse pactl list sinks short` to display available sinks
# run `sudo -u pulse alsamixer` to adjust volume levels
# run `sudo alsactl store` so save the volume levels persistently