diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8375 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d4109de..04bdf5d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 474fa95..2d1c813 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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/.md` før du implementerer en feature diff --git a/config/caddy/Caddyfile.dev b/config/caddy/Caddyfile.dev new file mode 100644 index 0000000..3a082b9 --- /dev/null +++ b/config/caddy/Caddyfile.dev @@ -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 + } +} diff --git a/config/postgres/init/01-create-databases.sql b/config/postgres/init/01-create-databases.sql new file mode 100644 index 0000000..d245efd --- /dev/null +++ b/config/postgres/init/01-create-databases.sql @@ -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; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..64ab176 --- /dev/null +++ b/docker-compose.dev.yml @@ -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 diff --git a/docs/features/ai_gateway.md b/docs/features/ai_gateway.md new file mode 100644 index 0000000..8406c97 --- /dev/null +++ b/docs/features/ai_gateway.md @@ -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 diff --git a/docs/features/live_ai_assistent.md b/docs/features/live_ai_assistent.md index c15d694..5e147d2 100644 --- a/docs/features/live_ai_assistent.md +++ b/docs/features/live_ai_assistent.md @@ -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. diff --git a/docs/features/podcastfabrikken.md b/docs/features/podcastfabrikken.md index 329d258..76f23b8 100644 --- a/docs/features/podcastfabrikken.md +++ b/docs/features/podcastfabrikken.md @@ -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. \ No newline at end of file +* **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. \ No newline at end of file diff --git a/docs/setup/lokal.md b/docs/setup/lokal.md index b27c173..eed3e07 100644 --- a/docs/setup/lokal.md +++ b/docs/setup/lokal.md @@ -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= +# === 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 git commit -m "beskrivelse" git push forgejo main -# 5. Deploy til prod (via SSH) -ssh sidelinja@ "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 | diff --git a/docs/setup/produksjon.md b/docs/setup/produksjon.md index ecbe1ff..9fe18ea 100644 --- a/docs/setup/produksjon.md +++ b/docs/setup/produksjon.md @@ -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)