Reviewed-on: #9
PKV · Beihilfe Abrechnung
Selbst gehostete Web-App zur Verwaltung von Krankheitskosten für Beamte (Beihilfe + PKV).
Features
- Mandantenfähig (Haushalt / Familie)
- Passkey-Login mit E-Mail-OTP-Recovery
- Versicherte mit Debeka/DKV-Profil (Pensionär-Defaults 70/30)
- Leistungserbringer-Stammdaten
- Vorgänge mit Workflow: Eingang → Bezahlt → Beihilfe → Bescheid → PKV → Erstattung → Archiv
- Beleg-Erfassung einzeln oder als Stapel (Scan/Upload); optionale KI-Extraktion (OpenRouter)
- Beihilfebescheid: Mehrfachzuordnung, Archiv, nicht erstattbare Positionen (Steuer-Export CSV/PDF)
- Kontoauszug einlesen und Umsätze den Vorgängen zuordnen
- Dashboard: offene Erwartungen (Pipeline vs. unbezahlte Rechnungen), Fristen, Steuerjahr
- Vorgänge-Filter inkl. Status „Nicht bezahlt“
- Lokale Dokumentenablage (Paperless optional)
- Backup/Restore des Haushalts (ZIP unter Einstellungen)
- PWA-Manifest
Schnellstart (Entwicklung)
cp .env.example .env
# BETTER_AUTH_SECRET setzen (mind. 32 Zeichen)
./scripts/dev.sh # Postgres (Docker) + Next.js
# alternativ manuell:
# docker compose up postgres -d
# npm install && npm run db:migrate && npm run db:seed-dev && npm run dev
Optional Beispieldaten: npm run db:seed-dev
App: http://localhost:3000 (Next.js wählt bei belegtem Port automatisch z. B. 3001 — Auth nutzt dann die aktuelle Browser-URL)
./scripts/dev.sh restart / stop starten den Dev-Server neu bzw. beenden ihn (Postgres bleibt).
Dev-Login (lokal)
Nach npm run db:seed-dev:
| Feld | Wert |
|---|---|
dev@example.com |
|
| OTP | 123456 (fest in Entwicklung) |
Der OTP gilt nur für die in DEV_SEED_EMAIL konfigurierte Adresse und nur außerhalb von Production.
Enthalten sind zwei Versicherte (Debeka/DKV) und ein Beispiel-Vorgang.
Konto anlegen (Onboarding)
- E-Mail eingeben → Bestätigungscode anfordern
- OTP aus E-Mail (oder Server-Log ohne SMTP) eingeben
- Passkey auf diesem Gerät einrichten (empfohlen)
- Haushalt benennen → Versicherte erfassen
Weitere Passkeys (z. B. zweites Handy): Einstellungen → Passkeys.
E-Mail / Gmail (OTP-Versand)
Für echte OTP-Mails in .env eintragen (siehe .env.example):
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=ihr.name@gmail.com
SMTP_PASS=ihr-16-stelliges-app-passwort
SMTP_FROM=ihr.name@gmail.com
Bei Gmail: App-Passwort erstellen (2FA muss aktiv sein).
Ohne SMTP erscheint der Code im Terminal ([DEV EMAIL] …).
Docker (vollständig)
cp .env.example .env
docker compose --profile dev up
Mit Paperless:
docker compose --profile dev --profile paperless up
Produktion:
docker compose up --build
docker compose --profile prod up # mit Caddy
Dokument-Backend
| Modus | Konfiguration |
|---|---|
| local (Standard) | DOCUMENT_BACKEND=local |
| Paperless mitgeliefert | docker compose --profile paperless up + DOCUMENT_BACKEND=paperless |
| Externes Paperless | DOCUMENT_BACKEND=paperless, PAPERLESS_URL, PAPERLESS_TOKEN |
Umgebungsvariablen
Siehe .env.example (lokal) bzw. .env.unraid.example (Produktion).
Wichtige optionale Werte:
OPENROUTER_API_KEY— KI-Extraktion (global; alternativ pro Benutzer unter Einstellungen)OPENROUTER_MODEL— Default-Modell für die Extraktion (global; in den Einstellungen überschreibbar)DOCUMENT_BACKEND/PAPERLESS_*— Dokumentenablage
Deployment auf Unraid (Gitea CI/CD)
Dauerhaft laufen Postgres + App auf Unraid; Migrationen als Einmal-Job. Deploy per Gitea Actions über den vorhandenen Gitea-Runner mit Docker-Socket.
| Komponente | Wert |
|---|---|
| Gitea | https://gitea.elpatron.me |
| Repository | elpatron/pkv-beihilfe-tool |
| Runner | Gitea-Runner auf Unraid (Docker-Socket) |
| Appdata | /mnt/user/appdata/pkv-beihilfe |
| App-URL | https://tower.warbler-bearded.ts.net (Tailscale Serve; intern :3010) |
Architektur
Push auf main → Gitea Actions (ubuntu-latest auf Unraid)
→ Docker-Image bauen & in Gitea Container Registry pushen
→ docker-compose auf Unraid aktualisieren (Postgres + Migrate + App)
Workflow: .gitea/workflows/deploy-unraid.yml
Einmalige Einrichtung
1. Gitea Actions aktivieren
Im Repository: Einstellungen → Aktionen → Repository-Aktionen aktivieren.
2. Container-Registry aktivieren
In Gitea: Site-Administration → Packages (falls noch nicht aktiv) und im Repo unter Einstellungen → Pakete prüfen, dass Container-Pakete erlaubt sind.
3. Registry-Token als Repository-Secret
GITEA_TOKEN reicht für docker push nicht — ein Personal Access Token (PAT) ist nötig:
- Gitea → Einstellungen → Anwendungen → Zugriffstoken erstellen
- Scopes: mindestens
write:package(optionalread:package) - Im Repository: Einstellungen → Actions → Secrets →
REGISTRY_TOKENanlegen (Wert = PAT)
Der Workflow loggt sich mit Benutzer ${{ gitea.actor }} und diesem Secret ein.
4. Appdata auf Unraid vorbereiten
Auf Unraid als root (wurde bereits eingerichtet unter /mnt/user/appdata/pkv-beihilfe):
bash /mnt/user/appdata/pkv-beihilfe/repo/scripts/setup-unraid-appdata.sh \
/mnt/user/appdata/pkv-beihilfe/repo
Das legt .env mit zufälligem BETTER_AUTH_SECRET und DB-Passwort an sowie postgres/ und documents/ Volumes.
5. .env anpassen
Datei /mnt/user/appdata/pkv-beihilfe/.env bearbeiten:
BETTER_AUTH_URL/PASSKEY_RP_ID— Tailscale-Serve-Hostname (HTTPS), siehe.env.unraid.exampleSMTP_*— für OTP-Mails in Produktion empfohlen- optional
OPENROUTER_API_KEY/OPENROUTER_MODELfür KI - optional
DOCUMENT_BACKEND=paperless+PAPERLESS_URL=http://192.168.177.5:8000(bestehendes Paperless auf Unraid)
Vorlage: .env.unraid.example
6. Erstes Deployment
Push auf main startet den Workflow automatisch, oder manuell unter Aktionen → Deploy Unraid → Ausführen.
Manuelles Deployment
ssh root@192.168.177.5
export APP_IMAGE=gitea.elpatron.me/elpatron/pkv-beihilfe-tool:latest
export MIGRATE_IMAGE=gitea.elpatron.me/elpatron/pkv-beihilfe-tool-migrate:latest
/mnt/user/appdata/pkv-beihilfe/deploy.sh
Dateien
| Datei | Zweck |
|---|---|
docker-compose.unraid.yml |
Produktions-Stack (wird nach Appdata kopiert) |
scripts/deploy-unraid.sh |
Pull, Migration, Neustart |
scripts/setup-unraid-appdata.sh |
Ersteinrichtung Appdata |
scripts/dev.sh |
Lokaler Dev-Server (Postgres + Next.js) |
scripts/configure-gitea-runner-unraid.sh |
Runner-Label für Unraid-Deploy (einmalig) |
.gitea/workflows/deploy-unraid.yml |
CI/CD-Workflow |
Dockerfile (Targets runner, migrate) |
App- und Migrations-Image |
Hinweise
- Kein öffentlicher Port, kein Funnel. Zugriff nur im Tailnet über Tailscale Serve (
https://tower.warbler-bearded.ts.net→ App:3010). LAN-HTTP unter:3010bleibt intern, ist aber kein Secure Context. - Passkeys brauchen HTTPS (oder
http://localhost). Deshalb Serve statt WireGuard/LAN-IP — unterhttp://192.168.x.xmelden Browser „WebAuthn is not supported“. In der Appdata-.env:BETTER_AUTH_URL=https://tower.warbler-bearded.ts.netPASSKEY_RP_ID=tower.warbler-bearded.ts.net(Hostname ohne Schema und Port) Serve auf Unraid:tailscale serve --bg 3010(MagicDNS + HTTPS-Zertifikate im Tailscale-Admin).
- Backups: Unter Einstellungen → Sicherung den Haushalt als ZIP exportieren (Vorgänge + Belege). Zusätzlich
/mnt/user/appdata/pkv-beihilfe/postgresunddocuments/regelmäßig sichern (vollständiger Serverstand inkl. Login). - Der Runner nutzt den Host-Docker-Socket — Deploy-Schritte laufen über
docker run … docker:28.1-climit Volume/mnt/user/appdata/pkv-beihilfe.
Lizenz
Privates Projekt.