Sonene trenger ikke stå i [zones] på sentralen. En sone sender hjerteslag
hvert 30. s (POST /api/mesh/register med navn, egen adresse og
mesh-nøkkelen), dukker opp i lista med en gang, og tas ut etter 90 s uten
slag. Riktig nøkkel er godkjenningen. Statisk [zones] vinner over et navn
som melder seg (409) – en skrivefeil i config skal ikke kunne overstyres av
en boks på nettet.
Hvorfor push og ikke pull fra NetBird: netbird status --json gir navn, IP og
status per peer, men ikke grupper. Pull ville krevd en service-token mot
management-serveren og virket bare over NetBird. Push virker likt over
NetBird og LAN, uten token, og gir liveness gratis.
Sonen finner sentralen lagvis: ANNOUNCER_SENTRAL hvis satt (NetBird-DNS-navn,
konvensjon announcer-<navn>.skynet.corp), ellers mDNS (_announcer._tcp,
discovery.py). Egen adresse regnes ut som ruten mot sentralen – over NetBird
blir det wt0-adressen, på LAN LAN-adressen – med ANNOUNCER_URL som
overstyring. Hjerteslag hvert 5. s til første OK, så oppstart ikke tar et
halvt minutt.
Containeren bruker nå vertens nett. mDNS-multicast når ikke LAN-et fra
bridge-nettet, så det var ingen vei utenom – og det ga tilbake ufw: det
gamle Docker-smutthullet er borte, portregler virker igjen, og BIND_IP er
appens eget --host. Appen lytter alltid også på 127.0.0.1 for helsesjekken.
install.sh tar NETBIRD_SETUP_KEY: installerer NetBird, melder maskina inn
som announcer-<navn>, venter på wt0, binder til NetBird-adressen.
Management-URL leses fra /etc/netbird/config.json når den finnes.
Rollen avgjøres av config (Config.role): sentral hvis [zones] eller
ANNOUNCER_ROLE=sentral, sone hvis bare nøkkelen er satt, ellers som før.
Dispatcher og ZonePoller slår opp soner ved hver bruk, siden de kommer og
går. register følger, som play, aldri med «alt åpent». Flask sorterte
JSON-nøkler alfabetisk, så sonene kom i feil rekkefølge i /api/status –
app.json.sort_keys = False.
25 nye tester, 173 totalt. Verifisert i compose på host-nett: to soner
meldte seg innen 4 s uten noe [zones], fordeling til en av dem havnet i kø,
avahi-browse på verten ser sentralen, en stoppet sone ble tatt ut etter 90 s
og var tilbake 2 s etter start. Underveis fant live-testen at testkonfigen
fortsatt hadde statiske a/b – og sentralen avviste hjerteslagene med 409,
som den skal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Flere anlegg – hovedscene, resepsjon, crew – og ett sted å sende fra, med
avkryssing for hvilke soner meldingen skal til.
Sentralen er en vanlig announcer med [zones] i config. En sone er en vanlig
announcer med ANNOUNCER_MESH_SECRET. Samme image, ingen mode, og en sone kan
brukes alene hvis sentralen faller ut. Hver avhengighet med C-kode har
aarch64-wheel i uv.lock, så det samme bildet går på en Raspberry Pi uten å
kompilere noe.
Sentralen syntetiserer én gang og sender ferdig WAV (POST /api/play,
multipart). Sonen trenger da verken nett, edge-tts eller Pipers 670 MB for
det den får – Item.wav er hele endringen i avspillingen. Køene forblir per
sone: dispatcheren har egen tråd og egen kø, leverer til hver sone i hver sin
tråd med timeout, og går videre. En opptatt hovedscene holder ikke igjen
resepsjonen, og en sone som er nede feiler alene – synlig per sone i UI og
revisjonslogg.
Auth gjenbrukes: ny leverandør mesh på header X-Mesh-Key (ikke Bearer – den
leses av JWT-leverandøren, som avviser alt som ikke er et JWT før neste får
prøve), med én handling, play. To regler som fulgte med:
- play følger aldri med «alt åpent». Uten det kunne hvem som helst POSTe en
WAV til en åpen sone og få den spilt. Testen for åpen modus fanget det.
- En legitimasjon som vises fram og er feil gir 401 også der gjester ellers
har lov. Før slapp en feil PIN i åpen modus stille gjennom som gjest.
mesh teller ikke som innlogging for folk: en sone med bare nøkkelen har
fortsatt åpen side lokalt.
Planlagte sendinger husker sonene sine og fordeles gjennom dispatcheren;
planen ender som sent hvis minst én sone tok imot, med «hovedscene: kø #3,
crew: nede» som note.
Hodeløs Pi: install.sh kjører loginctl enable-linger, ellers finnes ikke
lydsocketen når ingen logger inn. docs/mesh.md tar også raspi-config for
jack framfor HDMI.
38 nye tester, 148 totalt. Fordelingen testes uten nett med stubbet
transport; selve HTTP-en testes mot en levende werkzeug-server, så den
håndrullede multiparten møter Flask sin parser. Verifisert i compose med
sentral og to soner: til alle, bare til én, og én sone stoppet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sju ting fra en gjennomgang av det som ble bygd i går.
Stopp virket ikke mens lyden ble laget. Player.stop() drepte self.proc, men
under syntesen finnes ingen prosess ennå – det er de første sekundene av hver
edge-tts-melding – så knappen gjorde ingenting, og meldingen gikk ut etterpå.
Nå har køen statusen preparing, stop() setter stopped på den, og run_once()
sjekker det før den starter avspilling. UI-et viser «Lager lyd …» i stedet
for å late som det spilles i stillhet.
Planlagte sendinger løy om status. De ble markert «sendt» idet de ble lagt i
kø, og ble stående slik selv om avspillingen feilet. Player har fått en
on_finished-callback, og planen følger nå meldingen helt i mål: sent, failed
eller stopped – det som faktisk skjedde. Statusene i schedule.py er utvidet
tilsvarende (queued er «lagt i kø», sent er «spilt ferdig»).
Flask sin utviklingsserver er byttet ut med waitress. Én prosess med mange
tråder, med vilje: Player og planleggeren er singletons, så gunicorn med
flere workere ville gitt flere køer som snakker i munnen på hverandre.
HTTP-laget hadde null tester. import announcer startet tråder og dro inn
Piper, så rutene lot seg ikke prøve. Trådene startes nå i main(), og
run_once() driver køen ett steg om gangen. tests/test_routes.py dekker
rutene, valideringen, køen, Stopp i begge faser, revisjonsloggen og hele
planleggingsflyten gjennom HTTP. 30 nye tester, 110 totalt.
Kappløp i synthesize(): tmp-fila hadde fast navn, så to som forhåndslyttet
samme tekst samtidig skrev til samme fil, og den ene rename feilet. Unik
tmp-fil per kall.
verify_signature = false er fjernet. Det var en felle: én feilskrevet linje
i config, og alle tokens ble godtatt. Ingen trenger den.
docker compose up uten install.sh krasjet kryptisk ved første utrop, fordi
Docker lagde data/ som root. check_writable() sier nå klart fra ved oppstart,
med hva som må gjøres.
Verifisert live i container: Stopp under edge-tts-syntese gir stopped, ikke
done. Server-headeren er waitress sin, ikke werkzeugs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>