announcer/config.example.toml
Kent 7c521f8d1f Mesh: sonene melder seg selv; mDNS på LAN, NetBird-provisjonering
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>
2026-10-09 02:50:25 +02:00

167 lines
6.3 KiB
TOML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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"]