Lokalt utviklingsmiljø, Whisper-benchmark, AI Gateway og repostruktur

- Sett opp docker-compose.dev.yml med PostgreSQL, Redis, Caddy og Whisper
- Benchmarket faster-whisper (small/medium/large-v3) med norsk tale
- Besluttet medium + initial_prompt som standard, SRT som master-format
- Ny feature-spec: AI Gateway (LiteLLM) med BYOK og Promptfoo-testing
- Definert dataklassifisering (kritisk/gjenskapbar/avledet/flyktig)
- Konkretisert backup-strategi med pg_dump, rsync og restore-prosedyre
- Splittet repos: sidelinja/server (kode) + sidelinja/sidelinja (innhold)
- Oppdatert lokal.md: utviklingsmiljø for kode, ikke prod-replika
- Dokumentert transkripsjonspipeline: Whisper SRT → Git → PG (avledet)
- Live AI-assistent: small-modell, flyktig logg med 30d TTL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-13 14:51:15 +01:00
parent 53f0ccf49e
commit 4b56560bf9
11 changed files with 594 additions and 198 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
# Docker-volumer (flyktige, ikke i Git)
.docker-data/
# Miljovariabler
.env.local
.env
# Node
node_modules/
# Rust
target/
# OS
.DS_Store
Thumbs.db

View file

@ -14,29 +14,83 @@ Sidelinja er ikke bare en podcast-host; det er et **redaksjonelt operativsystem*
- `auth.sidelinja.org` — Authentik SSO (felles for alle domener)
- `git.sidelinja.org` — Forgejo
- `vegard.info` — Separat nettsted, deler SSO med Sidelinja
* **Kildekode og CI/CD:** **Forgejo** (Selv-hostet Git). All kode og konfigurasjon lever her.
* **Utvikling og Utrulling (Claude Code Workflow):** All prototyping og koding skjer lokalt i **WSL2 (Ubuntu)** på en lokal Windows 11-maskin. Når koden er testet og klar, pusher AI-agenten (Claude Code) til Forgejo. Deretter logger agenten seg på produksjonsserveren via SSH for å hente koden, trigge kompilering og starte tjenestene på nytt. Regelen "ikke programmere i produksjon" betyr utelukkende at redigering av kildekode og "prøving og feiling" hører hjemme lokalt, ikke live på serveren.
* **Kildekode og CI/CD:** **Forgejo** (Selv-hostet Git). To repos:
- `sidelinja/server` — app-kode, infra, arkitektur, config
- `sidelinja/sidelinja` — podcastinnhold (transkripsjoner, show notes, research)
* **Utvikling og Utrulling:** Kode skrives og testes lokalt i WSL2. Infrastruktur-config (docker-compose, Caddy, Authentik) endres direkte i prod. Deploy: push til Forgejo → SSH pull på server → `docker compose up -d --build`.
### 2.1 Serverstruktur og Backup-strategi (Produksjon)
### 2.1 Serverstruktur (Produksjon)
All persistent data, konfigurasjon og kildekode monteres via Docker Bind Mounts til en fast struktur på vertssystemet, typisk `/srv/sidelinja/`. Dette muliggjør granulert backup.
/srv/sidelinja/
├── docker-compose.yml # Orkestrering
├── .env # Miljøvariabler (IKKE i Git)
├── config/ # Konfigurasjonsfiler (Caddy, Authentik, etc.)
├── data/ # Databaser (Postgres, SpacetimeDB, Forgejo) -> KRITISK BACKUP
├── media/ # Lydfiler (podcast, råopptak) -> KRITISK BACKUP
└── logs/ # Caddy access-logger, app-logger -> SEPARAT/LANGTIDS BACKUP
├── data/ # Databaser (Postgres, SpacetimeDB, Forgejo)
├── media/ # Lydfiler (podcast, råopptak)
└── logs/ # Caddy access-logger, app-logger
*Målrettet backup:* Mappen `logs/` ekskluderes fra den daglige snapshot-backupen for å spare plass, men rulleres og arkiveres separat for fremtidig dataanalyse.
### 2.2 Dataklassifisering og backup-strategi
Alle data i Sidelinja faller i én av fire kategorier. Nye komponenter og features MÅ klassifisere sin data etter dette skjemaet.
### 2.2 Lokalt Utviklingsmiljø (Dev Replika)
For å sikre smidig lokal utvikling i WSL2, bygger vi en nøyaktig replika av produksjonsmiljøet, men optimalisert for utviklingshastighet (Hot Reloading). **Komplett steg-for-steg oppsett finnes i `docs/setup/lokal.md`, produksjonsoppsett i `docs/setup/produksjon.md`.**
#### Kategori 1: Kritisk — krever backup
Data som ikke kan gjenskapes. Tap = permanent informasjonstap.
* **Docker Compose Dev:** Vi bruker en egen `docker-compose.dev.yml` som spinner opp lokale instanser av databasene (PostgreSQL, SpacetimeDB) og LiveKit. Volumene for disse er lokale og flyktige/seedede.
* **SvelteKit HMR:** SvelteKit-klienten kjøres *utenfor* Docker under aktiv utvikling (ved bruk av `npm run dev` i WSL2). Dette sikrer at Hot Module Replacement (HMR) fungerer lynraskt når kode endres.
* **Lokal Ruting:** En lokal Caddy-instans ruter trafikk fra `localhost` til SvelteKit, SpacetimeDB og LiveKit, med self-signed sertifikater (`local_certs`) for sikker kontekst (WebRTC).
* **Forgejo:** Kjører *ikke* lokalt. Push direkte til produksjons-Forgejo fra WSL2.
| Data | Lagring | Backup |
|---|---|---|
| PostgreSQL (kunnskapsgraf, brukere, metadata, episoder) | `data/postgres/` | Daglig pg_dump + fil-backup |
| Lydfiler (MP3, råopptak) | `media/` | Daglig fil-backup |
| `.env` (hemmeligheter) | `/srv/sidelinja/.env` | Manuell kopi, ikke i Git |
#### Kategori 2: Gjenskapbar fra Git
Data som lever i Forgejo og kan klones/pulles på nytt.
| Data | Lagring | Restore |
|---|---|---|
| Kildekode | Git (Forgejo) | `git clone` |
| Transkripsjoner (SRT master) | Git (Forgejo) | `git clone` |
| PG-skjema + migrasjoner | Git (Forgejo) | Kjør migrasjoner |
| Config-filer (Caddyfile, etc.) | Git (Forgejo) | `git clone` |
| Forgejo-data (repos, issues) | `data/forgejo/` | **Daglig backup som sikkerhetsnett** — kan gjenskapes men tidkrevende |
#### Kategori 3: Avledet — kan regenereres
Data som er deterministisk avledet fra kategori 1 eller 2. Tåler tap — regenereres automatisk.
| Data | Kilde | Regenerering |
|---|---|---|
| Ren tekst (transkripsjoner) | SRT i Git | Rust-worker parser SRT |
| Segmenter (tidsstemplet, grafkoblet) | SRT i Git | Rust-worker parser SRT |
| Full-text søkeindeks | SRT i Git | Rebuild fra Git |
| SRT → PG-cache | SRT i Git | Reimport via webhook |
| Redis-cache | Applikasjonsdata | Regenereres automatisk |
| Caddy-sertifikater | Let's Encrypt | Regenereres automatisk |
#### Kategori 4: Flyktig — tåler tap, TTL-styrt
Arbeidsdata med begrenset levetid. Ryddes automatisk.
| Data | Lagring | TTL | Formål |
|---|---|---|---|
| Live-transkripsjonslogg | PostgreSQL | 30 dager | Feilsøking av live-assistent |
| Caddy access-logger | `logs/caddy/` | 90 dager | Podcast-statistikk (batch-prosesseres først) |
| Jobbkø-historikk (fullførte jobber) | PostgreSQL | 30 dager | Feilsøking |
| Whisper-modeller | `.docker-data/` (lokal) | Ingen TTL | Re-download fra HuggingFace ved behov |
#### Retningslinjer for nye komponenter
Når en ny feature eller komponent introduserer data:
1. **Klassifiser** — hvilken kategori faller dataen i?
2. **Dokumenter** — legg til i tabellen over
3. **Implementer TTL** for kategori 4 — aldri la flyktig data vokse ubegrenset
4. **Aldri dupliser kilde** — avledede data (kategori 3) skal kunne slettes og regenereres
5. **Aldri backup avledet data** — det er bortkastet plass og skaper falsk trygghet
### 2.3 Lokalt Utviklingsmiljø
Det lokale miljøet (WSL2) er et **kodeutviklingsmiljø**, ikke en replika av prod. Infrastruktur-config (docker-compose, Caddy, Authentik) testes direkte i prod. **Komplett oppsett: `docs/setup/lokal.md`.**
* **Docker Compose Dev:** `docker-compose.dev.yml` spinner opp PostgreSQL, Redis, Caddy og Whisper lokalt. Volumene er flyktige (`.docker-data/`, gitignored).
* **SvelteKit HMR:** Kjøres utenfor Docker for rask iterasjon.
* **Rust Workers:** Kompileres og kjøres lokalt med `cargo run`.
* **AI Gateway / Whisper:** Lokale instanser for eksperimentering og prompt-testing.
* **Forgejo/Authentik:** Kjører IKKE lokalt — push direkte til prod-Forgejo.
## 3. Teknologistack
Vi følger et "Best tool for the job"-prinsipp, med en sterk preferanse for minnesikkerhet, ytelse og rene grensesnitt.
@ -44,7 +98,7 @@ Vi følger et "Best tool for the job"-prinsipp, med en sterk preferanse for minn
* **Backend/Automasjon:** **Rust**. Brukes som bakgrunnsworkers (jobbkø), logg-parsing og SpacetimeDB-moduler. Rust er *ikke* en API-server — SvelteKit server-side håndterer all HTTP-kommunikasjon og PG-tilgang direkte (se `docs/features/api_grensesnitt.md`).
* **Frontend / UI:** **SvelteKit** (med TypeScript). Bygges som en PWA. Valgt for ytelse og enkel integrasjon med WebRTC og vanilla JS-biblioteker.
* **Sanntids Lyd/Video:** **LiveKit** (Selv-hostet). Håndterer WebRTC, fler-bruker videochat og opptak i det virtuelle "studioet".
* **AI / Prosessering:** `faster-whisper` (lokal transkripsjon) og OpenRouter (Claude-modeller for tekstanalyse).
* **AI / Prosessering:** `faster-whisper` (lokal transkripsjon) og **LiteLLM** (AI Gateway — sentralisert ruting til Gemini, Claude, Grok, OpenRouter). All AI-kode peker på `http://ai-gateway:4000/v1`, aldri direkte til leverandører. Se `docs/features/ai_gateway.md`.
* **SSO / Autentisering:** **Authentik** (Selv-hostet). Sentralisert rollestyring.
## 4. Den To-delte Databasestrategien
@ -61,7 +115,13 @@ Systemet er bygget rundt **Temaer** og **Aktører**, ikke episoder. Dette bygger
* **Segment:** En tidsavgrenset del av en episode med egen transkripsjon, koblet til Temaer/Aktører i grafen. Muliggjør presise oppslag ("hva sa vi om X i Episode 42?").
* **Research-klipp:** Råtekst renset av AI, koblet til Temaer/Aktører.
**Transkripsjoner:** Git (Forgejo) er kilde til sannhet (redigerbar, sporbar). PostgreSQL er søkeindeks (full-text, koblet til grafen). Endringer i Git reimporteres automatisk via Forgejo webhook.
**Transkripsjoner — eierskapsmodell:**
* **Git (Forgejo)** er kilde til sannhet. Master-formatet er **SRT** (SubRip) — et etablert undertekstformat med tidsstempler som er redigerbart, diffbart og lett å parse. Whisper leverer SRT direkte (`response_format=srt`).
* **PostgreSQL** lagrer alt avledet fra SRT-kilden:
- **Ren tekst** — strippes fra SRT for lesbart publiseringsdokument
- **Segmenter** — tidsstemplede utdrag koblet til Aktører/Temaer i kunnskapsgrafen
- **Full-text søkeindeks** — for oppslag på tvers av episoder
* **Flyt:** Whisper → SRT → Git commit → Forgejo webhook → Rust-worker parser SRT → avledede formater i PG. SvelteKit serverer SRT fra Git og avledet innhold fra PG.
## 6. Podcast Hosting og Distribusjon
* **Lagring:** MP3-filer lagres flatt i `/srv/sidelinja/media/`. Ingen lydfiler i databaser.
@ -80,26 +140,33 @@ Dette er hovedkonseptene plattformen skal støtte. **Merk: Detaljerte tekniske s
## 8. Bygge-rekkefølge (Avhengighetskart)
```
Lag 1 — Fundament (ingen avhengigheter):
├── PostgreSQL-skjema (nodes, graph_edges, job_queue)
├── SpacetimeDB grunnoppsett
└── SvelteKit skjelett med Authentik-integrasjon
### Lag 0 — Infrastruktur
- [x] Produksjonsserver: PostgreSQL, Caddy, Authentik, Forgejo, Redis
- [x] Lokalt utviklingsmiljo: docker-compose.dev.yml (PostgreSQL, Redis, Caddy)
- [x] Rust toolchain (lokal + server)
- [x] faster-whisper-server (lokal, testet med medium + prompt)
Lag 2 — Kjernekomponenter (krever Lag 1):
├── Jobbkø-worker (Rust)
├── Kunnskapsgraf CRUD (SvelteKit server-side)
└── Produktivitetssuiten: Chat + Kanban (SpacetimeDB ↔ PG synk)
### Lag 1 — Fundament (ingen avhengigheter)
- [ ] PostgreSQL-skjema (nodes, graph_edges, job_queue)
- [ ] SpacetimeDB grunnoppsett
- [ ] SvelteKit skjelett med Authentik-integrasjon
- [ ] AI Gateway (LiteLLM) oppsett + config
- [ ] Git-repostruktur for transkripsjoner (SRT-filer)
Lag 3 — Features (krever Lag 2):
├── AI Research-Klipper (kunnskapsgraf + jobbkø)
├── Podcastfabrikken (jobbkø + episoder/segmenter)
└── Podcast-Statistikk (jobbkø + episoder)
### Lag 2 — Kjernekomponenter (krever Lag 1)
- [ ] Jobbko-worker (Rust)
- [ ] Kunnskapsgraf CRUD (SvelteKit server-side)
- [ ] Produktivitetssuiten: Chat + Kanban (SpacetimeDB <-> PG synk)
- [ ] Promptfoo testsett for forste jobbtyper (norsk testdata)
Lag 4 — Avansert (krever Lag 3):
├── Live AI-Assistent (fylt kunnskapsgraf + LiveKit + Whisper)
└── Valgomat (selvstendig, lav prioritet)
```
### Lag 3 — Features (krever Lag 2)
- [ ] Podcastfabrikken (Whisper SRT -> Git -> PG-avledede formater + episodeside)
- [ ] AI Research-Klipper (kunnskapsgraf + jobbko + AI Gateway)
- [ ] Podcast-Statistikk (jobbko + episoder)
### Lag 4 — Avansert (krever Lag 3)
- [ ] Live AI-Assistent (fylt kunnskapsgraf + LiveKit + Whisper small)
- [ ] Valgomat (selvstendig, lav prioritet)
## 9. Observabilitet

View file

@ -19,26 +19,30 @@ Self-hosted på Hetzner VPS med full datakontroll.
- `jobbkø.md` — Felles PostgreSQL-basert køsystem for alle bakgrunnsjobber
- `synkronisering.md` — PostgreSQL ↔ SpacetimeDB dataflyt og eierskapsmodell
- `api_grensesnitt.md` — Kommunikasjonskart: SvelteKit er web-API, Rust er worker
- `ai_gateway.md` — LiteLLM som sentralisert AI-ruter (BYOK + OpenRouter fallback)
## Stack
- **Backend/Automasjon:** Rust
- **Frontend:** SvelteKit (TypeScript, PWA)
- **Sanntid:** SpacetimeDB (arbeidsflyt/state) + LiveKit (lyd/video)
- **Database:** PostgreSQL (persistent/kunnskapsgraf) + SpacetimeDB (in-memory/sanntid)
- **AI:** faster-whisper (transkripsjon), OpenRouter (Claude-modeller)
- **AI:** faster-whisper (transkripsjon), LiteLLM (AI Gateway → Gemini/Claude/Grok/OpenRouter)
- **Infra:** Docker Compose, Caddy, Authentik (SSO), Forgejo (Git)
## Produksjonsserver
- **IP:** 157.180.81.26
- **SSH:** `ssh sidelinja@157.180.81.26` (nøkkelbasert, sudo uten passord)
- **Filer:** `/srv/sidelinja/` (docker-compose.yml, .env, config/, data/, media/, logs/)
- **Git remote:** `forgejo``ssh://git@git.sidelinja.org:222/sidelinja/sidelinja.git`
- **Git repos:**
- `server` — app-kode, infra, arkitektur: `ssh://git@git.sidelinja.org:222/sidelinja/server.git`
- `sidelinja` — podcastinnhold (transkripsjoner, show notes, research): `ssh://git@git.sidelinja.org:222/sidelinja/sidelinja.git`
- **Domener:** sidelinja.org, auth.sidelinja.org (Authentik), git.sidelinja.org (Forgejo), vegard.info
- **Status:** Lag A komplett (PostgreSQL, Caddy, Authentik, Forgejo, Redis). Lag B-C gjenstår.
## Viktige regler
- Aldri eksponere databaseporter mot internett (kun port 80/443 via Caddy)
- Bruk `tea` CLI, ikke `gh` (vi bruker Forgejo, ikke GitHub)
- Tunge AI-jobber (Whisper, OpenRouter) skal aldri blokkere web-requests
- Tunge AI-jobber (Whisper, LLM-kall) skal aldri blokkere web-requests
- All AI-kode peker på `http://ai-gateway:4000/v1` — aldri direkte til leverandør-APIer
- Kod og test lokalt i WSL2, deploy via push til Forgejo + SSH pull
- Sjekk alltid `docs/features/<navn>.md` før du implementerer en feature

View file

@ -0,0 +1,30 @@
{
local_certs
}
localhost {
# SvelteKit dev server (kjorer utenfor Docker)
reverse_proxy host.docker.internal:5173
# SpacetimeDB (legges til i Lag B)
# handle_path /spacetime/* {
# reverse_proxy spacetimedb:3000
# }
# LiveKit (legges til i Lag B)
# handle_path /livekit/* {
# reverse_proxy livekit:7880
# }
# Podcast media
handle_path /media/* {
root * /srv/media
file_server
}
# Access log (samme format som prod)
log {
output file /var/log/caddy/access.log
format json
}
}

View file

@ -0,0 +1,9 @@
-- Lokale utviklingsdatabaser (speiler produksjon)
-- Authentik og Forgejo kjorer ikke lokalt, men vi oppretter
-- databasene for a holde skjemaet identisk med prod.
CREATE USER authentik WITH PASSWORD 'localdev';
CREATE DATABASE authentik OWNER authentik;
CREATE USER forgejo WITH PASSWORD 'localdev';
CREATE DATABASE forgejo OWNER forgejo;

72
docker-compose.dev.yml Normal file
View file

@ -0,0 +1,72 @@
networks:
sidelinja-dev:
driver: bridge
services:
# === Lag A: Fundament ===
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./.docker-data/postgres:/var/lib/postgresql/data
- ./config/postgres/init:/docker-entrypoint-initdb.d
ports:
- "127.0.0.1:5432:5432"
networks:
- sidelinja-dev
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: --save 60 1 --loglevel warning
volumes:
- ./.docker-data/redis:/data
ports:
- "127.0.0.1:6379:6379"
networks:
- sidelinja-dev
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 10s
timeout: 5s
retries: 5
# === Whisper: Transkripsjon ===
whisper:
image: fedirz/faster-whisper-server:latest-cpu
restart: unless-stopped
environment:
WHISPER__MODEL: Systran/faster-whisper-small
WHISPER__INFERENCE_DEVICE: cpu
volumes:
- ./.docker-data/whisper-models:/root/.cache/huggingface
- ./.docker-data/whisper-large-v3:/root/.cache/huggingface/hub/models--Systran--faster-whisper-large-v3/snapshots/main
ports:
- "127.0.0.1:8000:8000"
networks:
- sidelinja-dev
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "127.0.0.1:80:80"
- "127.0.0.1:443:443"
volumes:
- ./config/caddy/Caddyfile.dev:/etc/caddy/Caddyfile
- ./.docker-data/caddy:/data
- ./.docker-data/logs/caddy:/var/log/caddy
- ./.docker-data/media:/srv/media:ro
networks:
- sidelinja-dev

170
docs/features/ai_gateway.md Normal file
View file

@ -0,0 +1,170 @@
# Feature Spec: AI Gateway (LiteLLM)
**Filsti:** `docs/features/ai_gateway.md`
## 1. Konsept
Sidelinja bruker en sentralisert AI Gateway (LiteLLM) som eneste kontaktpunkt for alle AI-kall i systemet. All kode — Rust-workers, SvelteKit server-side — snakker med `http://ai-gateway:4000/v1`. Aldri direkte til leverandør-APIer.
Fordeler:
* **BYOK (Bring Your Own Key):** Direkte API-nøkler til Anthropic, Google, xAI — ingen markup
* **OpenRouter som fallback:** Tilgang til alle modeller vi ikke har direkte nøkler til, og sikkerhetsventil ved nedetid
* **Kostnadskontroll:** Rutineoppgaver rutes til gratisnivå (Gemini), dyre modeller kun når det trengs
* **Sentralisert logging:** Token-bruk per funksjon (Podcastfabrikken, Research-Klipper, Live-assistent) på ett sted
* **Redundans:** Automatisk failover mellom leverandører — redaksjonen merker ikke nedetid
## 2. Leverandører og bruksmønster
| Leverandør | Nøkkeltype | Primært bruksområde |
|---|---|---|
| Google Gemini | BYOK (gratisnivå) | Rutineoppgaver: transkripsjonsvasking, research-oppsummering, metadata-uttrekk |
| Anthropic (Claude) | BYOK | Oppgaver som krever høy resonneringsevne: live-assistent faktoid-vurdering, kompleks analyse |
| xAI (Grok) | BYOK | Alternativ for analyse, sanntidssøk (når tilgjengelig) |
| OpenRouter | BYOK | Fallback for alle modeller, sikkerhetsventil ved leverandør-nedetid |
**Merk:** Kvaliteten på norsk tekst varierer mellom modeller. Test alltid med norsk innhold før en modell tildeles en produksjonsoppgave.
## 3. Modellruting
Modellvalg styres av to mekanismer:
### 3.1 Standard ruting (config.yaml)
LiteLLM konfigureres med modellaliaser som mapper til billigste egnede leverandør:
```yaml
model_list:
# Ruting: billigste først, fallback til dyrere
- model_name: "sidelinja/rutine"
litellm_params:
model: "gemini/gemini-2.0-flash"
api_key: "os.environ/GEMINI_API_KEY"
- model_name: "sidelinja/rutine"
litellm_params:
model: "openrouter/google/gemini-2.0-flash-001"
api_key: "os.environ/OPENROUTER_API_KEY"
- model_name: "sidelinja/resonering"
litellm_params:
model: "anthropic/claude-sonnet-4-20250514"
api_key: "os.environ/ANTHROPIC_API_KEY"
- model_name: "sidelinja/resonering"
litellm_params:
model: "openrouter/anthropic/claude-sonnet-4-20250514"
api_key: "os.environ/OPENROUTER_API_KEY"
router_settings:
routing_strategy: "simple-shuffle" # prøv første, fallback til neste
num_retries: 2
timeout: 60
general_settings:
master_key: "os.environ/LITELLM_MASTER_KEY"
```
### 3.2 Jobbkø-styrt modellvalg
Jobbkøen (se `jobbkø.md`) spesifiserer modellalias per jobbtype:
| Jobbtype | Modellalias | Begrunnelse |
|---|---|---|
| `whisper_postprocess` (transkripsjonsvasking) | `sidelinja/rutine` | Høyt volum, lav kompleksitet |
| `openrouter_analyze` (metadata-uttrekk) | `sidelinja/rutine` | Strukturert output, lav kompleksitet |
| `research_clip` (research-oppsummering) | `sidelinja/rutine` | Høyt volum |
| `live_factoid_eval` (live-assistent) | `sidelinja/resonering` | Krever presis vurdering under tidspress |
Modellalias lagres som felt på jobben i PG — kan overstyres manuelt per jobb ved behov.
## 4. Docker-oppsett
```yaml
# docker-compose.dev.yml / docker-compose.yml
ai-gateway:
image: ghcr.io/berriai/litellm:main
restart: unless-stopped
command: --config /etc/litellm/config.yaml
environment:
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
GEMINI_API_KEY: ${GEMINI_API_KEY}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
XAI_API_KEY: ${XAI_API_KEY}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
volumes:
- ./config/litellm/config.yaml:/etc/litellm/config.yaml:ro
ports:
- "127.0.0.1:4000:4000" # kun localhost (dev), ingen port i prod
networks:
- sidelinja-dev # eller sidelinja-net i prod
```
## 5. Prompt-kvalitetssikring (Promptfoo)
Alle LLM-prompts i Sidelinja testes systematisk med [Promptfoo](https://promptfoo.dev) før de brukes i produksjon. Dette er spesielt viktig fordi vi jobber med norsk tekst, der modellkvaliteten varierer kraftig mellom leverandører.
### 5.1 Hva vi tester
Hver jobbtype som bruker LLM har et tilhørende testsett:
| Jobbtype | Testsett | Eksempler på assertions |
|---|---|---|
| `whisper_postprocess` | Norske transkripsjoner med kjente feil | Egennavn korrigert, setningsflyt bevart |
| `openrouter_analyze` | Episoder med kjent metadata | Riktig tittel, kapitler matcher innhold |
| `research_clip` | Nyhetsartikler med kjente aktører/fakta | Aktører identifisert, faktoider korrekte |
| `live_factoid_eval` | Transkripsjons-chunks med kjente entiteter | Riktig entity-match, lav falsk-positiv-rate |
### 5.2 Hva vi sammenligner
Promptfoo kjøres mot alle kandidatmodeller via AI Gateway:
```yaml
# promptfoo-config.yaml
providers:
- id: "openai:chat:sidelinja/rutine"
config:
apiBaseUrl: "http://localhost:4000/v1"
apiKey: "${LITELLM_MASTER_KEY}"
- id: "openai:chat:sidelinja/resonering"
config:
apiBaseUrl: "http://localhost:4000/v1"
apiKey: "${LITELLM_MASTER_KEY}"
```
Dette lar oss svare på:
* Klarer Gemini (gratis) denne oppgaven like bra som Claude (betalt)?
* Fungerer prompten på norsk, eller trenger vi en annen formulering?
* Har en modelloppgradering hos leverandøren degradert kvaliteten?
### 5.3 Når vi kjører tester
* **Ved ny prompt:** Før den tas i bruk i produksjon
* **Ved modellbytte:** Før en leverandør/modell settes som primær for en jobbtype
* **Periodisk:** Månedlig regresjonssjekk — leverandører oppdaterer modeller uten varsel
* **Ved kvalitetsklager:** Når redaksjonen rapporterer dårlig output
### 5.4 Lagring av testsett
Testsett og promptfoo-config versjonskontrolleres i Git under `tests/prompts/`. Testdata er norske eksempler fra faktiske episoder og artikler.
```
tests/prompts/
├── promptfooconfig.yaml
├── whisper_postprocess/
│ ├── prompt.txt
│ └── dataset.json
├── metadata_extract/
│ ├── prompt.txt
│ └── dataset.json
└── research_clip/
├── prompt.txt
└── dataset.json
```
## 6. Dataklassifisering (ref. ARCHITECTURE.md 2.2)
| Data | Kategori | Detaljer |
|---|---|---|
| LiteLLM config.yaml | Gjenskapbar (Git) | Versjonskontrollert |
| API-nøkler | Kritisk (.env) | Aldri i Git |
| Token-bruk-logger | Flyktig (TTL 90 dager) | For kostnadsoversikt, ryddes automatisk |
| Promptfoo testsett | Gjenskapbar (Git) | `tests/prompts/` — versjonskontrollert |
| Promptfoo testresultater | Flyktig (lokal) | Kjøres on-demand, ikke lagret permanent |
## 6. Instruks for Claude Code
* All AI-kode skal peke på `http://ai-gateway:4000/v1` — aldri direkte til leverandør
* Bruk modellaliaser (`sidelinja/rutine`, `sidelinja/resonering`) — aldri hardkod leverandør-spesifikke modellnavn i applikasjonskode
* API-nøkler i `.env`, aldri i config-filer eller kode
* Test alltid med norsk innhold før en ny modell/leverandør tas i bruk for en produksjonsoppgave
* Kjør `promptfoo eval` før du endrer prompts eller bytter modell for en jobbtype
* Nye jobbtyper som bruker LLM skal ha et tilhørende testsett i `tests/prompts/` før de merges

View file

@ -8,11 +8,30 @@ En "virtuell co-host" som lytter på innspillingen i sanntid. Når programledern
Denne funksjonen krever lav forsinkelse og asynkron prosessering.
1. **Lydkilde (SvelteKit + LiveKit):** SvelteKit-appen bruker `livekit-client`. I tillegg til å sende høykvalitetslyd til de andre deltakerne, rutes en komprimert lydstrøm (via WebSockets eller LiveKit sine egne server-side hooks) til en lokal Rust-tjeneste.
2. **Transkripsjon (Rust + Whisper):** Rust-tjenesten mater lyden inn i `faster-whisper` (eller et tilsvarende raskt API) i små chunks. Den spytter ut en kontinuerlig tekststrøm.
3. **Entity Extraction & Oppslag (Rust + PostgreSQL):** Rust-skriptet analyserer tekststrømmen for egennavn (Named Entity Recognition). Den gjør et lynraskt asynkront oppslag i PostgreSQL: `SELECT * FROM factoids JOIN actors... WHERE actor.name = $1`.
4. **Sanntids-Push (SpacetimeDB):** Hvis et treff finnes, dytter Rust-skriptet faktoiden inn i SpacetimeDB som et event: `LiveFactoidEvent`.
2. **Transkripsjon (Rust + Whisper):** Rust-tjenesten mater lyden inn i faster-whisper-server (`Systran/faster-whisper-small`) i chunks på ~5 sekunder. `small` er valgt for latency — den prosesserer ~5x raskere enn sanntid, noe som gir <1s forsinkelse per chunk. Ingen `initial_prompt` hastighet prioriteres over navnenøyaktighet.
3. **Entity Extraction & Oppslag (Rust + PostgreSQL):** Rust-tjenesten analyserer tekststrømmen for egennavn (Named Entity Recognition). Den gjør et lynraskt asynkront oppslag i PostgreSQL: `SELECT * FROM factoids JOIN actors... WHERE actor.name = $1`.
4. **Sanntids-Push (SpacetimeDB):** Hvis et treff finnes, dytter Rust-tjenesten faktoiden inn i SpacetimeDB som et event: `LiveFactoidEvent`.
5. **Visning (SvelteKit):** Studio-grensesnittet lytter på SpacetimeDB. Når `LiveFactoidEvent` inntreffer, popper faktoiden lydløst opp i en egen boks på skjermen.
### 2.1 Lagringsstrategi
Live-transkripsjonen er **flyktig arbeidsdata** — ikke en del av det permanente datasettet. Den lagres i PostgreSQL som en feilsøkingslogg med TTL (standard 30 dager, konfigurerbart):
```sql
live_transcription_log (
id SERIAL,
session_id UUID, -- knytter chunks til en innspillingssesjon
chunk_timestamp TIMESTAMPTZ,
chunk_text TEXT,
matched_entities TEXT[], -- hvilke entiteter NER fant
pushed_factoids UUID[], -- hvilke faktoider ble pushet til frontend
created_at TIMESTAMPTZ DEFAULT now()
)
```
En nattlig jobbkø-jobb sletter rader eldre enn TTL. Dette gir mulighet til å feilsøke kjeden chunk → entity match → factoid push ("hvorfor dukket den faktoiden opp?") uten å akkumulere data over tid.
Den endelige, kvalitetssikrede transkripsjonen av hele episoden lages etterpå via `medium` + `initial_prompt` gjennom Podcastfabrikkens pipeline (se `podcastfabrikken.md`).
## 3. Utviklingsfaser (For Claude Code)
* **Fase 1:** Ikke bygg live-lyd enda. Bygg funksjonaliteten der grensesnittet lytter på SpacetimeDB, og lag et dummy-script i Rust som dytter test-faktoider inn i SpacetimeDB for å verifisere UI-et.
* **Fase 2:** Koble Whisper til et offline lydopptak og kjør NER/oppslag mot PostgreSQL.

View file

@ -9,13 +9,25 @@ Dette er en asynkron arbeidsflyt som kombinerer filsystem, AI, databaser og CI/C
1. **Trigger (Opplasting/Oppdatering):** Brukeren laster opp en `.mp3`-fil via SvelteKit-grensesnittet. Dette rutes enten som en *ny* episode (`INSERT`), eller en *oppdatering* av en eksisterende (`UPDATE`).
2. **Kø-system (PostgreSQL jobbkø):** Siden lydprosessering tar tid (CPU-intensivt), legges oppgaven i den felles jobbkøen (se `docs/features/jobbkø.md`). Opplastingen oppretter to jobber i sekvens: først `whisper_transcribe`, deretter `openrouter_analyze` (som trigges automatisk ved fullført transkripsjon).
3. **Transkripsjon (faster-whisper):** Rust-worker trigger `faster-whisper` lokalt på serveren og genererer rå tekst med tidsstempler.
4. **AI-Analyse (OpenRouter):** Transkripsjonen sendes til OpenRouter (Claude-modell) for uttrekk av forslag til tittel, sammendrag, show notes og kapittler.
5. **Manuell Godkjenning & Fletting (SvelteKit):**
3. **Transkripsjon (faster-whisper):** Rust-worker kaller faster-whisper-server (OpenAI-kompatibelt API, `POST /v1/audio/transcriptions`) med `response_format=srt` og mottar SRT direkte. Modell: `Systran/faster-whisper-medium` med `initial_prompt` (navneliste).
4. **Lagring av transkripsjon (Git):** Rust-worker committer SRT-filen til Forgejo. SRT er master-formatet — redigerbart, tidsstemplet, og et etablert standardformat. Git gir diff, historikk og sporbarhet. Redaksjonen kan redigere SRT direkte.
5. **Avledede formater (PostgreSQL):** Ved commit (via Forgejo webhook) parser en Rust-worker SRT-filen og genererer:
* **Ren tekst** — strippes fra SRT (fjern tidsstempler/sekvensnummer) for lesbart publiseringsdokument
* **Segmenter** — tidsstemplede utdrag koblet til Aktører/Temaer i kunnskapsgrafen
* **Full-text søkeindeks** — for oppslag på tvers av episoder
6. **AI-Analyse (OpenRouter):** Transkripsjonen sendes til OpenRouter (Claude-modell) for uttrekk av forslag til tittel, sammendrag, show notes og kapittler.
7. **Manuell Godkjenning & Fletting (SvelteKit):**
* *For nye episoder:* Presenteres som et ferskt utkast.
* *For oppdateringer:* Viser AI-ens nye forslag side-om-side med eksisterende metadata. Redaksjonen kan da velge hva som skal beholdes eller flettes (merge).
6. **Publisering (PostgreSQL):** Ved "Godkjenn" lagres metadataene permanent i databasen.
7. **RSS-Generering:** SvelteKit-appen genererer en oppdatert `/feed.xml`.
8. **Publisering (PostgreSQL):** Ved "Godkjenn" lagres metadataene permanent i databasen.
9. **RSS-Generering:** SvelteKit-appen genererer en oppdatert `/feed.xml`.
### 2.1 Episodeside (publisert visning)
Hver publisert episode får en side med:
* Lydavspiller + sammendrag + kapitler + stikkord
* Personreferanser og artikler (koblet via kunnskapsgrafen)
* Fane: **SRT** (nedlastbar undertekstfil — master-kopi fra Git)
* Fane: **Ren tekst** (lesbart transkripsjonsdokument — avledet fra SRT, lagret i PG)
## 3. Spesialhåndtering: Oppdatering av eksisterende episoder (Cache-busting)
Podcast-apper (Apple, Spotify) og CDN-er cacher innhold aggressivt. For at en endring i f.eks. "Introepisoden" skal slå gjennom hos lytterne, MÅ følgende tekniske regler følges:
@ -27,7 +39,38 @@ Podcast-apper (Apple, Spotify) og CDN-er cacher innhold aggressivt. For at en en
* Alternativ A: "Behold opprinnelig dato" (Episoden oppdateres i det stille for nye lyttere).
* Alternativ B: "Sett dato til NÅ" (Episoden spretter til toppen av feeden som en ny utgivelse).
## 4. Instruks for Claude Code
## 4. Whisper-konfigurasjon
* **Tjeneste:** `fedirz/faster-whisper-server` (Docker, OpenAI-kompatibelt API)
* **Endepunkt:** `POST /v1/audio/transcriptions` med `response_format=srt`
* **Beslutning:** SRT direkte fra Whisper, ikke verbose JSON. Verbose JSON inneholder diagnostikk (tokens, logprob, temperatur) som ikke har verdi for oss. SRT gir tidsstempler + tekst i et etablert format som er redigerbart, diffbart i Git, og trivielt å parse til ren tekst og segmenter.
* **Modeller (benchmarket med E277.mp3, 32:45 norsk tale, CPU i7-13900K):**
| Konfigurasjon | Tid (CPU) | Seg | Tegn | Kommentar |
|---|---|---|---|---|
| `small` | ~6 min | 777 | 25851 | Rask, men hyppige feil i egennavn |
| `medium` | ~18 min | 442 | 26938 | God balanse, noen navnefeil |
| `medium` + prompt | ~17 min | 455 | 26957 | Riktige egennavn, anbefalt standard |
| `large-v3` | ~24 min | 520 | 14559 | Hallusinerer uten VAD — IKKE bruk uten VAD |
| `large-v3` + VAD | ~31 min | 964 | 28291 | God kvalitet, men noen navnefeil |
| `large-v3` + VAD + prompt | ~31 min | 964 | 28295 | Best kvalitet, riktige egennavn |
* **Anbefaling:** `medium` + `initial_prompt` som standard. `large-v3` + VAD + prompt for best mulig kvalitet der det er verdt ventetiden.
* **Viktig:** `large-v3` KREVER `vad_filter=true` — uten hallusinerer modellen repeterende tekst.
* **Språk:** Sett `language=no` eksplisitt for norsk — unngå auto-detect som kan velge dansk/svensk.
### 4.1 initial_prompt (navneliste)
`initial_prompt` primes Whisper med ordforråd som forbedrer gjenkjenning av egennavn. Effekten er tydelig:
* Uten prompt: "Vegard Nøgnes", "SideLinja", "Sidlinja"
* Med prompt: "Vegard Nøtnæs", "Sidelinja" (riktig)
Prompten bygges automatisk av Rust-worker fra en statisk navneliste + aktører i kunnskapsgrafen:
```
Sidelinja podcast med Vegard Nøtnæs, Trond Sørensen, Arne Eidshagen,
Peter Hagen, Nicolai Buzatu, Bjørn Einar Drag, Øystein Sjølie
```
## 5. Instruks for Claude Code
* **Lydfiler:** Håndter filopplasting i SvelteKit strømmende (streaming) for filer >100MB for å unngå minne-lekkasjer.
* **Feilhåndtering:** Hvis OpenRouter timer ut eller Whisper feiler, må oppgaven flagges med status `error` i databasen slik at brukeren kan trigge jobben på nytt manuelt via UI.
* **Opprydding (Disk):** Når en fil oppdateres vellykket, skal den gamle/foreldede `.mp3`-filen enten slettes fra Hetzner-serveren automatisk, eller flyttes til en `/archive/`-mappe basert på en miljøvariabel.
* **Opprydding (Disk):** Når en fil oppdateres vellykket, skal den gamle/foreldede `.mp3`-filen enten slettes fra Hetzner-serveren automatisk, eller flyttes til en `/archive/`-mappe basert på en miljøvariabel.
* **Transkripsjoner:** Master-kopi alltid i Git. Aldri rediger avledede formater direkte i PG — de regenereres fra Git-kilden.

View file

@ -1,7 +1,18 @@
# Oppsett: Lokalt Utviklingsmiljø (WSL2)
**Filsti:** `docs/setup/lokal.md`
Denne oppskriften setter opp en lokal utviklingsreplika av Sidelinja i WSL2. Målet er at all utvikling og testing skjer her — aldri på produksjonsserveren.
Det lokale miljøet er et **utviklingsmiljø for kode**, ikke en staging-replika av prod. Vi tester kode her og infrastruktur-config direkte i prod.
## Hva som gjøres hvor
| Aktivitet | Hvor | Hvorfor |
|---|---|---|
| Skrive/teste kode (Rust, SvelteKit, TypeScript) | Lokalt | Rask iterasjon, HMR, ingen risiko |
| PG-skjema og migrasjoner | Lokalt først, deploy til prod | Test mot lokal PG, push migrasjoner |
| Whisper/AI-eksperimentering | Lokalt | Tung CPU-bruk, eksperimentelt |
| Docker-compose endringer | Direkte i prod | Miljøene er for forskjellige til å teste lokalt |
| Caddy/Authentik/Forgejo config | Direkte i prod | Avhenger av domener, sertifikater, SSO |
| Prompt-testing (Promptfoo) | Lokalt via AI Gateway | Systematisk testing for du deployer prompts |
## 0. Forutsetninger
- Windows 11 med WSL2 (Ubuntu 24.04 LTS)
@ -22,9 +33,6 @@ nvm use 20
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# tea CLI (Forgejo)
# Installer fra: https://gitea.com/gitea/tea/releases
```
## 2. Klon prosjektet
@ -32,193 +40,94 @@ source ~/.cargo/env
```bash
mkdir -p ~/server
cd ~/server
git clone ssh://git@sidelinja.no:222/sidelinja/sidelinja.git .
git clone ssh://git@git.sidelinja.org:222/sidelinja/server.git .
# Eller om repo allerede finnes lokalt, sett opp remote:
git remote add forgejo ssh://git@sidelinja.no:222/sidelinja/sidelinja.git
git remote add forgejo ssh://git@git.sidelinja.org:222/sidelinja/server.git
```
## 3. Lokal mappestruktur
```bash
# Docker-volumer for databaser (flyktige, ikke backupes)
mkdir -p .docker-data/{postgres,spacetimedb}
mkdir -p .docker-data/media/podcast
mkdir -p .docker-data/logs/caddy
# Docker-volumer (flyktige, gitignored)
mkdir -p .docker-data/{postgres,redis,caddy,spacetimedb}
mkdir -p .docker-data/{media/podcast,logs/caddy,whisper-models}
```
## 4. Miljøvariabler (.env.local)
```bash
cat > .env.local << 'EOF'
# === Lokalt utviklingsmiljø ===
# === Lokalt utviklingsmiljo ===
DOMAIN=localhost
COMPOSE_PROJECT_NAME=sidelinja-dev
# === PostgreSQL (lokale verdier, ikke hemmelige) ===
# === PostgreSQL ===
POSTGRES_USER=sidelinja
POSTGRES_PASSWORD=localdev
POSTGRES_DB=sidelinja
# === SpacetimeDB ===
# Lokale defaults
# === LiveKit (lokale test-nøkler) ===
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=devsecret
# === OpenRouter (bruk egen nøkkel for AI-testing) ===
OPENROUTER_API_KEY=<din personlige nøkkel>
# === AI Gateway (sett dine egne nokler) ===
LITELLM_MASTER_KEY=localdev
GEMINI_API_KEY=
ANTHROPIC_API_KEY=
OPENROUTER_API_KEY=
EOF
```
## 5. docker-compose.dev.yml
Spinner opp kun infrastruktur-tjenestene. SvelteKit kjøres utenfor Docker for HMR.
```yaml
# Fullstendig docker-compose.dev.yml bygges ut ved implementering.
# Struktur:
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./.docker-data/postgres:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432" # KUN localhost, aldri 0.0.0.0
networks:
- sidelinja-dev
spacetimedb:
image: clockworklabs/spacetimedb
volumes:
- ./.docker-data/spacetimedb:/var/lib/spacetimedb
ports:
- "127.0.0.1:3001:3000" # KUN localhost
networks:
- sidelinja-dev
livekit:
image: livekit/livekit-server
ports:
- "127.0.0.1:7880:7880" # KUN localhost
networks:
- sidelinja-dev
caddy:
image: caddy:2
ports:
- "127.0.0.1:443:443"
- "127.0.0.1:80:80"
volumes:
- ./config/caddy/Caddyfile.dev:/etc/caddy/Caddyfile
- ./.docker-data/logs/caddy:/var/log/caddy
- ./.docker-data/media:/srv/media
networks:
- sidelinja-dev
networks:
sidelinja-dev:
driver: bridge
```
## 6. Start infrastruktur
## 5. Start lokale tjenester
```bash
# Start Docker-tjenestene
# Start infrastruktur
docker compose -f docker-compose.dev.yml --env-file .env.local up -d
# Verifiser
docker compose -f docker-compose.dev.yml ps
docker compose -f docker-compose.dev.yml exec postgres pg_isready
docker compose -f docker-compose.dev.yml --env-file .env.local ps
docker compose -f docker-compose.dev.yml --env-file .env.local exec postgres pg_isready
```
## 7. Start SvelteKit (utenfor Docker, for HMR)
Lokale tjenester (docker-compose.dev.yml):
- **PostgreSQL**`127.0.0.1:5432` (kodeutvikling, migrasjoner)
- **Redis**`127.0.0.1:6379`
- **Caddy**`127.0.0.1:80/443` (lokal HTTPS for WebRTC)
- **faster-whisper**`127.0.0.1:8000` (transkripsjon-eksperimentering)
## 6. Utviklingsflyt
### Kode (daglig)
```bash
cd sveltekit/ # eller der SvelteKit-prosjektet lever
npm install
npm run dev -- --host # Tilgjengelig på https://localhost:5173
```
## 8. Start Rust Workers (under utvikling)
```bash
cd workers/ # eller der Rust worker-prosjektet lever
cargo run --bin sidelinja-worker
```
## 9. Database-seeding (valgfritt)
```bash
# Kjør SQL-seed mot lokal PostgreSQL for testdata
psql -h localhost -U sidelinja -d sidelinja -f db/seed.sql
```
## 10. Lokal Caddy (HTTPS for WebRTC)
WebRTC krever sikker kontekst. Lokal Caddy genererer self-signed cert:
```caddyfile
# config/caddy/Caddyfile.dev
{
local_certs
}
localhost {
# SvelteKit dev server
reverse_proxy host.docker.internal:5173
# SpacetimeDB
handle_path /spacetime/* {
reverse_proxy spacetimedb:3000
}
# LiveKit
handle_path /livekit/* {
reverse_proxy livekit:7880
}
# Media
handle_path /media/* {
root * /srv/media
file_server
}
}
```
## 11. Daglig utviklingsflyt
```bash
# 1. Start infrastruktur (om ikke allerede kjørende)
# 1. Start lokale tjenester (om ikke allerede kjorende)
docker compose -f docker-compose.dev.yml --env-file .env.local up -d
# 2. Start SvelteKit
# 2. Start SvelteKit med HMR
cd sveltekit && npm run dev
# 3. Gjør endringer, test lokalt
# 3. Start Rust workers
cd workers && cargo run --bin sidelinja-worker
# 4. Commit og push til Forgejo
# 4. Skriv kode, test lokalt
```
### Deploy
```bash
# 1. Commit og push
git add <filer>
git commit -m "beskrivelse"
git push forgejo main
# 5. Deploy til prod (via SSH)
ssh sidelinja@<server-ip> "cd /srv/sidelinja && git pull && docker compose up -d --build"
# 2. Deploy til prod via SSH
ssh sidelinja@157.180.81.26 "cd /srv/sidelinja && git pull && docker compose up -d --build"
```
## 12. Forskjeller fra produksjon
## 7. Forskjeller fra produksjon (bevisste)
| Aspekt | Lokalt | Produksjon |
|---|---|---|
| SvelteKit | `npm run dev` (HMR) | Docker container (bygget) |
| **Formål** | Kodeutvikling | Kjørende tjenester |
| SvelteKit | `npm run dev` (HMR) | Docker container |
| Porter | Localhost-bundet (`127.0.0.1`) | Kun 80/443 via Caddy |
| HTTPS | Self-signed (Caddy `local_certs`) | Let's Encrypt |
| Forgejo | Ikke installert, push direkte til prod | Docker container |
| Authentik | Ikke installert (bypass auth lokalt) | Docker container |
| DB-data | Flyktig (`.docker-data/`, gitignored) | Persistent (`/srv/sidelinja/data/`) |
| Whisper | Valgfritt, kan mockes | Alltid tilgjengelig |
| Forgejo | Ikke installert — push til prod | Docker container |
| Authentik | Ikke installert — bypass auth lokalt | Docker container |
| DB-data | Flyktig (`.docker-data/`, gitignored) | Persistent, backupes daglig |
| Whisper | For eksperimentering | Produksjonstranskripsjon |
| AI Gateway | For prompt-testing | Produksjonsruting |

View file

@ -252,13 +252,70 @@ Forgejo konfigureres med Authentik som OAuth2-kilde:
## 11. Backup-strategi
```bash
# Daglig snapshot (cron, 03:00)
# Inkluderer: data/, media/, config/, docker-compose.yml
# Ekskluderer: logs/ (arkiveres separat månedlig)
Se `ARCHITECTURE.md` seksjon 2.2 for full dataklassifisering. Kun kategori 1 (kritisk) og Forgejo-data backupes.
# Eksempel med restic eller borgbackup:
# borg create /backup/sidelinja::{now} /srv/sidelinja --exclude /srv/sidelinja/logs
### 11.1 PostgreSQL (daglig, 03:00)
```bash
# pg_dump er konsistent selv under last — ingen nedetid
docker compose exec -T postgres pg_dump -U sidelinja -Fc sidelinja \
> /srv/sidelinja/backup/pg/sidelinja_$(date +%Y%m%d).dump
# Behold 30 dager, slett eldre
find /srv/sidelinja/backup/pg/ -name "*.dump" -mtime +30 -delete
```
### 11.2 Media-filer (daglig, 03:30)
```bash
# Inkrementell med rsync til lokal backup-disk eller ekstern lagring
rsync -a --delete /srv/sidelinja/media/ /srv/sidelinja/backup/media/
```
### 11.3 Forgejo-data (daglig, 04:00)
```bash
# Forgejo-repos kan gjenskapes, men det er tidkrevende.
# Sikkerhetsnett-backup av hele data-mappen:
rsync -a --delete /srv/sidelinja/data/forgejo/ /srv/sidelinja/backup/forgejo/
```
### 11.4 Hemmeligheter (.env)
```bash
# Manuell kopi ved endring — ALDRI i Git
cp /srv/sidelinja/.env /srv/sidelinja/backup/env_$(date +%Y%m%d)
chmod 600 /srv/sidelinja/backup/env_*
```
### 11.5 Cron-oppsett
```bash
# /etc/cron.d/sidelinja-backup
0 3 * * * sidelinja /srv/sidelinja/scripts/backup-pg.sh
30 3 * * * sidelinja /srv/sidelinja/scripts/backup-media.sh
0 4 * * * sidelinja /srv/sidelinja/scripts/backup-forgejo.sh
```
### 11.6 Hva som IKKE backupes (bevisst)
- **Redis** — cache, regenereres automatisk
- **Caddy-data** — sertifikater regenereres av Let's Encrypt
- **Avledede data i PG** (ren tekst, segmenter, søkeindeks) — regenereres fra Git
- **Logger** — rulleres med logrotate, arkiveres separat ved behov
- **Whisper-modeller** — re-download fra HuggingFace
- **SpacetimeDB** — sanntidsdata synkes til PG, in-memory state er flyktig
### 11.7 Restore-prosedyre
```bash
# 1. PostgreSQL
docker compose exec -T postgres pg_restore -U sidelinja -d sidelinja --clean \
< /srv/sidelinja/backup/pg/sidelinja_YYYYMMDD.dump
# 2. Media
rsync -a /srv/sidelinja/backup/media/ /srv/sidelinja/media/
# 3. Forgejo
docker compose down forgejo
rsync -a /srv/sidelinja/backup/forgejo/ /srv/sidelinja/data/forgejo/
docker compose up -d forgejo
# 4. Avledede data: trigges automatisk ved webhook eller manuelt
# Rust-worker reimporterer alle SRT-filer fra Git til PG
```
## 12. Deploy-workflow (etter initial setup)