Datenbank von SQLite auf Postgres umgestellt

Verbindung über KVER_DSN; Tabelle user heißt jetzt account (reserviertes
Wort in Postgres). kver import-sqlite übernimmt die alte Datei einmalig in
einer Transaktion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiXsPUqw7oeomZ8wZrQW5q
This commit is contained in:
irrlicht
2026-09-26 22:22:07 +02:00
co-authored by Claude Opus 5.5
parent c6ab082f8c
commit 2714fdb1a5
15 changed files with 412 additions and 301 deletions
+2 -36
View File
@@ -215,43 +215,9 @@ mitgelöscht, zugehörige Mediendateien best effort entfernt.
---
## Datenbankschema (SQLite)
## Datenbankschema
```sql
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid INTEGER UNIQUE,
username TEXT UNIQUE,
password BLOB,
created_at INTEGER,
last_login INTEGER
);
CREATE TABLE session (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid INTEGER,
value TEXT UNIQUE,
created_at INTEGER,
expires INTEGER,
description TEXT
);
CREATE TABLE entry (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pid INTEGER UNIQUE,
uid INTEGER,
created_at INTEGER,
content TEXT,
filepath TEXT
);
CREATE TABLE vote (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid INTEGER,
pid INTEGER,
mode TEXT -- 'left' | 'right'
);
```
Postgres; maßgeblich ist die Konstante `schema` in `db.go`.
---
+41 -12
View File
@@ -27,8 +27,8 @@ Beim ersten Deployment: DB und Medien einmalig in die Volumes kopieren
podman build -t kver .
```
Multi-Stage-Build (`Containerfile`): statisches Go-Binary (CGO aus, modernc-
SQLite ist pures Go), Laufzeit-Image ist Alpine mit unprivilegiertem Nutzer.
Multi-Stage-Build (`Containerfile`): statisches Go-Binary (CGO aus), Laufzeit-
Image ist Alpine mit unprivilegiertem Nutzer.
Die Warnung `HEALTHCHECK is not supported for OCI image format` ist erwartbar:
Podman baut OCI-Images, dort gibt es kein eingebettetes HEALTHCHECK. Der
@@ -46,8 +46,9 @@ podman run -d --name kver \
kver
```
- `kver-data` hält die SQLite-DB (`/app/data/kver.db`), `kver-media` die
hochgeladenen Bilder. Beides Named Volumes → überleben Image-Updates.
- Die Daten liegen in Postgres (`KVER_DSN`). `kver-data` hält nur noch die
GeoLite2-ASN-DB (und die alte SQLite-Datei), `kver-media` die hochgeladenen
Bilder. Beides Named Volumes → überleben Image-Updates.
- Port nur an localhost binden; nach außen geht es über den Reverse-Proxy.
- Der Container lauscht intern immer auf `:8080`; der externe Port (hier 7777)
ist nur das Host-seitige Mapping.
@@ -57,7 +58,7 @@ Konfiguration über Umgebungsvariablen:
| Variable | Default | Bedeutung |
|-------------|---------------------|----------------------|
| `KVER_ADDR` | `:8080` | Listen-Adresse im Container |
| `KVER_DB` | `/app/data/kver.db` | Pfad zur SQLite-Datei |
| `KVER_DSN` | _(Pflicht)_ | Postgres-Verbindung, z. B. `postgres://kver:pw@host:5432/kontrollverlust?sslmode=disable` |
| `KVER_GEOIP_ASN` | _(leer)_ | Pfad zur GeoLite2-ASN-`.mmdb`; leer = ASN-Auflösung aus |
## GeoLite2-ASN-Datenbank (optional)
@@ -77,9 +78,40 @@ Dann `KVER_GEOIP_ASN=/app/data/GeoLite2-ASN.mmdb` setzen. Die Datei wird beim St
einmalig gemappt; für aktualisierte Daten die Datei ersetzen und den Dienst neu
starten.
## Bestehende Daten übernehmen
## Postgres
DB und Medien einmalig in die Volumes kopieren (Volumes müssen existieren;
Die App erwartet eine Datenbank, die ihrem Login-User gehört. Einmalig als
Admin:
```sql
CREATE ROLE kver LOGIN PASSWORD '...';
CREATE DATABASE kontrollverlust OWNER kver;
REVOKE CONNECT, TEMPORARY ON DATABASE kontrollverlust FROM PUBLIC;
```
Tabellen und Indizes legt die App beim Start selbst an (`CREATE ... IF NOT
EXISTS`, als `kver` → gehören `kver`). Nicht als Admin anlegen.
## Umzug SQLite → Postgres (einmalig)
Dienst stoppen, dann mit dem neuen Image die alte Datei aus dem Volume
importieren. Der Import läuft in einer Transaktion, prüft die Zeilenzahlen und
bricht ab, wenn in Postgres schon Daten liegen:
```sh
systemctl --user stop kver
podman run --rm --env-file /etc/kver/kver.env \
-v kver-data:/app/data:ro \
kver import-sqlite /app/data/kver.db
systemctl --user start kver
```
Der Container braucht dabei (und im Betrieb) dieselbe Netzwerkanbindung an
Postgres wie der Dienst selbst.
## Bestehende Medien übernehmen
Medien einmalig ins Volume kopieren (Volumes müssen existieren;
Container kann laufen oder gestoppt sein):
```sh
@@ -88,14 +120,11 @@ podman volume create kver-media
# Rootless: podman unshare sorgt dafür, dass die UID ins User-Namespace-
# Mapping passt — ohne das bekommt der Container die Dateien nicht zu lesen.
podman unshare cp kver.db \
"$(podman volume inspect kver-data --format '{{.Mountpoint}}')/"
podman unshare cp -r static/media/. \
"$(podman volume inspect kver-media --format '{{.Mountpoint}}')/"
```
Danach Container (neu) starten. Die App legt beim ersten Start alle fehlenden
Tabellen und Indizes automatisch per `CREATE ... IF NOT EXISTS` an.
Danach Container (neu) starten.
## Reverse-Proxy (TLS) — Caddy
@@ -201,4 +230,4 @@ systemctl --user restart kver
```
Die App fährt bei SIGTERM sauber herunter (laufende Requests werden zu Ende
bedient, WAL-Checkpoint beim DB-Close).
bedient, dann wird die DB-Verbindung geschlossen).