fix(music-assistant): bind streams to net1 so Sendspin mDNS queries the IoT VLAN #1459

Merged
k.renner merged 1 commits from fix/ma-sendspin-mdns-net1 into main 2026-10-26 14:34:12 +00:00
Member

Problem

Nach jedem Music-Assistant-Neustart bleiben die Voice PEs stunden­lang getrennt (2026-10-23 16:52 → Reconnect erst 19:35 bzw. 03:00 am Folgetag; 2026-10-26 13:56 → 3 von 4 um 15:05 noch offline).

Ursache (verifiziert)

  • Sendspin ist bei Voice PE server-initiated: MA sucht _sendspin._tcp per mDNS und wählt das Gerät an.
  • aiosendspin 10.2.1 (server.py ~L1318): AsyncZeroconf(interfaces=[host] if host != "0.0.0.0" else InterfaceChoice.Default), host = streams bind_ip.
  • Reconcile erzwingt bind_ip=0.0.0.0 → Default → Multicast-Egress über die Main-Table-Default-Route = eth0; net1-Routen liegen in der sbr-Tabelle (/proc/net/route im Pod: nur eth0).
  • Folge: MA fragt das IoT-VLAN nie ab, sieht Voice PEs nur, wenn sie sich spontan ankündigen. Zusätzlich stoppt aiosendspin den Re-Query, sobald ein Client bekannt ist.

Fix

Reconcile schreibt bind_ip = live net1-Adresse (dieselbe wie publish_ip). Da sie jeden Boot vom Interface gelesen wird, kann der frühere Sendspin-No-Bind-Fallback-Fehler (veraltete Literal-IP) nicht wieder auftreten. Webserver (8095, Ingress) hat eigenes bind_ip und ist nicht betroffen.

Rollout / Verifikation

ConfigMap-Änderung wirkt beim nächsten Pod-Start. Nach Merge: MA neu starten, dann prüfen

  • reconcile-Log: bind_ip ... -> '10.2.30.x'
  • MA-Log: Sendspin server started successfully on 10.2.30.x:8927
  • alle 4 Voice PEs (09a684, 090718, 09078c, 09bf86) connected innerhalb ~1 min.

🤖 Generated with Claude Code

## Problem Nach jedem Music-Assistant-Neustart bleiben die Voice PEs stunden­lang getrennt (2026-10-23 16:52 → Reconnect erst 19:35 bzw. 03:00 am Folgetag; 2026-10-26 13:56 → 3 von 4 um 15:05 noch offline). ## Ursache (verifiziert) - Sendspin ist bei Voice PE **server-initiated**: MA sucht `_sendspin._tcp` per mDNS und wählt das Gerät an. - aiosendspin 10.2.1 (`server.py` ~L1318): `AsyncZeroconf(interfaces=[host] if host != "0.0.0.0" else InterfaceChoice.Default)`, `host` = streams `bind_ip`. - Reconcile erzwingt `bind_ip=0.0.0.0` → `Default` → Multicast-Egress über die Main-Table-Default-Route = **eth0**; net1-Routen liegen in der sbr-Tabelle (`/proc/net/route` im Pod: nur eth0). - Folge: MA fragt das IoT-VLAN nie ab, sieht Voice PEs nur, wenn sie sich spontan ankündigen. Zusätzlich stoppt aiosendspin den Re-Query, sobald *ein* Client bekannt ist. ## Fix Reconcile schreibt `bind_ip` = live net1-Adresse (dieselbe wie `publish_ip`). Da sie jeden Boot vom Interface gelesen wird, kann der frühere Sendspin-No-Bind-Fallback-Fehler (veraltete Literal-IP) nicht wieder auftreten. Webserver (8095, Ingress) hat eigenes bind_ip und ist nicht betroffen. ## Rollout / Verifikation ConfigMap-Änderung wirkt beim nächsten Pod-Start. Nach Merge: MA neu starten, dann prüfen - reconcile-Log: `bind_ip ... -> '10.2.30.x'` - MA-Log: `Sendspin server started successfully on 10.2.30.x:8927` - alle 4 Voice PEs (09a684, 090718, 09078c, 09bf86) `connected` innerhalb ~1 min. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-10-26 14:12:47 +00:00
fix(music-assistant): bind streams to net1 so Sendspin mDNS queries the IoT VLAN
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
af7103fd75
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 <noreply@anthropic.com>
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit af7103fd7573b5076ce7c3c82e44fc775eee06d5.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review.


Summary

The reconcile initContainer now writes bind_ip as the live net1 (v30iot) address instead of 0.0.0.0. The goal is for aiosendspin's zeroconf to send _sendspin._tcp queries onto the IoT VLAN, not out through eth0. The script change is small and consistent: the idempotency check, the assignment and the log line all use the same ip (configmap-net-reconcile.yaml:97,101-105). The old reason for 0.0.0.0 was a stale stored IP that the Sendspin server could not bind (PR #131). That can't come back, because the IP is re-read from the interface on every boot and the script refuses to start if it can't find one (:76-83). I found no blocking defects. This is a first review; there were no earlier reviews or comments.

⚪ Unverified

  • The root cause rests on how MA and aiosendspin behave, which I could not read from here. Three claims are untested: aiosendspin starts AsyncZeroconf(interfaces=[host] … else InterfaceChoice.Default); a specific address makes multicast go out on net1 despite the sbr setup; and the webserver (8095) uses its own bind_ip, so ingress and probes don't change. The probes hit port: http only (deployment.yaml:86-99), and the Service exposes only http (:139-142). So the k8s side is fine if the webserver claim holds. The rollout checks in the PR body ("Sendspin server started successfully on 10.2.30.x:8927", all 4 Voice PEs connected) are the right way to confirm this after merge.
  • Anything that reached the stream (8097) or Sendspin (8927) servers through eth0 or loopback will stop working. With bind_ip set to the net1 address, those servers no longer listen on the pod's eth0 IP or on 127.0.0.1.
    • In this directory, nothing targets those ports: the Service and Ingress expose only 8095.
    • Per PR #131, the in-process sendspin_proxy targets bind_ip when it isn't 0.0.0.0. That address is now local, so it should keep working.
    • I could not search the rest of the repo for code that uses those ports (e.g. under kubernetes/apps/home-assistant), and I could not check whether SlimProto (3483) or Snapcast also take this bind_ip. The PR quietly drops "slimproto" from the list in network-attachment.yaml:16-17. Please confirm that list is accurate, and check that squeezelite/SlimProto players still connect after the restart.

🟡 Suggestions

  • The head branch is based on an old commit. Its parent is 35a387a3 (Oct 19, merged into master), not the current main tip 1984b01e. As a result, deployment.yaml at head still pins server:2.10.7 (deployment.yaml:57,67). It also lacks the pocketcasts-overlay initContainer, its volumes, and kumamonitors.yaml, all of which are on main.
    • Gitea reports mergeable: true, and the PR only changes comment lines 47-49, which don't overlap those changes. So a 3-way merge should keep main's content.
    • Still, please rebase onto main. Then CI and any manual kubectl kustomize check run on the tree that will actually ship.
  • Merging won't restart MA by itself (kustomization.yaml on main). music-assistant-net-reconcile is a plain resource, not a configMapGenerator entry, so Flux updating it doesn't roll the pod. The PR body already says to restart MA manually. Moving it into the existing configMapGenerator block, like the pocketcasts ConfigMap, would make this and future reconcile changes roll out automatically.

Tests

The repo has no tests for reconcile.py. The change is three lines and keeps the existing idempotent, atomic-write structure. The real test is the runtime check in the PR body. Please also confirm SlimProto/Snapcast players and the web UI's "this device" Sendspin player after the restart.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `claude-opus-5-5`, commit `af7103fd7573b5076ce7c3c82e44fc775eee06d5`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review. --- ## Summary The reconcile initContainer now writes `bind_ip` as the live net1 (v30iot) address instead of `0.0.0.0`. The goal is for aiosendspin's zeroconf to send `_sendspin._tcp` queries onto the IoT VLAN, not out through eth0. The script change is small and consistent: the idempotency check, the assignment and the log line all use the same `ip` (`configmap-net-reconcile.yaml:97,101-105`). The old reason for `0.0.0.0` was a stale stored IP that the Sendspin server could not bind (PR #131). That can't come back, because the IP is re-read from the interface on every boot and the script refuses to start if it can't find one (`:76-83`). I found no blocking defects. This is a first review; there were no earlier reviews or comments. ## ⚪ Unverified - **The root cause rests on how MA and aiosendspin behave, which I could not read from here.** Three claims are untested: aiosendspin starts `AsyncZeroconf(interfaces=[host] … else InterfaceChoice.Default)`; a specific address makes multicast go out on net1 despite the sbr setup; and the webserver (8095) uses its own `bind_ip`, so ingress and probes don't change. The probes hit `port: http` only (`deployment.yaml:86-99`), and the Service exposes only `http` (`:139-142`). So the k8s side is fine if the webserver claim holds. The rollout checks in the PR body ("Sendspin server started successfully on 10.2.30.x:8927", all 4 Voice PEs connected) are the right way to confirm this after merge. - **Anything that reached the stream (8097) or Sendspin (8927) servers through eth0 or loopback will stop working.** With `bind_ip` set to the net1 address, those servers no longer listen on the pod's eth0 IP or on 127.0.0.1. - In this directory, nothing targets those ports: the Service and Ingress expose only 8095. - Per PR #131, the in-process `sendspin_proxy` targets `bind_ip` when it isn't `0.0.0.0`. That address is now local, so it should keep working. - I could not search the rest of the repo for code that uses those ports (e.g. under `kubernetes/apps/home-assistant`), and I could not check whether SlimProto (3483) or Snapcast also take this `bind_ip`. The PR quietly drops "slimproto" from the list in `network-attachment.yaml:16-17`. Please confirm that list is accurate, and check that squeezelite/SlimProto players still connect after the restart. ## 🟡 Suggestions - **The head branch is based on an old commit.** Its parent is `35a387a3` (Oct 19, merged into `master`), not the current `main` tip `1984b01e`. As a result, `deployment.yaml` at head still pins `server:2.10.7` (`deployment.yaml:57,67`). It also lacks the `pocketcasts-overlay` initContainer, its volumes, and `kumamonitors.yaml`, all of which are on `main`. - Gitea reports `mergeable: true`, and the PR only changes comment lines 47-49, which don't overlap those changes. So a 3-way merge should keep `main`'s content. - Still, please rebase onto `main`. Then CI and any manual `kubectl kustomize` check run on the tree that will actually ship. - **Merging won't restart MA by itself** (`kustomization.yaml` on main). `music-assistant-net-reconcile` is a plain resource, not a `configMapGenerator` entry, so Flux updating it doesn't roll the pod. The PR body already says to restart MA manually. Moving it into the existing `configMapGenerator` block, like the pocketcasts ConfigMap, would make this and future reconcile changes roll out automatically. ## Tests The repo has no tests for `reconcile.py`. The change is three lines and keeps the existing idempotent, atomic-write structure. The real test is the runtime check in the PR body. Please also confirm SlimProto/Snapcast players and the web UI's "this device" Sendspin player after the restart. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=af7103fd7573b5076ce7c3c82e44fc775eee06d5 -->
k.renner merged commit 532b853929 into main 2026-10-26 14:34:12 +00:00
Sign in to join this conversation.