From af7103fd7573b5076ce7c3c82e44fc775eee06d5 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Mon, 26 Oct 2026 15:12:32 +0100 Subject: [PATCH] fix(music-assistant): bind streams to net1 so Sendspin mDNS queries the IoT VLAN aiosendspin starts its zeroconf on [bind_ip], or on InterfaceChoice.Default when bind_ip is 0.0.0.0. Default egresses via the pod's main-table default route (eth0); net1 routes sit in the sbr table. MA therefore never sent _sendspin._tcp queries onto v30iot and only rediscovered Voice PEs when they announced on their own, leaving them disconnected for hours after every MA restart (e.g. 2026-10-26 13:56: 3 of 4 still offline at 15:05). The reconcile initContainer now writes bind_ip = the live net1 address it already computes for publish_ip. The address is read from the interface on every boot, so the Sendspin no-bind-fallback failure (stale literal IP) cannot recur. Co-Authored-By: Claude Opus 5.5 --- .../configmap-net-reconcile.yaml | 17 ++++++++++------- kubernetes/apps/music-assistant/deployment.yaml | 4 +++- .../music-assistant/network-attachment.yaml | 6 +++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/kubernetes/apps/music-assistant/configmap-net-reconcile.yaml b/kubernetes/apps/music-assistant/configmap-net-reconcile.yaml index ccfa8dfa..a85a20ef 100644 --- a/kubernetes/apps/music-assistant/configmap-net-reconcile.yaml +++ b/kubernetes/apps/music-assistant/configmap-net-reconcile.yaml @@ -21,10 +21,13 @@ data: the pod's default route — which players on the IoT VLAN cannot route to. The v30iot address lives on the net1 macvlan child (see network-attachment.yaml). - - bind_ip must be 0.0.0.0. The Sendspin server (aiosendspin, port 8927) has - NO all-interfaces fallback: if bind_ip is a specific IP that is not local - it fails to bind and the server never starts, and its proxy then targets - that dead IP instead of 127.0.0.1. Binding 0.0.0.0 avoids both. + - bind_ip must be the same live v30iot address. aiosendspin starts its + zeroconf on [bind_ip], or on InterfaceChoice.Default for 0.0.0.0; Default + sends mDNS queries via the main-table default route (eth0, net1 routes + live in the sbr table), so MA never queries the IoT VLAN for + _sendspin._tcp and Voice PEs are only rediscovered when they announce + on their own. The Sendspin server has no all-interfaces bind fallback, so + bind_ip must be local at start; it is, because it is read from net1 here. The v30iot IP is DHCP and node-dependent (MA is not node-pinned), so a stored IP is a stale assumption the moment MA reschedules or the lease changes - @@ -91,15 +94,15 @@ data: .setdefault("values", {}) ) old_pub, old_bind = values.get("publish_ip"), values.get("bind_ip") - if (old_pub, old_bind) == (ip, "0.0.0.0"): + if (old_pub, old_bind) == (ip, ip): print("[reconcile] publish_ip/bind_ip already correct; no change") return 0 values["publish_ip"] = ip - values["bind_ip"] = "0.0.0.0" + values["bind_ip"] = ip print( f"[reconcile] publish_ip {old_pub!r} -> {ip!r}, " - f"bind_ip {old_bind!r} -> '0.0.0.0'" + f"bind_ip {old_bind!r} -> {ip!r}" ) tmp = SETTINGS + ".reconcile.tmp" diff --git a/kubernetes/apps/music-assistant/deployment.yaml b/kubernetes/apps/music-assistant/deployment.yaml index d6bb0172..5d04c0b5 100644 --- a/kubernetes/apps/music-assistant/deployment.yaml +++ b/kubernetes/apps/music-assistant/deployment.yaml @@ -44,7 +44,9 @@ spec: # which IoT players can't reach; and # - net1 is DHCP and MA is not node-pinned, so any hard-coded IP goes # stale on the next reschedule or lease change (the Sendspin server - # then fails to bind and dies, with no fallback). + # then fails to bind and dies, with no fallback); and + # - a 0.0.0.0 bind_ip makes Sendspin's mDNS browse query via eth0, so + # Voice PEs on the IoT VLAN are not rediscovered after a restart. # So we recompute the v30iot IP from the live interface every boot and # write it into /data/settings.json before MA reads it. Multus attaches # net1 (with its DHCP lease) before any container runs, so this sees the diff --git a/kubernetes/apps/music-assistant/network-attachment.yaml b/kubernetes/apps/music-assistant/network-attachment.yaml index 33b72df9..17f9e107 100644 --- a/kubernetes/apps/music-assistant/network-attachment.yaml +++ b/kubernetes/apps/music-assistant/network-attachment.yaml @@ -13,9 +13,9 @@ # initContainer (configmap-net-reconcile.yaml) discovers net1's leased # address the same way it used to discover the host's ens19 address — # scanning the pod's own interfaces for a 10.2.30.0/24 address — and -# writes it as MA's publish_ip. bind_ip stays 0.0.0.0, which now binds -# eth0 (pod) + net1; players reach the stream/slimproto/sendspin servers -# on net1. Requires the cni-dhcp-daemon DaemonSet in infrastructure/multus. +# writes it as MA's publish_ip and bind_ip; players reach the +# stream/sendspin servers on net1. Requires the cni-dhcp-daemon DaemonSet +# in infrastructure/multus. # # Mirrors matter-server-iot; the only difference is MA does not speak # Thread, so the matter NAD's accept_ra_rt_info_max_plen tuning (for the -- 2.54.0