- 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>
349 lines
11 KiB
Markdown
349 lines
11 KiB
Markdown
# Oppsett: Produksjonsserver (Hetzner VPS)
|
|
**Filsti:** `docs/setup/produksjon.md`
|
|
|
|
Denne oppskriften tar en fersk Ubuntu VPS fra null til en komplett Sidelinja-installasjon. Hvert steg er sekvensielt — ikke hopp over noe.
|
|
|
|
## 0. Forutsetninger
|
|
- Hetzner VPS med Ubuntu 24.04 LTS (8 vCPU, 16 GB RAM minimum)
|
|
- DNS A-records som peker til VPS-ens IP:
|
|
- `sidelinja.org` + `*.sidelinja.org`
|
|
- `vegard.info` + `*.vegard.info`
|
|
- SSH-tilgang med nøkkelpar (passordautentisering deaktiveres i steg 1)
|
|
|
|
## 1. Grunnsikring av VPS
|
|
|
|
```bash
|
|
# Oppdater systemet
|
|
apt update && apt upgrade -y
|
|
|
|
# Opprett tjenestebruker (ikke kjør alt som root)
|
|
adduser sidelinja
|
|
usermod -aG sudo sidelinja
|
|
|
|
# Kopier SSH-nøkkel til ny bruker
|
|
mkdir -p /home/sidelinja/.ssh
|
|
cp ~/.ssh/authorized_keys /home/sidelinja/.ssh/
|
|
chown -R sidelinja:sidelinja /home/sidelinja/.ssh
|
|
|
|
# Deaktiver passordautentisering og root-login
|
|
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
|
|
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
|
|
systemctl restart sshd
|
|
|
|
# Brannmur: kun SSH, HTTP, HTTPS
|
|
ufw allow OpenSSH
|
|
ufw allow 80/tcp
|
|
ufw allow 443/tcp
|
|
ufw enable
|
|
```
|
|
|
|
**Logg ut og logg inn som `sidelinja` fra nå av.**
|
|
|
|
## 2. Installer Docker
|
|
|
|
```bash
|
|
# Docker Engine (offisiell repo)
|
|
sudo apt install -y ca-certificates curl gnupg
|
|
sudo install -m 0755 -d /etc/apt/keyrings
|
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
|
|
sudo apt update
|
|
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
|
|
# Kjør Docker uten sudo
|
|
sudo usermod -aG docker sidelinja
|
|
newgrp docker
|
|
```
|
|
|
|
## 3. Opprett mappestruktur
|
|
|
|
```bash
|
|
sudo mkdir -p /srv/sidelinja/{config,data,media,logs}
|
|
sudo mkdir -p /srv/sidelinja/config/{caddy,authentik}
|
|
sudo mkdir -p /srv/sidelinja/data/{postgres,spacetimedb,forgejo,authentik}
|
|
sudo mkdir -p /srv/sidelinja/media/podcast
|
|
sudo mkdir -p /srv/sidelinja/logs/caddy
|
|
sudo chown -R sidelinja:sidelinja /srv/sidelinja
|
|
```
|
|
|
|
Resultat:
|
|
```
|
|
/srv/sidelinja/
|
|
├── docker-compose.yml
|
|
├── .env
|
|
├── config/
|
|
│ ├── caddy/Caddyfile
|
|
│ └── authentik/
|
|
├── data/
|
|
│ ├── postgres/
|
|
│ ├── spacetimedb/
|
|
│ ├── forgejo/
|
|
│ └── authentik/
|
|
├── media/
|
|
│ └── podcast/
|
|
└── logs/
|
|
└── caddy/
|
|
```
|
|
|
|
## 4. Miljøvariabler (.env)
|
|
|
|
```bash
|
|
cat > /srv/sidelinja/.env << 'EOF'
|
|
# === Domener ===
|
|
DOMAIN_SIDELINJA=sidelinja.org
|
|
DOMAIN_VEGARD=vegard.info
|
|
DOMAIN_AUTH=auth.sidelinja.org
|
|
COMPOSE_PROJECT_NAME=sidelinja
|
|
|
|
# === PostgreSQL ===
|
|
POSTGRES_USER=sidelinja
|
|
POSTGRES_PASSWORD=<generer med: openssl rand -hex 32>
|
|
POSTGRES_DB=sidelinja
|
|
|
|
# === Authentik ===
|
|
AUTHENTIK_SECRET_KEY=<generer med: openssl rand -hex 64>
|
|
AUTHENTIK_POSTGRESQL_PASSWORD=<generer med: openssl rand -hex 32>
|
|
# Authentik bruker sin egen database i samme PostgreSQL-instans
|
|
AUTHENTIK_POSTGRESQL_HOST=postgres
|
|
AUTHENTIK_POSTGRESQL_USER=authentik
|
|
AUTHENTIK_POSTGRESQL_NAME=authentik
|
|
|
|
# === Forgejo ===
|
|
FORGEJO_DB_PASSWD=<generer med: openssl rand -hex 32>
|
|
|
|
# === LiveKit ===
|
|
LIVEKIT_API_KEY=<generer>
|
|
LIVEKIT_API_SECRET=<generer med: openssl rand -hex 32>
|
|
|
|
# === OpenRouter ===
|
|
OPENROUTER_API_KEY=<fra openrouter.ai>
|
|
|
|
# === Intern ===
|
|
# Ingen porter eksponeres utenom 80/443. Alt rutes internt via Docker-nettverket.
|
|
EOF
|
|
|
|
chmod 600 /srv/sidelinja/.env
|
|
```
|
|
|
|
## 5. Tjeneste-installasjon (rekkefølge)
|
|
|
|
Tjenestene startes i rekkefølge fordi noen avhenger av andre. Alle defineres i `docker-compose.yml`, men vi verifiserer hvert lag før vi går videre.
|
|
|
|
### Lag A: Fundament (ingen avhengigheter mellom seg)
|
|
1. **Docker-nettverk:** Opprett internt nettverk `sidelinja-net`
|
|
2. **PostgreSQL:** Start, opprett databaser for Authentik og Forgejo, verifiser (`pg_isready`)
|
|
3. **Caddy:** Start med Caddyfile for alle domener, verifiser at HTTPS fungerer
|
|
4. **Authentik:** Start, gjennomfør initial setup via `https://auth.sidelinja.org`
|
|
5. **Forgejo:** Start med Authentik som OAuth2-provider, opprett organisasjon og repo
|
|
|
|
### Lag B: Sanntid (krever nettverk)
|
|
6. **SpacetimeDB:** Start, verifiser tilkobling
|
|
7. **LiveKit:** Start, verifiser at WebRTC fungerer
|
|
|
|
### Lag C: Applikasjon (krever alt over)
|
|
8. **SvelteKit:** Bygg og start container, verifiser at frontenden laster
|
|
9. **Rust Workers:** Bygg og start container(e), verifiser at jobbkøen polles
|
|
|
|
## 6. docker-compose.yml (skjelett)
|
|
|
|
```yaml
|
|
# Fullstendig docker-compose.yml bygges ut når tjenestene implementeres.
|
|
# Denne seksjonen dokumenterer strukturen og viktige regler.
|
|
|
|
# REGLER:
|
|
# - Ingen "ports:" mot host UTENOM Caddy (80, 443)
|
|
# - Alle tjenester på samme interne nettverk (sidelinja-net)
|
|
# - Volumer bruker bind mounts til /srv/sidelinja/
|
|
# - .env-filen lastes automatisk av Docker Compose
|
|
|
|
networks:
|
|
sidelinja-net:
|
|
driver: bridge
|
|
|
|
services:
|
|
caddy: # Eneste tjeneste med eksponerte porter (80, 443)
|
|
postgres: # data:/srv/sidelinja/data/postgres
|
|
authentik: # SSO for alle domener, på auth.sidelinja.org
|
|
forgejo: # data:/srv/sidelinja/data/forgejo, på git.sidelinja.org
|
|
spacetimedb: # data:/srv/sidelinja/data/spacetimedb
|
|
livekit: # Intern port, proxyet via Caddy
|
|
sveltekit: # Intern port, proxyet via Caddy
|
|
workers: # Rust job workers, ingen porter
|
|
```
|
|
|
|
## 7. Caddy (Caddyfile grunnstruktur)
|
|
|
|
```caddyfile
|
|
# === SSO (felles for alle domener) ===
|
|
auth.sidelinja.org {
|
|
reverse_proxy authentik:9000
|
|
}
|
|
|
|
# === Sidelinja (hovedapplikasjon) ===
|
|
sidelinja.org {
|
|
# SvelteKit (frontend + API)
|
|
reverse_proxy sveltekit:3000
|
|
|
|
# LiveKit (WebSocket upgrade)
|
|
handle_path /livekit/* {
|
|
reverse_proxy livekit:7880
|
|
}
|
|
|
|
# SpacetimeDB (WebSocket)
|
|
handle_path /spacetime/* {
|
|
reverse_proxy spacetimedb:3000
|
|
}
|
|
|
|
# Podcast media (statiske filer med byte-range support)
|
|
handle_path /media/* {
|
|
root * /srv/sidelinja/media
|
|
file_server
|
|
}
|
|
|
|
# Podcast access log (kun media-forespørsler)
|
|
log {
|
|
output file /srv/sidelinja/logs/caddy/podcast_access.log
|
|
format json
|
|
}
|
|
}
|
|
|
|
# === Forgejo (Git) ===
|
|
git.sidelinja.org {
|
|
reverse_proxy forgejo:3000
|
|
}
|
|
|
|
# === Vegard.info ===
|
|
vegard.info {
|
|
# Konfigureres når innhold er klart
|
|
respond "Under construction" 200
|
|
}
|
|
```
|
|
|
|
## 8. PostgreSQL: Initielle databaser
|
|
|
|
Ved første oppstart må det opprettes separate databaser og brukere for Authentik og Forgejo:
|
|
|
|
```sql
|
|
-- Kjøres mot PostgreSQL etter første start
|
|
-- (eller via init-script montert til /docker-entrypoint-initdb.d/)
|
|
|
|
CREATE USER authentik WITH PASSWORD '<AUTHENTIK_POSTGRESQL_PASSWORD>';
|
|
CREATE DATABASE authentik OWNER authentik;
|
|
|
|
CREATE USER forgejo WITH PASSWORD '<FORGEJO_DB_PASSWD>';
|
|
CREATE DATABASE forgejo OWNER forgejo;
|
|
```
|
|
|
|
## 9. Authentik: Initial konfigurasjon
|
|
|
|
Etter oppstart, gå til `https://auth.sidelinja.org/if/flow/initial-setup/`:
|
|
1. Opprett admin-konto
|
|
2. Opprett OAuth2/OpenID Connect-provider for Forgejo
|
|
3. Opprett OAuth2/OpenID Connect-provider for SvelteKit (senere)
|
|
4. Konfigurer brukergrupper etter behov (redaksjon, admin)
|
|
|
|
## 10. Forgejo: Koble til Authentik
|
|
|
|
Forgejo konfigureres med Authentik som OAuth2-kilde:
|
|
- Authentication Source: OAuth2
|
|
- Provider: OpenID Connect
|
|
- Discovery URL: `https://auth.sidelinja.org/application/o/<slug>/.well-known/openid-configuration`
|
|
- Etter oppsett: opprett organisasjon `sidelinja`, opprett repo `sidelinja`
|
|
|
|
## 11. Backup-strategi
|
|
|
|
Se `ARCHITECTURE.md` seksjon 2.2 for full dataklassifisering. Kun kategori 1 (kritisk) og Forgejo-data backupes.
|
|
|
|
### 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)
|
|
Etter at serveren er satt opp, er dette den daglige deploy-flyten:
|
|
|
|
```bash
|
|
# Fra lokal maskin (WSL2):
|
|
git push forgejo main
|
|
|
|
# SSH inn til server:
|
|
ssh sidelinja@<server-ip>
|
|
cd /srv/sidelinja
|
|
git pull
|
|
docker compose build --no-cache <tjeneste>
|
|
docker compose up -d <tjeneste>
|
|
```
|
|
|
|
## 13. Verifisering etter oppsett
|
|
|
|
### Lag A (minimum fungerende server)
|
|
- [ ] `https://auth.sidelinja.org` viser Authentik login
|
|
- [ ] `https://git.sidelinja.org` viser Forgejo, innlogging via Authentik fungerer
|
|
- [ ] PostgreSQL: `docker compose exec postgres pg_isready` returnerer OK
|
|
- [ ] SSH-push fra lokal WSL2 til Forgejo fungerer
|
|
|
|
### Lag B-C (når implementert)
|
|
- [ ] `https://sidelinja.org` laster SvelteKit-appen
|
|
- [ ] `https://vegard.info` svarer
|
|
- [ ] SpacetimeDB: WebSocket-tilkobling fra nettleser fungerer
|
|
- [ ] LiveKit: Test-rom med video/lyd fungerer
|
|
- [ ] Media: `curl -I https://sidelinja.org/media/podcast/test.mp3` returnerer `Accept-Ranges: bytes`
|