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:
parent
53f0ccf49e
commit
4b56560bf9
11 changed files with 594 additions and 198 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
|
||||
131
ARCHITECTURE.md
131
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
|
||||
|
||||
|
|
|
|||
10
CLAUDE.md
10
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/<navn>.md` før du implementerer en feature
|
||||
|
|
|
|||
30
config/caddy/Caddyfile.dev
Normal file
30
config/caddy/Caddyfile.dev
Normal 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
|
||||
}
|
||||
}
|
||||
9
config/postgres/init/01-create-databases.sql
Normal file
9
config/postgres/init/01-create-databases.sql
Normal 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
72
docker-compose.dev.yml
Normal 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
170
docs/features/ai_gateway.md
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue