Commit graph

11 commits

Author SHA1 Message Date
eb27a162d5 Gjennomgang: Stopp under syntese, planstatus, waitress, tester på HTTP-laget
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>
2026-10-09 01:41:31 +02:00
04b3712ef5 Planlagte sendinger
«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>
2026-10-08 16:29:16 +02:00
6c1253f54c Docker er driftsformen, ikke bare en testrigg
Announcer skal kjøre standalone på klienten som er koblet til anlegget, i
Docker – aldri på utviklingsmaskina. install.sh installerte nativt med apt
og uv, og det passer ikke lenger.

- install.sh er nå en Docker-bootstrap: installerer Docker om den mangler,
  lager .env med riktig bruker-ID og portbinding, oppretter data/, bygger,
  starter, og setter ufw. Den rydder også bort gamle native enheter, både
  announcer.service og sg-announcer.service.
- Dockerfile kopierer koden inn istedenfor å låne den fra et bind-mount, så
  det som kjører i drift er uavhengig av hva som ligger i mappa.
- Alt som skal overleve en ny container ligger i data/ på verten. Bind-mount
  og ikke navngitt volum, med vilje: da kan man lese
  data/logs/announcements.jsonl og slippe egne jingler i data/chimes/ uten å
  gå inn i containeren, og eierskapet følger brukeren istedenfor root.
- Testriggen er flyttet til compose.test.yaml, med eget prosjektnavn, så den
  ikke kan forveksles med eller forstyrre drift.

Lyden ut av containeren krever tre ting, og de er dokumentert i AGENTS.md:
hele XDG_RUNTIME_DIR montert inn (ikke bare pipewire-socketen – PulseAudio
trenger pulse/native og en cookie i samme mappe, og da virker begge
lydservere), user: PUID:PGID lik eieren av socketen, og en innlogget grafisk
økt. Containeren starter ved boot, altså før innlogging, så avspilling feiler
til noen logger inn – det retter seg selv, siden player_cmd() slås opp per
melding. Verifisert: lyden spiller fra driftscontaineren.

Brannmuren er den viktige endringen fra nativ tid. Dockers publiserte porter
settes opp i nat-tabellen, før ufws regler, så en ufw-regel på porten gjør
ingenting. Tilgangen styres nå av BIND_IP i .env, og install.sh setter den til
NetBird-adressen når wt0 finnes. ufw settes fortsatt for ssh, men er ikke
involvert i tilgangen til announcer.

Testriggen bindes nå til 127.0.0.1. Den sto med PIN 1234 publisert på
0.0.0.0, og gikk altså forbi ufw ut på LAN-et.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-08 16:07:59 +02:00
6aaa98d8b1 Testoppsett i Docker med dummy-IdP
Crew-panelet har ikke OIDC ennå, så OIDC-veien kunne ikke prøves i det hele
tatt – bare enhetstestes mot et stubbet HTTP-lag. Dette gir noe å faktisk
logge inn i.

    docker compose up --build

    localhost:8091   announcer med PIN, JWT og OIDC slått på samtidig
    localhost:9000   docker/dummy_idp.py, som står for crew-panelet

8091 med vilje, så den ikke kolliderer med tjenesten på 8090.

Dummy-IdP-en kjører en ekte autorisasjonskode-runde: den krever PKCE med
S256, avviser feil code_verifier, bruker koden bare én gang, og signerer
id_token med klienthemmeligheten. Du velger hvem du logger inn som – Kari
(crew), Kent (admin) eller Ola, som har null roller og dermed skal gi 403
og ikke 401. /token?who=kari&go=1 logger deg inn via token-lenka, slik
panelet skal gjøre.

To ting i oppsettet som ser rart ut, men ikke er det:

- authorization_endpoint peker på localhost:9000 fordi nettleseren skal
  dit, mens token_endpoint peker på idp:9000 fordi announcer selv snakker
  med den inne i compose-nettet. Delt horisont. Det tester samtidig at
  manuelle endepunkter virker, som er veien når et panel mangler discovery.
- UV_PROJECT_ENVIRONMENT=/opt/venv i bildet: /app er et bind-mount av
  repoet, og uten den ville containerens uv sync overskrevet .venv som den
  kjørende tjenesten bruker.

/run/user/1000 er montert inn, så pw-play i containeren spiller på vertens
anlegg. Det er verifisert; kommenter ut linja hvis du bare skal teste UI.

Docker er bare for testing. På en ekte maskin kjører appen nativt med
install.sh, fordi lyden skal ut på maskinens egen lydutgang.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-08 15:44:19 +02:00
d6eb811ee5 Pluggbar autentisering: PIN, JWT og OIDC
Dagens «auth» var én delt PIN i et X-Pin-header, sammenlignet med !=, og
den sa ingenting om hvem som sendte et utrop – køen viste bare IP-adressen.
Crew har et eget adminpanel som skal bli identitetsleverandør.

Modellen er én datatype og én funksjon: Principal (hvem + roller +
rettigheter) og require(action), som enten gir deg den eller stopper
forespørselen. Handlinger: view, preview, announce, stop, clear.

Tre leverandører, prøvd i rekkefølge, alle valgfrie:

- pin   X-Pin som før, men med hmac.compare_digest istedenfor ==. Rolle
        admin som standard, så eksisterende oppsett oppfører seg likt.
        Beholdt bevisst: den virker uten nett, og det er nettopp da man
        trenger å rope ut noe.
- jwt   Authorization: Bearer. HS256 med delt hemmelighet, eller
        RS256/ES256 mot panelets JWKS. Krever exp – et token uten
        utløpstid er en evig nøkkel. Roller fra et konfigurerbart claim,
        som tåler liste, streng, kommaliste og punktsti.
- oidc  Autorisasjonskode med PKCE. Discovery hentes først ved første
        innlogging, så tjenesten starter selv om panelet er nede.

En leverandør er aktiv når den står i auth.providers *eller* når den er
ferdig konfigurert. Derfor slår ANNOUNCER_PIN=1234 alene fortsatt på PIN,
slik det har virket til nå. Er ingen aktiv, er alt åpent – som før.

/auth/token?t=<jwt> er broa mens panelet mangler OIDC: panelet lenker hit
med et kortlevd token, vi setter sesjonscookie. Ett klikk, ingen PIN.
Kontrakten panelet skal oppfylle står i docs/crew-integration.md.

401 betyr «vi vet ikke hvem du er, logg inn», 403 «vi vet hvem du er, men
rollen din får ikke dette». Et gyldig token uten roller gir 403, ikke 401 –
å logge inn på nytt ville ikke hjulpet.

Samtidig:

- /api/status er lukket bak «view». Den var åpen, og lekket både
  meldingstekstene og IP-ene til alle som hadde sendt noe, til hvem som
  helst som nådde porten. IP vises nå bare for den som har «clear»; alle
  andre ser navnet.
- Køen og en revisjonslogg i logs/announcements.jsonl viser hvem som sendte
  hva. Loggen feiler aldri forespørselen – en logg som ikke lar seg skrive
  skal ikke hindre et utrop.
- Branding flyttet fra index.html til /api/config, så config.toml fra
  forrige commit faktisk brukes.
- Sesjonsnøkkel i instance/secret_key, ellers logges alle ut ved hver
  restart. SESSION_COOKIE_SECURE settes bare bak TLS: appen kjører på rå
  HTTP over LAN og NetBird, og med Secure på ville ingen klart å logge inn.

57 tester, de fleste på auth, inkludert hele OIDC-runden mot en stubbet
leverandør. Det er delen man ikke kan se etter med øyet: at en PIN virker
ser du med en gang, at et utløpt token blir avvist ser du aldri før det
smeller.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-08 15:43:35 +02:00
775ab6d075 install.sh: ufw låst til NetBird (wt0) når det finnes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 19:20:47 +02:00
ad1715838e install.sh for ny maskin + paplay-reserve når PipeWire mangler
Skriptet installerer apt-pakker, uv, Python 3.13, Piper-stemmene og
systemd-brukerenheten, og åpner porten i ufw hvis den er aktiv. Avspilling
velger pw-play når PipeWire kjører, ellers paplay (Linux Mint 21).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 18:55:10 +02:00
756a234b13 Valgbare jingler: seks innebygde + egne filer i chimes/
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 03:50:48 +02:00
45a22e7ed6 edge-tts som standardmotor (Finn/Pernille), Piper som lokal reserve
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 03:42:16 +02:00
ff43624969 Flere stemmer: nvcc (10 norske talere), flere engelske, svensk og dansk
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 03:37:46 +02:00
1c3e17afa0 SG Announcer: Piper-TTS-utroper med forhåndslytt og kø
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 03:18:13 +02:00