«Send denne kl. 18:00.» Engangs, ikke gjentakende. Dette er den første delen av appen som må ligge på disk. Kø og historikk har alltid bare levd i minnet, og det har gått bra fordi en melding spilles med en gang. En melding planlagt til 18:00 må overleve at containeren startes om 17:45, så schedule.py lagrer i data/scheduled.json – skrevet til side og flyttet på plass, så fila aldri er halvskrevet. Ingen database, med vilje: dette er en håndfull meldinger på et arrangement, og en fil man kan lese med cat er verdt mer her enn indekser. En ødelagt fil hindrer ikke oppstart; da er det bedre å miste planene og si det i loggen. Modulen vet ingenting om Flask eller avspilling. Den lagrer, og den svarer på «hva skal sendes nå?». _scheduler() i announcer.py eier tråden som spør hvert 20. sekund og legger forfalte meldinger i den vanlige køen – derfra oppfører de seg nøyaktig som en melding sendt for hånd. Gikk den glipp av, spilles den ikke. due() skiller på grace_seconds (120): innenfor den sendes meldingen, utenfor markeres den missed og vises som det. Nådefristen er slingringsmonn for at tråden våkner med mellomrom og for en kjapp omstart, ikke en policy om å ta igjen etterslep – «turneringen starter om ti minutter» skal ikke ut på anlegget en time etter at maskina kom tilbake. due() endrer ingenting selv; markeringen skjer først etter at meldingen faktisk er i kø, så ingenting kan stå som «sendt» uten å ha blitt det. Klienten sender absolutt tidspunkt (epoch), så tidssoner spiller ingen rolle for selve planleggingen. TZ settes likevel fra .env, ellers står loggen i UTC mens man selv står i CEST. Planlegging er låst til samme forhåndslytt-nøkkel som «Send ut»: man planlegger bare det man har hørt. Det gir en bonus – forhåndslytt varmer cache/, så WAV-en ligger ferdig på disk, og en planlagt edge-tts-melding spiller kl. 18:00 selv om nettet er nede da. Sine egne kan man alltid avbryte. Andres krever clear, samme rett som å tømme køen, siden det er samme slags inngrep i noe andre har satt i gang. 23 nye tester. Verifisert i container: sending fyrer og spiller, overlever en omstart av containeren, og en melding som forfalt mens maskina var av blir markert missed uten å bli spilt. Merk ved oppgradering: schedule er en ny handling, og et oppsett med eksplisitt [roles] i config.toml får den ikke automatisk. Den må legges inn selv. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
134 lines
4.8 KiB
TOML
134 lines
4.8 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!",
|
||
]
|
||
|
||
[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
|
||
# 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 = ["*"]
|