{ pkgs, config, lib, python3Packages, ... }: let domain = "snapcast.cloonar.com"; mopidyDomain = "mopidy.cloonar.com"; networkPrefix = config.networkPrefix; in { security.acme.certs."${domain}" = { group = "nginx"; }; security.acme.certs."${mopidyDomain}" = { group = "nginx"; }; sops.secrets.mopidy-spotify = { }; containers.snapcast = { autoStart = true; ephemeral = false; # because of ssh key privateNetwork = true; hostBridge = "server"; hostAddress = "${networkPrefix}.97.1"; localAddress = "${networkPrefix}.97.21/24"; extraFlags = [ "--capability=CAP_NET_ADMIN" ]; bindMounts = { "/var/lib/acme/snapcast/" = { hostPath = "${config.security.acme.certs.${domain}.directory}"; isReadOnly = true; }; "/var/lib/acme/mopidy/" = { hostPath = "${config.security.acme.certs.${mopidyDomain}.directory}"; isReadOnly = true; }; "/run/secrets/mopidy-spotify" = { hostPath = "${config.sops.secrets.mopidy-spotify.path}"; }; }; config = { lib, config, pkgs, python3Packages, ... }: let shairport-sync = pkgs.shairport-sync.overrideAttrs (_: { configureFlags = [ "--with-alsa" "--with-pipe" "--with-pa" "--with-stdout" "--with-avahi" "--with-ssl=openssl" "--with-soxr" "--without-configfiles" "--sysconfdir=/etc" "--with-metadata" ]; }); snapweb = pkgs.stdenv.mkDerivation { pname = "snapweb"; version = "0.8"; src = pkgs.fetchzip { url = "https://github.com/badaix/snapweb/releases/download/v0.8.0/snapweb.zip"; sha256 = "sha256-IpT1pcuzcM8kqWJUX3xxpRQHlfPNsrwhemLmY0PyzjI="; stripRoot = false; }; installPhase = '' mkdir -p $out cp -r $src/* $out/ ''; }; mopidy-autoplay = pkgs.python3Packages.buildPythonApplication rec { pname = "Mopidy-Autoplay"; version = "0.2.3"; src = pkgs.python3Packages.fetchPypi { inherit pname version; sha256 = "sha256-E2Q+Cn2LWSbfoT/gFzUfChwl67Mv17uKmX2woFz/3YM="; }; propagatedBuildInputs = [ pkgs.mopidy ] ++ (with pkgs.python3Packages; [ configobj ]); # no tests implemented doCheck = false; meta = with lib; { homepage = "https://codeberg.org/sph/mopidy-autoplay"; }; }; in { networking = { hostName = "snapcast"; useHostResolvConf = false; defaultGateway = { address = "${networkPrefix}.97.1"; interface = "eth0"; }; nameservers = [ "${networkPrefix}.97.1" ]; firewall.enable = false; }; environment.systemPackages = with pkgs; [ # shanocast ]; environment.etc = { # Creates /etc/nanorc shairport = { text = '' whatever you want to put in the file goes here. metadata = { enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes". cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems pipe_name = "/tmp/shairport-sync-metadata"; pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up }; ''; # The UNIX file mode bits mode = "0440"; }; }; systemd.tmpfiles.rules = [ "p /run/snapserver/mopidyfifo 0660 mopidy snapserver -" ]; services.mopidy = { enable = true; extensionPackages = [ pkgs.mopidy-iris pkgs.mopidy-tunein pkgs.mopidy-spotify mopidy-autoplay ]; configuration = '' [audio] output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! wavenc ! filesink location=/run/snapserver/mopidyfifo [file] enabled = false [autoplay] enabled = true ''; extraConfigFiles = [ "/run/secrets/mopidy-spotify" ]; }; services.snapserver = { enable = true; codec = "flac"; http.enable = true; http.docRoot = "${snapweb}/"; buffer = 1000; streamBuffer = 1000; streams.airplay = { type = "airplay"; location = "${shairport-sync}/bin/shairport-sync"; query = { devicename = "Multi Room"; port = "5000"; params = "--mdns=avahi"; }; sampleFormat = "44100:16:2"; codec = "pcm"; }; streams.mopidy = { type = "pipe"; location = "/run/snapserver/mopidyfifo"; }; streams.mixed = { type = "meta"; location = "meta:///airplay/mopidy?name=Mixed&sampleformat=44100:16:2"; codec = "opus"; }; }; # run after tmpfiles-setup systemd.services.snapserver = { after = [ "systemd-tmpfiles-setup.service" ]; requires = [ "systemd-tmpfiles-setup.service" ]; }; systemd.services.mopidy = { after = [ "systemd-tmpfiles-setup.service" ]; requires = [ "systemd-tmpfiles-setup.service" ]; }; services.avahi.enable = true; services.avahi.publish.enable = true; services.avahi.publish.userServices = true; services.nginx.enable = true; services.nginx.virtualHosts."${domain}" = { sslCertificate = "/var/lib/acme/snapcast/fullchain.pem"; sslCertificateKey = "/var/lib/acme/snapcast/key.pem"; sslTrustedCertificate = "/var/lib/acme/snapcast/chain.pem"; forceSSL = true; extraConfig = '' proxy_buffering off; ''; locations."/".extraConfig = '' proxy_pass http://127.0.0.1:1780; proxy_set_header Host $host; proxy_redirect http:// https://; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; ''; }; services.nginx.virtualHosts."${mopidyDomain}" = { sslCertificate = "/var/lib/acme/mopidy/fullchain.pem"; sslCertificateKey = "/var/lib/acme/mopidy/key.pem"; sslTrustedCertificate = "/var/lib/acme/mopidy/chain.pem"; forceSSL = true; extraConfig = '' proxy_buffering off; ''; locations."/".extraConfig = '' proxy_pass http://127.0.0.1:6680; proxy_set_header Host $host; proxy_redirect http:// https://; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; ''; }; system.stateVersion = "23.05"; }; }; }