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>
167 lines
6.3 KiB
TOML
167 lines
6.3 KiB
TOML
# Kopier til config.toml og endre det du vil ha annerledes. Alt er valgfritt:
|
||
# uten fila kjører appen med standardverdiene nedenfor, uten innlogging.
|
||
#
|
||
# cp config.example.toml config.toml
|
||
#
|
||
# Hver nøkkel kan overstyres av en miljøvariabel, se tabellen i AGENTS.md.
|
||
# Hemmeligheter bør settes slik istedenfor å stå her, f.eks. i systemd-enheten:
|
||
# Environment=ANNOUNCER_JWT_SECRET=...
|
||
# config.toml er i .gitignore.
|
||
|
||
[app]
|
||
title = "Announcer"
|
||
subtitle = "Skriv meldingen, forhåndslytt, og send den ut på anlegget."
|
||
accent = "#4fd1c5"
|
||
idle_text = "Stille · ingenting i kø"
|
||
message_placeholder = "Skriv meldingen som skal leses opp …"
|
||
max_chars = 600
|
||
default_voice = "finn"
|
||
default_chime = "dingdong"
|
||
|
||
# Knapper over tekstfeltet. Tom liste = ingen knapper.
|
||
quick_phrases = [
|
||
"Pizzaen er kommet. Hent i kantina!",
|
||
"Turneringen starter om ti minutter.",
|
||
"Husk å rydde rundt plassen din. Takk!",
|
||
]
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Mesh: én sentral som fordeler til flere soner
|
||
# --------------------------------------------------------------------------
|
||
# Settes BARE på sentralen. Navn -> "local" (eget anlegg) eller URL til en
|
||
# sone. Sonene er vanlige announcere med ANNOUNCER_MESH_SECRET satt i .env –
|
||
# samme hemmelighet som sentralen. En sone kan brukes alene også.
|
||
# Sentralen syntetiserer én gang og sender ferdig WAV; sonen trenger
|
||
# verken nett eller stemmer for det den får derfra.
|
||
#
|
||
# [zones]
|
||
# her = "local"
|
||
# hovedscene = "http://10.20.70.11:8090"
|
||
# resepsjon = "http://10.20.70.12:8090"
|
||
# crew = "http://10.20.70.13:8090"
|
||
|
||
[mesh]
|
||
# secret settes som ANNOUNCER_MESH_SECRET i .env, ikke her.
|
||
# Sonene trenger ikke stå i [zones]: de melder seg hos sentralen selv med
|
||
# hjerteslag, og tas ut når de slutter. [zones] er for faste adresser.
|
||
role = "auto" # auto | sentral | sone
|
||
name = "" # denne maskinas sonenavn. Tom = hostname
|
||
sentral = "" # for en sone: sentralens URL. Tom = mDNS
|
||
local = true # sentralen har eget anlegg
|
||
mdns = true
|
||
heartbeat_seconds = 30
|
||
stale_seconds = 90 # uten hjerteslag så lenge = sonen er borte
|
||
timeout_seconds = 5
|
||
poll_seconds = 2
|
||
|
||
[server]
|
||
host = "0.0.0.0"
|
||
port = 8090
|
||
# Slå på bare når appen står bak HTTPS. Setter Secure på sesjonscookien; på
|
||
# rå HTTP ville ingen klart å logge inn med den på.
|
||
behind_tls = false
|
||
# Trengs bare bak en omvendt proxy, for at OIDC-redirect skal peke riktig.
|
||
# public_url = "https://announcer.example.no"
|
||
# Tom = nøkkel genereres én gang og lagres i instance/secret_key.
|
||
# secret_key = ""
|
||
|
||
[paths]
|
||
# Kommenter inn hvis noe skal ligge et annet sted enn ved siden av koden.
|
||
# voices = "voices"
|
||
# cache = "cache"
|
||
# chimes = "chimes"
|
||
# log = "logs/announcements.jsonl"
|
||
# state = "instance"
|
||
|
||
[schedule]
|
||
# Slingringsmonn etter planlagt tid. Tråden våkner med noen sekunders
|
||
# mellomrom, og en kjapp omstart skal ikke miste en sending. Mer enn dette
|
||
# for sent regnes som gått glipp av, og sendes *ikke* – et utrop om noe som
|
||
# skjedde for en time siden er feil å spille.
|
||
grace_seconds = 120
|
||
# Fanger opp tastefeil i årstall.
|
||
max_days_ahead = 30
|
||
tick_seconds = 20
|
||
|
||
[audio]
|
||
# pw-play --target / paplay --device. Tom = systemets standardutgang.
|
||
# Finn navnet med: pactl list short sinks
|
||
sink = ""
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Innlogging
|
||
# --------------------------------------------------------------------------
|
||
# Tom liste her og ingen av blokkene under utfylt = alt er åpent. Det er
|
||
# standarden, og helt greit på et lukket nett.
|
||
#
|
||
# En metode er aktiv når den står i providers *eller* når den er ferdig
|
||
# konfigurert nedenfor. Derfor holder det å sette en PIN for å slå på PIN.
|
||
# Rekkefølgen her bestemmer hvilken som prøves først.
|
||
|
||
[auth]
|
||
providers = []
|
||
# providers = ["oidc", "jwt", "pin"]
|
||
|
||
# --- PIN: én delt kode. Virker uten nett, så den er verdt å ha i reserve. ---
|
||
[auth.pin]
|
||
pin = ""
|
||
role = "admin"
|
||
label = "PIN"
|
||
hint = "Spør vaktleder"
|
||
|
||
# --- JWT: crew-panelet signerer et token. Se docs/crew-integration.md. -----
|
||
[auth.jwt]
|
||
issuer = "https://crew.example.no"
|
||
audience = "announcer"
|
||
# Ett av disse to. Delt hemmelighet er enklest; JWKS er bedre fordi
|
||
# announcer da aldri ser en nøkkel som kan lage tokens.
|
||
# secret = "sett-via-ANNOUNCER_JWT_SECRET"
|
||
# jwks_url = "https://crew.example.no/.well-known/jwks.json"
|
||
# Tom = HS256 med secret, RS256/ES256 med jwks_url.
|
||
algorithms = []
|
||
# Hvor rollene står i tokenet. Punktum for nestet, f.eks. "realm_access.roles".
|
||
role_claim = "roles"
|
||
# Hvilken rolle alle med gyldig token får hvis tokenet ikke sier noe.
|
||
default_role = ""
|
||
# Hvor lenge sesjonen fra /auth/token varer, hvis tokenet ikke har kortere exp.
|
||
session_seconds = 43200
|
||
# Slingringsmonn på exp/nbf i sekunder, for klokker som ikke står helt likt.
|
||
# En maskin på et arrangement har ikke alltid NTP i orden.
|
||
leeway = 30
|
||
|
||
# --- OIDC: vanlig innlogging i nettleseren, autorisasjonskode med PKCE. ----
|
||
[auth.oidc]
|
||
issuer = "https://crew.example.no"
|
||
client_id = ""
|
||
# client_secret = "sett-via-ANNOUNCER_OIDC_CLIENT_SECRET"
|
||
scopes = ["openid", "profile", "email"]
|
||
role_claim = "roles"
|
||
default_role = ""
|
||
label = "Logg inn med crew-kontoen"
|
||
# Bare hvis panelet mangler .well-known/openid-configuration:
|
||
# authorization_endpoint = "https://crew.example.no/oauth/authorize"
|
||
# token_endpoint = "https://crew.example.no/oauth/token"
|
||
# jwks_url = "https://crew.example.no/.well-known/jwks.json"
|
||
# end_session_endpoint = "https://crew.example.no/logout"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Roller
|
||
# --------------------------------------------------------------------------
|
||
# Rollenavnene kommer fra tokenet (role_claim). Handlinger:
|
||
# view se køen og hva som spilles nå
|
||
# preview forhåndslytte (synteser lyd)
|
||
# announce sende ut på anlegget
|
||
# schedule planlegge en sending fram i tid, og avbryte sine egne
|
||
# play ta imot ferdig lyd fra sentralen (mesh). Bare rollen «mesh».
|
||
# register melde seg som sone hos sentralen (mesh). Bare rollen «mesh».
|
||
# stop stoppe det som spilles
|
||
# clear tømme køen
|
||
# "*" betyr alt.
|
||
|
||
[roles]
|
||
# Alle som ikke er innlogget. ["view"] er nyttig for en statusskjerm på veggen.
|
||
anonymous = []
|
||
crew = ["view", "preview", "announce", "schedule"]
|
||
admin = ["*"]
|
||
# Sentralen, når den snakker med denne maskina som sone. Ikke endre.
|
||
mesh = ["play", "view", "register"]
|