From 7797b66e3ac5c3da052741fca1d532af3ac5b86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 03:54:00 +0100 Subject: [PATCH 1/8] shairport-sync: add enableAvahi and enableTinySVCmDNS options --- pkgs/by-name/sh/shairport-sync/package.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgs/by-name/sh/shairport-sync/package.nix b/pkgs/by-name/sh/shairport-sync/package.nix index 7e4ef2aec05a..10951c7864e3 100644 --- a/pkgs/by-name/sh/shairport-sync/package.nix +++ b/pkgs/by-name/sh/shairport-sync/package.nix @@ -27,6 +27,7 @@ soxr, alac, sndio, + enableAvahi ? true, enableAirplay2 ? false, enableStdout ? true, enableAlsa ? true, @@ -45,6 +46,7 @@ enableAlac ? true, enableConvolution ? true, enableLibdaemon ? false, + enableTinySVCmDNS ? true, }: let @@ -78,10 +80,10 @@ stdenv.mkDerivation (finalAttrs: { buildInputs = [ openssl - avahi popt libconfig ] + ++ optional enableAvahi avahi ++ optional enableLibdaemon libdaemon ++ optional enableAlsa alsa-lib ++ optional enableSndio sndio @@ -115,8 +117,8 @@ stdenv.mkDerivation (finalAttrs: { "--sysconfdir=/etc" "--with-ssl=openssl" "--with-stdout" - "--with-avahi" ] + ++ optional enableAvahi "--with-avahi" ++ optional enablePulse "--with-pa" ++ optional enablePipewire "--with-pw" ++ optional enableAlsa "--with-alsa" @@ -133,6 +135,7 @@ stdenv.mkDerivation (finalAttrs: { ++ optional enableMetadata "--with-metadata" ++ optional enableMpris "--with-mpris-interface" ++ optional enableMqttClient "--with-mqtt-client" + ++ optional enableTinySVCmDNS "--with-tinysvcmdns" ++ optional enableLibdaemon "--with-libdaemon" ++ optional enableAirplay2 "--with-airplay-2"; From 7684cfcfeec8321815f0fda9ce8b4f3588f6b93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 03:53:27 +0100 Subject: [PATCH 2/8] music-assistant: replace pre-compiled shairport-sync binaries --- .../services/audio/music-assistant.nix | 11 ++++++++++ pkgs/by-name/mu/music-assistant/package.nix | 7 ++++++- .../mu/music-assistant/shairport-sync.patch | 20 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 pkgs/by-name/mu/music-assistant/shairport-sync.patch diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix index 40646f33c572..1bef47c20d5a 100644 --- a/nixos/modules/services/audio/music-assistant.nix +++ b/nixos/modules/services/audio/music-assistant.nix @@ -68,6 +68,14 @@ in }; config = mkIf cfg.enable { + services.avahi = lib.mkIf (lib.elem "airplay_receiver" cfg.providers) { + enable = true; + publish = { + enable = true; + userServices = true; + }; + }; + systemd.services.music-assistant = { description = "Music Assistant"; documentation = [ "https://music-assistant.io" ]; @@ -87,6 +95,9 @@ in [ lsof ] + ++ lib.optionals (lib.elem "airplay_receiver" cfg.providers) [ + shairport-sync + ] ++ lib.optionals (lib.elem "spotify" cfg.providers) [ librespot-ma ] diff --git a/pkgs/by-name/mu/music-assistant/package.nix b/pkgs/by-name/mu/music-assistant/package.nix index 9b0e4ecdce32..2edf5f216688 100644 --- a/pkgs/by-name/mu/music-assistant/package.nix +++ b/pkgs/by-name/mu/music-assistant/package.nix @@ -66,6 +66,9 @@ python.pkgs.buildPythonApplication rec { # Look up librespot from PATH at runtime ./librespot.patch + # Look up shairport-sync from PATH at runtime + ./shairport-sync.patch + # Disable interactive dependency resolution, which clashes with the immutable Python environment ./dont-install-deps.patch @@ -91,7 +94,9 @@ python.pkgs.buildPythonApplication rec { substituteInPlace pyproject.toml \ --replace-fail "get-mac" "getmac" - rm -rv music_assistant/providers/spotify/bin + rm -rv \ + music_assistant/providers/airplay_receiver/bin/{build_binaries.sh,shairport-sync-*} \ + music_assistant/providers/spotify/bin ''; build-system = with python.pkgs; [ diff --git a/pkgs/by-name/mu/music-assistant/shairport-sync.patch b/pkgs/by-name/mu/music-assistant/shairport-sync.patch new file mode 100644 index 000000000000..298d06f59449 --- /dev/null +++ b/pkgs/by-name/mu/music-assistant/shairport-sync.patch @@ -0,0 +1,20 @@ +diff --git a/music_assistant/providers/airplay_receiver/helpers.py b/music_assistant/providers/airplay_receiver/helpers.py +index 6f2b35df..dd839b73 100644 +--- a/music_assistant/providers/airplay_receiver/helpers.py ++++ b/music_assistant/providers/airplay_receiver/helpers.py +@@ -22,15 +22,9 @@ async def check_shairport_sync(shairport_path: str) -> str | None: + except OSError: + return None + +- # First, check if bundled binary exists +- base_path = os.path.join(os.path.dirname(__file__), "bin") + system = platform.system().lower().replace("darwin", "macos") + architecture = platform.machine().lower() + +- if shairport_binary := await check_shairport_sync( +- os.path.join(base_path, f"shairport-sync-{system}-{architecture}") +- ): +- return shairport_binary + + # If no bundled binary, check system PATH + if system_binary := shutil.which("shairport-sync"): From c6be07d7b5a85e8bf1ff83d26e098c4ef4bc583f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 03:54:31 +0100 Subject: [PATCH 3/8] cliairplay: init at 0.2-unstable-2025-12-30 --- pkgs/by-name/cl/cliairplay/package.nix | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 pkgs/by-name/cl/cliairplay/package.nix diff --git a/pkgs/by-name/cl/cliairplay/package.nix b/pkgs/by-name/cl/cliairplay/package.nix new file mode 100644 index 000000000000..9b5aa51a4fe9 --- /dev/null +++ b/pkgs/by-name/cl/cliairplay/package.nix @@ -0,0 +1,81 @@ +{ + lib, + autoreconfHook, + bison, + curl, + fetchFromGitHub, + fetchpatch, + ffmpeg-headless, + flex, + gperf, + json_c, + libconfuse, + libevent, + libgcrypt, + libgpg-error, + libplist, + libsodium, + libunistring, + libuuid, + libxml2, + pkg-config, + stdenv, + zlib, +}: + +stdenv.mkDerivation { + pname = "cliairplay"; + # see the beginning of configure.ac for the upstream version number + version = "0.2-unstable-2025-12-30"; + + src = fetchFromGitHub { + owner = "music-assistant"; + repo = "cliairplay"; + # we try to closely match the commit used in the last music-assistant release from + # https://github.com/music-assistant/server/tree/stable/music_assistant/providers/airplay/bin + rev = "4660d886585d6bf8f32e889feec2a0e8975c51dc"; + fetchSubmodules = true; + hash = "sha256-oDStn9LdLYWKhZNm7Qfdibs4qsct8gE3RZbTKooQeOM="; + }; + + patches = [ + # Support gettext 0.25 + (fetchpatch { + url = "https://github.com/music-assistant/cliairplay/commit/92a2445d64c476d740feba1f31c7e5bc768701b7.patch"; + hash = "sha256-/YnopvAGHHnQhfr2X1OenLMoF4ZlUq9x8tQZha/XfbQ="; + }) + ]; + + nativeBuildInputs = [ + autoreconfHook + bison + flex + gperf + pkg-config + ]; + + buildInputs = [ + curl + ffmpeg-headless + json_c + libconfuse + libevent + libgcrypt + libgpg-error + libplist + libsodium + libunistring + libuuid + libxml2 + zlib + ]; + + meta = { + description = "Command line interface for audio streaming to AirPlay 2 devices"; + homepage = "https://github.com/music-assistant/cliairplay"; + license = lib.licenses.gpl2Plus; + maintainers = with lib.maintainers; [ SuperSandro2000 ]; + mainProgram = "cliap2"; + platforms = lib.platforms.unix; + }; +} From 7abc4361f83145c9a8089b230a910a919e170fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 03:54:43 +0100 Subject: [PATCH 4/8] libraop: init at 0-unstable-2026-02-09 --- pkgs/by-name/li/libraop/link-libssl.diff | 15 +++++ pkgs/by-name/li/libraop/package.nix | 83 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 pkgs/by-name/li/libraop/link-libssl.diff create mode 100644 pkgs/by-name/li/libraop/package.nix diff --git a/pkgs/by-name/li/libraop/link-libssl.diff b/pkgs/by-name/li/libraop/link-libssl.diff new file mode 100644 index 000000000000..975826c30276 --- /dev/null +++ b/pkgs/by-name/li/libraop/link-libssl.diff @@ -0,0 +1,15 @@ +diff --git a/Makefile b/Makefile +index 7d735b8..c3ea73a 100644 +--- a/Makefile ++++ b/Makefile +@@ -68,7 +68,9 @@ OBJECTS_BIN += $(patsubst %.cpp,$(BUILDDIR)/%.o,$(filter %.cpp,$(SOURCES_BIN))) + + LIBRARY = $(CODECS)/$(HOST)/$(PLATFORM)/libcodecs.a $(MDNS)/$(HOST)/$(PLATFORM)/libmdns.a + +-ifneq ($(STATIC),) ++ifeq ($(STATIC),) ++LDFLAGS += -lcrypto ++else + LIBRARY += $(OPENSSL)/libopenssl.a + DEFINES += -DSSL_STATIC_LIB + endif diff --git a/pkgs/by-name/li/libraop/package.nix b/pkgs/by-name/li/libraop/package.nix new file mode 100644 index 000000000000..036cc1e311da --- /dev/null +++ b/pkgs/by-name/li/libraop/package.nix @@ -0,0 +1,83 @@ +{ + lib, + fetchFromGitHub, + openssl, + stdenv, +}: + +let + host = + if stdenv.hostPlatform.isLinux then + "linux" + else if stdenv.hostPlatform.isDarwin then + "macos" + else + throw "libraop does not support this platform, yet"; +in +stdenv.mkDerivation { + pname = "libraop"; + version = "0-unstable-2026-02-09"; + + src = fetchFromGitHub { + owner = "music-assistant"; + repo = "libraop"; + # we try to closely match the commit used in the last music-assistant release from + # https://github.com/music-assistant/server/tree/stable/music_assistant/providers/airplay/bin + rev = "f49284282ea4ea740d07fabc230b4182f8c69a74"; + fetchSubmodules = true; + hash = "sha256-m1ll5vRZx4d/5IWCG24yY/SWEIIz2k/iU84vQKHlCdo="; + }; + + patches = [ + # https://github.com/philippe44/libraop/pull/48 + ./link-libssl.diff + ]; + + postPatch = '' + # the most security critical part we build ourself + rm -r libopenssl/ + + # do not confuse the prebuilt binaries with the ones we build + rm bin/* + + # easen debugging and we strip ourselves, too + substituteInPlace Makefile \ + --replace-fail "LDFLAGS += -s" "LDFLAGS +=" + '' + + lib.optionalString stdenv.hostPlatform.isDarwin '' + # on darwin the direct dlopen to system libcrypto crashes with + # WARNING: /nix/store/.../bin/cliraop is loading libcrypto in an unsafe way + # Abort trap: 6 + substituteInPlace crosstools/src/cross_ssl.c \ + --replace-fail '"libcrypto.dylib"' '"${lib.getLib openssl}/lib/libcrypto.dylib"' \ + --replace-fail '"libssl.dylib"' '"${lib.getLib openssl}/lib/libssl.dylib"' + ''; + + buildInputs = [ + openssl + ]; + + makeFlags = [ + "HOST=${host}" + "PLATFORM=${stdenv.hostPlatform.uname.processor}" + ]; + + installPhase = '' + mkdir -p $out/bin + cp bin/cliraop-${host}-${stdenv.hostPlatform.uname.processor} $out/bin/cliraop + ''; + + meta = { + description = "RAOP player and library (AirPlay)"; + homepage = "https://github.com/music-assistant/libraop"; + # https://github.com/philippe44/libraop/issues/36 + license = with lib.licenses; [ + gpl2Only + mit + ]; + maintainers = with lib.maintainers; [ SuperSandro2000 ]; + mainProgram = "cliraop"; + platforms = with lib.platforms; linux ++ darwin; + sourceProvenance = [ lib.sourceTypes.binaryNativeCode ]; + }; +} From 5b65c02b1cb213f3e30a1715e5c2c114cf8eecb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 03:55:10 +0100 Subject: [PATCH 5/8] music-assistant: bring back airplay support --- .../services/audio/music-assistant.nix | 4 +++ .../mu/music-assistant/cliraop-cliap2.patch | 29 +++++++++++++++++++ pkgs/by-name/mu/music-assistant/package.nix | 14 +++------ 3 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 pkgs/by-name/mu/music-assistant/cliraop-cliap2.patch diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix index 1bef47c20d5a..c4717bb5817f 100644 --- a/nixos/modules/services/audio/music-assistant.nix +++ b/nixos/modules/services/audio/music-assistant.nix @@ -95,6 +95,10 @@ in [ lsof ] + ++ lib.optionals (lib.elem "airplay" cfg.providers) [ + cliairplay + libraop + ] ++ lib.optionals (lib.elem "airplay_receiver" cfg.providers) [ shairport-sync ] diff --git a/pkgs/by-name/mu/music-assistant/cliraop-cliap2.patch b/pkgs/by-name/mu/music-assistant/cliraop-cliap2.patch new file mode 100644 index 000000000000..33b856711eed --- /dev/null +++ b/pkgs/by-name/mu/music-assistant/cliraop-cliap2.patch @@ -0,0 +1,29 @@ +diff --git a/music_assistant/providers/airplay/helpers.py b/music_assistant/providers/airplay/helpers.py +index c5fef7eb..a628622d 100644 +--- a/music_assistant/providers/airplay/helpers.py ++++ b/music_assistant/providers/airplay/helpers.py +@@ -6,6 +6,7 @@ + import os + import platform + import time ++from shutil import which + from typing import TYPE_CHECKING + + from zeroconf import IPVersion +@@ -176,7 +177,6 @@ async def check_binary(cli_path: str) -> str | None: + pass + return None + +- base_path = os.path.join(os.path.dirname(__file__), "bin") + system = platform.system().lower().replace("darwin", "macos") + architecture = platform.machine().lower() + +@@ -188,7 +188,7 @@ async def check_binary(cli_path: str) -> str | None: + raise RuntimeError(f"Unsupported streaming protocol requested: {protocol}") + + if bridge_binary := await check_binary( +- os.path.join(base_path, f"{package}-{system}-{architecture}") ++ which(package) + ): + return bridge_binary + diff --git a/pkgs/by-name/mu/music-assistant/package.nix b/pkgs/by-name/mu/music-assistant/package.nix index 2edf5f216688..c1b94ebc6302 100644 --- a/pkgs/by-name/mu/music-assistant/package.nix +++ b/pkgs/by-name/mu/music-assistant/package.nix @@ -40,11 +40,6 @@ let pythonPath = python.pkgs.makePythonPath providerDependencies; in - -assert - (lib.elem "airplay" providers) - -> throw "music-assistant: airplay support is missing libraop, a library we will not package because it depends on OpenSSL 1.1."; - python.pkgs.buildPythonApplication rec { pname = "music-assistant"; version = "2.7.8"; @@ -69,6 +64,9 @@ python.pkgs.buildPythonApplication rec { # Look up shairport-sync from PATH at runtime ./shairport-sync.patch + # Look up cliraop/cliap2 from PATH at runtime + ./cliraop-cliap2.patch + # Disable interactive dependency resolution, which clashes with the immutable Python environment ./dont-install-deps.patch @@ -95,6 +93,7 @@ python.pkgs.buildPythonApplication rec { --replace-fail "get-mac" "getmac" rm -rv \ + music_assistant/providers/airplay/bin/{cliap2-*,cliraop-*} \ music_assistant/providers/airplay_receiver/bin/{build_binaries.sh,shairport-sync-*} \ music_assistant/providers/spotify/bin ''; @@ -196,11 +195,6 @@ python.pkgs.buildPythonApplication rec { pythonImportsCheck = [ "music_assistant" ]; - postFixup = '' - # binary native code, segfaults when autopatchelf'd, requires openssl 1.1 to build - rm $out/${python3.sitePackages}/music_assistant/providers/airplay/bin/cliraop-* - ''; - passthru = { inherit python From 637483dc39223f1d2984546e1b0442c3f3e32942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 03:55:25 +0100 Subject: [PATCH 6/8] music-assistant: fail loudly on updates when new pre-compiled binaries are added --- pkgs/by-name/mu/music-assistant/package.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkgs/by-name/mu/music-assistant/package.nix b/pkgs/by-name/mu/music-assistant/package.nix index c1b94ebc6302..e4019090dd8e 100644 --- a/pkgs/by-name/mu/music-assistant/package.nix +++ b/pkgs/by-name/mu/music-assistant/package.nix @@ -96,6 +96,12 @@ python.pkgs.buildPythonApplication rec { music_assistant/providers/airplay/bin/{cliap2-*,cliraop-*} \ music_assistant/providers/airplay_receiver/bin/{build_binaries.sh,shairport-sync-*} \ music_assistant/providers/spotify/bin + + found_bins=$(find music_assistant/ -wholename '*/bin/*' -type f -executable -print0 | tr '\0' ' ') + if [[ -n $found_bins ]]; then + echo "Found binaries that should be replaced with packages built from source: $found_bins" + exit 2 + fi ''; build-system = with python.pkgs; [ From 94081af8d17240aa7b7db5ad1a6b9a4b0e41c29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 20 Feb 2026 04:37:45 +0100 Subject: [PATCH 7/8] shairport-sync: remove duplicated --with-stdout configure flag --- pkgs/by-name/sh/shairport-sync/package.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/pkgs/by-name/sh/shairport-sync/package.nix b/pkgs/by-name/sh/shairport-sync/package.nix index 10951c7864e3..8204c1fc5003 100644 --- a/pkgs/by-name/sh/shairport-sync/package.nix +++ b/pkgs/by-name/sh/shairport-sync/package.nix @@ -116,7 +116,6 @@ stdenv.mkDerivation (finalAttrs: { "--without-configfiles" "--sysconfdir=/etc" "--with-ssl=openssl" - "--with-stdout" ] ++ optional enableAvahi "--with-avahi" ++ optional enablePulse "--with-pa" From fbe6eb952a657ef372548a6fa45d9da9ded13de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Tue, 17 Mar 2026 01:45:14 +0100 Subject: [PATCH 8/8] nixos/music-assistant: add openFirewall option to open all required ports for airplay or sendspin as otherwise airplay (raopcli) just coredumps. --- .../services/audio/music-assistant.nix | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix index c4717bb5817f..a9f605fcf461 100644 --- a/nixos/modules/services/audio/music-assistant.nix +++ b/nixos/modules/services/audio/music-assistant.nix @@ -16,6 +16,7 @@ let ; inherit (types) + bool listOf enum str @@ -54,6 +55,15 @@ in ''; }; + openFirewall = lib.mkOption { + type = bool; + default = false; + description = '' + Whether to open required ports for the configured providers. + Currently airplay and sendspin need port to be opened to function. + ''; + }; + providers = mkOption { type = listOf (enum cfg.package.providerNames); default = [ ]; @@ -68,6 +78,23 @@ in }; config = mkIf cfg.enable { + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = + lib.optional cfg.enable 8097 # Music Assistant stream port + ++ lib.optional (lib.elem "airplay" cfg.providers) 7000 + ++ lib.optional (lib.elem "sendspin" cfg.providers) 8927; + # The information published by Apple 1 seem to not apply to libraop. + # The closest we could find that represents the port range being used as observed by tcpdump is the ephemeral port range. + # 1: https://support.apple.com/en-us/103229#:~:text=49152%E2%80%93-,65535,-TCP%2C%20UDP + # 2: https://en.wikipedia.org/wiki/Ephemeral_port#Range + allowedUDPPortRanges = lib.mkIf (lib.elem "airplay" cfg.providers) [ + { + from = 32768; + to = 65535; + } + ]; + }; + services.avahi = lib.mkIf (lib.elem "airplay_receiver" cfg.providers) { enable = true; publish = {