diff --git a/README.md b/README.md index 33f691e..95cbb06 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ eines Angehörigen: Fristen, Vorgänge, Belege, Vermögen, Sozialhilfe, Berichte ## Inhalt - [Was die Anwendung kann](#was-die-anwendung-kann) +- [Die drei Dokumente, die du wirklich brauchst](#die-drei-dokumente-die-du-wirklich-brauchst) - [Schnellstart zum Ausprobieren](#schnellstart-zum-ausprobieren) - [Produktivbetrieb mit Docker Compose](#produktivbetrieb-mit-docker-compose) - [Reverse Proxy](#reverse-proxy) @@ -46,6 +47,23 @@ eines Angehörigen: Fristen, Vorgänge, Belege, Vermögen, Sozialhilfe, Berichte --- +## Die drei Dokumente, die du wirklich brauchst + +| Wofür | Wo | +|---|---| +| **Einen Server aufsetzen** — von der leeren Maschine bis zur laufenden Anwendung, mit Kontrolle nach jedem Schritt | [`docs/INSTALLATION.md`](docs/INSTALLATION.md) | +| **Täglich damit arbeiten** — die zwölf Handgriffe, der Jahresrhythmus, was tun wenn etwas schiefgeht | [`docs/ANLEITUNG.md`](docs/ANLEITUNG.md), in der Anwendung unter `/anleitung` | +| **Wissen, was sich ändert** — was neu ist, was du vor einem Update beachten musst | [`CHANGELOG.md`](CHANGELOG.md) | + +Dazu: [`docs/ROADMAP.md`](docs/ROADMAP.md) für das, was als Nächstes +kommt, und [`docs/BAUPROTOKOLL.md`](docs/BAUPROTOKOLL.md) für die Frage, +wie das hier entstanden ist. + +Der Rest dieser Datei ist die Kurzfassung für alle, die schon wissen, was +sie tun. + +--- + ## Schnellstart zum Ausprobieren Voraussetzungen: **Node.js ≥ 20.11**, **Docker** und **Git**. @@ -113,6 +131,10 @@ laufender Antrag beim Bezirk Oberfranken mit acht Nachforderungen. ## Produktivbetrieb mit Docker Compose +> **Zum ersten Mal?** Nimm die [Schritt-für-Schritt-Anleitung](docs/INSTALLATION.md). +> Sie fängt beim leeren Server an, hat nach jedem Abschnitt eine Kontrolle +> und endet mit einer Abnahmeliste. Was hier steht, ist die Kurzfassung. + Das Compose-Setup bringt drei Dienste mit: die Anwendung, PostgreSQL und MinIO als S3-kompatiblen Objektspeicher, dazu einen Sicherungsdienst. @@ -479,6 +501,7 @@ npm test # Vitest: Fachlogik npm run test:e2e # Playwright: kritische Abläufe npm run db:studio # Prisma Studio npm run protokoll # Bauprotokoll aus der Git-Historie +npm run version -- patch # Version anheben, Changelog vorbereiten ``` Konventionen — verbindlich für alle Beiträge: diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..701a0f0 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,799 @@ +# Installation Schritt für Schritt + +Von einem leeren Server bis zur laufenden Anwendung. Rechne mit **60 bis +90 Minuten**, wenn du es zum ersten Mal machst. + +Diese Anleitung setzt voraus, dass du dich per SSH auf einem Linux-Server +anmelden und Befehle eintippen kannst. Mehr nicht — jeder Schritt steht +ausgeschrieben da, und nach jedem Abschnitt steht eine **Kontrolle**, mit +der du prüfst, ob es geklappt hat, bevor du weitermachst. + +> Wenn du die Anwendung nur ausprobieren willst, ohne Server: Der +> Abschnitt *Schnellstart* in der [README](../README.md) bringt dich in +> zehn Minuten auf dem eigenen Rechner zum Laufen. Diese Anleitung hier +> ist für den echten Betrieb. + +--- + +## Inhalt + +- [Schritt 0 — Was du vorher brauchst](#schritt-0--was-du-vorher-brauchst) +- [Schritt 1 — Server vorbereiten](#schritt-1--server-vorbereiten) +- [Schritt 2 — Docker installieren](#schritt-2--docker-installieren) +- [Schritt 3 — Anwendung holen](#schritt-3--anwendung-holen) +- [Schritt 4 — Schlüssel erzeugen](#schritt-4--schlüssel-erzeugen) +- [Schritt 5 — Konfiguration ausfüllen](#schritt-5--konfiguration-ausfüllen) +- [Schritt 6 — Starten](#schritt-6--starten) +- [Schritt 7 — Datenbank vorbereiten](#schritt-7--datenbank-vorbereiten) +- [Schritt 8 — Dateiablage einrichten](#schritt-8--dateiablage-einrichten) +- [Schritt 9 — Reverse Proxy und TLS](#schritt-9--reverse-proxy-und-tls) +- [Schritt 10 — Ersteinrichtung im Browser](#schritt-10--ersteinrichtung-im-browser) +- [Schritt 11 — Sicherung prüfen](#schritt-11--sicherung-prüfen) +- [Schritt 12 — Wartungslauf einrichten](#schritt-12--wartungslauf-einrichten) +- [Schritt 13 — Schlüssel in Sicherheit bringen](#schritt-13--schlüssel-in-sicherheit-bringen) +- [Aktualisieren](#aktualisieren) +- [Wenn etwas nicht klappt](#wenn-etwas-nicht-klappt) +- [Abnahmeliste](#abnahmeliste) + +--- + +## Schritt 0 — Was du vorher brauchst + +**Ein Server in Deutschland oder mindestens in der EU.** Für eine Betreuung +reicht die kleinste Größe bequem: + +| | Minimum | Bequem | +|---|---|---| +| Arbeitsspeicher | 2 GB | 4 GB | +| Speicherplatz | 20 GB | 40 GB | +| Kerne | 2 | 2 | +| Betriebssystem | Debian 12 oder Ubuntu 24.04 | dasselbe | + +Anbieter mit Rechenzentren in Deutschland: Hetzner (Falkenstein, +Nürnberg), netcup (Nürnberg), IONOS (Karlsruhe). Ein Server für rund fünf +Euro im Monat genügt. + +> **Bevor du bestellst:** Kläre den **Auftragsverarbeitungsvertrag nach +> Art. 28 DSGVO**. Bei Hetzner findest du ihn im Kundenkonto unter +> *Verträge → AV-Vertrag*, bei anderen ähnlich. Ohne ihn ist der Betrieb +> formal rechtswidrig — und es dauert fünf Minuten. + +**Eine Domain oder Subdomain**, zum Beispiel `cockpit.deine-domain.de`. +Setz einen A-Record auf die IP-Adresse des Servers, bevor du anfängst; +DNS-Änderungen brauchen manchmal eine Stunde. + +**Etwas zum Aufschreiben.** Du erzeugst gleich Schlüssel, die du nicht +verlieren darfst. Ein Passwortmanager ist das Richtige. + +### Kontrolle + +```bash +# Von deinem Rechner aus - zeigt die IP deines Servers? +dig +short cockpit.deine-domain.de + +# Kommst du auf den Server? +ssh root@cockpit.deine-domain.de +``` + +--- + +## Schritt 1 — Server vorbereiten + +Alles Folgende läuft **auf dem Server**. + +```bash +# System aktuell bringen +apt update && apt upgrade -y + +# Werkzeuge, die wir brauchen +apt install -y git curl ca-certificates ufw + +# Zeitzone setzen - sonst rechnet der Server in UTC und +# verschiebt Fristen um einen Tag +timedatectl set-timezone Europe/Berlin +``` + +**Einen eigenen Benutzer anlegen.** Als `root` zu arbeiten ist unnötig +riskant: + +```bash +adduser --gecos "" cockpit +usermod -aG sudo cockpit + +# SSH-Schlüssel übernehmen, damit du dich weiter anmelden kannst +rsync --archive --chown=cockpit:cockpit ~/.ssh /home/cockpit/ +``` + +**Die Firewall einschalten.** Nur SSH, HTTP und HTTPS von außen: + +```bash +ufw allow OpenSSH +ufw allow 80/tcp +ufw allow 443/tcp +ufw --force enable +``` + +**SSH absichern** — Anmeldung nur mit Schlüssel, nicht mit Passwort. Nur +machen, wenn du dich sicher mit Schlüssel anmelden kannst: + +```bash +sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config +sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config +systemctl restart ssh +``` + +### Kontrolle + +```bash +timedatectl | grep "Time zone" # Europe/Berlin +ufw status # active, drei Regeln +``` + +Melde dich jetzt neu an, als `cockpit`: + +```bash +ssh cockpit@cockpit.deine-domain.de +``` + +--- + +## Schritt 2 — Docker installieren + +```bash +# Offizielles Paketverzeichnis von Docker eintragen +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/debian/gpg \ + | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +sudo chmod a+r /etc/apt/keyrings/docker.gpg + +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ +https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +sudo apt update +sudo apt install -y docker-ce docker-ce-cli containerd.io \ + docker-buildx-plugin docker-compose-plugin + +# Ohne sudo arbeiten dürfen +sudo usermod -aG docker $USER +``` + +> Bei **Ubuntu** ersetzt du in den beiden URLs `debian` durch `ubuntu`. + +**Melde dich ab und wieder an**, damit die Gruppenzugehörigkeit greift. + +### Kontrolle + +```bash +docker --version # Docker version 27.x oder neuer +docker compose version # Docker Compose version v2.x +docker run --rm hello-world # "Hello from Docker!" +``` + +Kommt bei `docker run` ein *permission denied*, hast du das Ab- und +Anmelden vergessen. + +--- + +## Schritt 3 — Anwendung holen + +```bash +sudo mkdir -p /opt/betreuungs-cockpit +sudo chown $USER:$USER /opt/betreuungs-cockpit + +git clone https://git.wilde-server.de/florian.wilde/betreuungs-cockpit.git \ + /opt/betreuungs-cockpit + +cd /opt/betreuungs-cockpit + +# Auf die letzte veröffentlichte Fassung wechseln, nicht auf den +# laufenden Entwicklungsstand +git checkout v1.0.0 +``` + +### Kontrolle + +```bash +ls docker-compose.yml Dockerfile .env.example # alle drei da +git describe --tags # v1.0.0 +``` + +--- + +## Schritt 4 — Schlüssel erzeugen + +Die Anwendung braucht vier Geheimnisse. Erzeuge sie **auf dem Server**, +nicht auf dem eigenen Rechner: + +```bash +docker run --rm node:22-alpine node -e " +const c = require('crypto'); +const b = n => c.randomBytes(n).toString('base64'); +console.log('AUTH_SECRET=' + b(32)); +console.log('ENC_KEY_V1=' + b(32)); +console.log('INDEX_KEY=' + b(32)); +console.log('POSTGRES_PASSWORD=' + b(24).replace(/[+/=]/g,'x')); +console.log('SICHERUNG_PASSWORT=' + b(24).replace(/[+/=]/g,'x')); +console.log('S3_ACCESS_KEY_ID=' + b(12).replace(/[+/=]/g,'x')); +console.log('S3_SECRET_ACCESS_KEY=' + b(24).replace(/[+/=]/g,'x')); +" +``` + +Kopier die Ausgabe in einen Texteditor. Du brauchst sie gleich. + +> **`ENC_KEY_V1` ist der wichtigste Wert der ganzen Installation.** Mit ihm +> sind Diagnosen, IBANs, Versicherungsnummern und alle Notizen +> verschlüsselt. Geht er verloren, sind diese Felder **unwiederbringlich** +> weg — auch aus jedem Backup. Es gibt keine Hintertür. +> +> Schritt 13 sagt dir, wohin damit. Verlier ihn bis dahin nicht. + +--- + +## Schritt 5 — Konfiguration ausfüllen + +```bash +cp .env.example .env +nano .env +``` + +Diese Werte **musst** du setzen: + +```bash +# --- Datenbank --- +POSTGRES_PASSWORD= + +# --- Anmeldung --- +AUTH_SECRET= +AUTH_URL=https://cockpit.deine-domain.de # exakt deine Adresse, mit https +AUTH_TRUST_HOST=true + +# --- Verschlüsselung --- +ENC_KEY_V1= +ENC_KEY_AKTIV=1 +INDEX_KEY= + +# --- Objektspeicher --- +S3_ACCESS_KEY_ID= +S3_SECRET_ACCESS_KEY= + +# --- Sicherung --- +SICHERUNG_PASSWORT= +``` + +> **`AUTH_URL` muss exakt der Adresse entsprechen**, unter der du die +> Anwendung später aufrufst — mit `https://`, ohne Schrägstrich am Ende. +> Steht dort etwas anderes, wird das Sitzungs-Cookie für die falsche +> Herkunft gesetzt: Die Anmeldung landet stumm wieder auf dem Formular, +> ohne Fehlermeldung. Das ist der Fehler, den man am längsten sucht. + +Optional, aber empfohlen — Fristerinnerungen per E-Mail: + +```bash +SMTP_HOST=mail.deine-domain.de +SMTP_PORT=587 +SMTP_USER=cockpit@deine-domain.de +SMTP_PASSWORD= +SMTP_FROM="Betreuungs-Cockpit " +``` + +Speichern mit `Strg+O`, `Enter`, `Strg+X`. + +**Die Datei absichern** — sie enthält alle Schlüssel: + +```bash +chmod 600 .env +``` + +### Kontrolle + +```bash +# Sind alle Pflichtwerte gefüllt? Zeigt nur die Namen, nicht die Werte. +grep -E '^(POSTGRES_PASSWORD|AUTH_SECRET|AUTH_URL|ENC_KEY_V1|INDEX_KEY|S3_ACCESS_KEY_ID|S3_SECRET_ACCESS_KEY|SICHERUNG_PASSWORT)=' .env \ + | sed 's/=.*/= gesetzt/' +``` + +Acht Zeilen müssen erscheinen. Fehlt eine, startet die Anwendung nicht — +und sagt dir beim Start auch, welche. + +--- + +## Schritt 6 — Starten + +```bash +cd /opt/betreuungs-cockpit + +# Git-Stand mitgeben, damit später sichtbar ist, was läuft +GIT_COMMIT=$(git rev-parse --short HEAD) \ +BUILD_ZEIT=$(date -Iseconds) \ +docker compose up -d --build +``` + +Der erste Bau dauert **fünf bis zehn Minuten** — Tesseract und die +deutschen Sprachdaten wollen heruntergeladen werden. Danach geht es +schnell. + +### Kontrolle + +```bash +docker compose ps +``` + +Vier Dienste, alle `running`, die Datenbank `healthy`: + +``` +NAME STATUS +betreuungs-cockpit-app-1 Up (healthy) +betreuungs-cockpit-db-1 Up (healthy) +betreuungs-cockpit-minio-1 Up (healthy) +betreuungs-cockpit-sicherung-1 Up +``` + +```bash +# Antwortet die Anwendung? +curl -s http://127.0.0.1:3000/api/gesundheit +# {"status":"bereit","version":"1.0.0","commit":"..."} +``` + +Kommt hier nichts, sieh in die Logs: + +```bash +docker compose logs app --tail 50 +``` + +--- + +## Schritt 7 — Datenbank vorbereiten + +Die **Migrationen laufen beim Start automatisch mit**. Zwei SQL-Skripte +kommen einmalig von Hand dazu — sie richten die Volltextsuche ein und +machen das Audit-Log unveränderlich: + +```bash +docker compose exec app node node_modules/prisma/build/index.js db execute \ + --schema prisma/schema.prisma --file prisma/sql/volltextsuche.sql + +docker compose exec app node node_modules/prisma/build/index.js db execute \ + --schema prisma/schema.prisma --file prisma/sql/audit-schutz.sql +``` + +### Kontrolle + +Prüf, dass der Schutz des Audit-Logs wirklich greift. Der folgende Befehl +**muss** mit einem Fehler enden: + +```bash +docker compose exec db psql -U cockpit betreuungs_cockpit \ + -c "UPDATE audit_log SET entitaet='x';" +``` + +Erwartete Ausgabe: + +``` +ERROR: Das Audit-Log ist unveraenderlich. UPDATE auf audit_log ist nicht zulaessig. +``` + +Kommt stattdessen `UPDATE 0`, ist das Skript nicht gelaufen. Führ es +erneut aus. + +--- + +## Schritt 8 — Dateiablage einrichten + +Der Objektspeicher braucht einen Ablagebereich für die Dokumente: + +```bash +source .env + +docker compose exec minio mc alias set lokal \ + http://localhost:9000 "$S3_ACCESS_KEY_ID" "$S3_SECRET_ACCESS_KEY" + +docker compose exec minio mc mb lokal/betreuungs-cockpit +``` + +### Kontrolle + +```bash +docker compose exec minio mc ls lokal +# [...] betreuungs-cockpit/ +``` + +> Wenn du statt MinIO einen externen Objektspeicher in der EU benutzt +> (z. B. Hetzner Object Storage), lass den `minio`-Dienst in der +> `docker-compose.yml` weg und zeig mit `S3_ENDPOINT` dorthin. **Auch +> dafür brauchst du einen AV-Vertrag.** + +--- + +## Schritt 9 — Reverse Proxy und TLS + +Die Anwendung horcht bewusst nur auf `127.0.0.1:3000` und bringt **keinen +eigenen Proxy** mit. Von außen erreichbar wird sie über den Proxy auf dem +Server, der auch das Zertifikat besorgt. + +### Wenn noch kein Proxy da ist: Caddy + +Caddy holt Let's-Encrypt-Zertifikate von selbst und erneuert sie: + +```bash +sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \ + | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \ + | sudo tee /etc/apt/sources.list.d/caddy-stable.list +sudo apt update && sudo apt install -y caddy +``` + +Dann `/etc/caddy/Caddyfile` ersetzen: + +```bash +sudo tee /etc/caddy/Caddyfile > /dev/null <<'ENDE' +cockpit.deine-domain.de { + encode zstd gzip + reverse_proxy 127.0.0.1:3000 + + # Fotos von Behördenschreiben können groß sein + request_body { + max_size 30MB + } +} +ENDE + +sudo systemctl reload caddy +``` + +### Wenn schon nginx läuft + +```nginx +server { + listen 443 ssl http2; + server_name cockpit.deine-domain.de; + + ssl_certificate /etc/letsencrypt/live/cockpit.deine-domain.de/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/cockpit.deine-domain.de/privkey.pem; + + client_max_body_size 30M; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +``` + +Das Zertifikat holst du mit `sudo certbot --nginx -d cockpit.deine-domain.de`. + +> Der Proxy **muss** `X-Forwarded-For` weiterreichen. Die Anwendung +> braucht die echte Adresse für die Login-Sperre und für das Audit-Log +> (dort nur als Hash). Ohne den Kopf zählt sie alle Zugriffe als denselben +> Anschluss. + +### Kontrolle + +Von deinem eigenen Rechner aus: + +```bash +curl -sI https://cockpit.deine-domain.de/anmelden | head -5 +``` + +Erwartet: `HTTP/2 200`, dazu die Kopfzeilen +`content-security-policy`, `x-frame-options: DENY` und +`strict-transport-security`. + +--- + +## Schritt 10 — Ersteinrichtung im Browser + +Ruf `https://cockpit.deine-domain.de` auf. Ein Assistent führt dich durch +drei Schritte: Zugang anlegen, zweiten Faktor einrichten, betreute Person +erfassen. + +Die Einzelheiten stehen in der [Bedienungsanleitung](ANLEITUNG.md), +Abschnitt *Der erste Tag*. Drei Dinge sind hier wichtig: + +1. **Die Seite `/ersteinrichtung` sperrt sich selbst**, sobald ein Zugang + existiert. Niemand kann sich danach einen zweiten Administrator anlegen. +2. **Die Wiederherstellungscodes werden genau einmal angezeigt.** Druck sie + aus, bevor du weiterklickst. +3. **Das Bestellungsdatum** muss stimmen — daraus entstehen alle + gesetzlichen Fristen. + +Zum Schluss: **Fristen → Pflichtfristen ableiten**. + +### Kontrolle + +- Du kommst mit E-Mail, Passwort und dem Code aus der App herein. +- Unter **Fristen** stehen Einträge mit Paragraphenbezug. +- Unter **Protokoll** siehst du deine eigene Anmeldung. +- Unten links in der Seitenleiste steht `Version 1.0.0 · `. + +--- + +## Schritt 11 — Sicherung prüfen + +Die tägliche Sicherung läuft ab jetzt um **03:15 Uhr**. Warte nicht bis +morgen — stoß sie einmal von Hand an: + +```bash +cd /opt/betreuungs-cockpit +docker compose exec sicherung /bin/sh /sicherung.sh +``` + +Erwartete Ausgabe, in dieser Reihenfolge: + +``` +Sicherung beginnt: /backups/cockpit_2026-08-22_1430.dump.enc +Sicherung fertig (1.2M) +Selbstpruefung bestanden. +``` + +Die **Selbstprüfung** ist der eigentliche Punkt: Das Skript entschlüsselt +die eben geschriebene Datei sofort wieder. Schlägt das fehl, endet der +Lauf mit einem Fehler statt mit einem stillen Scheinerfolg. + +### Und jetzt einmal zurückspielen + +Ein Backup, das nie zurückgespielt wurde, ist kein Backup. Probier es +gleich aus, solange nichts drinsteht, was weh tut: + +```bash +# Testdatenbank neben der echten anlegen +docker compose exec db createdb -U cockpit probe + +# Zurückspielen +docker compose exec -e PGDATABASE=probe -e SICHERUNG_OHNE_RUECKFRAGE=ja \ + sicherung /bin/sh /wiederherstellung.sh /backups/cockpit_.dump.enc + +# Stichprobe +docker compose exec db psql -U cockpit probe \ + -c "SELECT vorname, nachname FROM betreute;" + +# Aufräumen +docker compose exec db dropdb -U cockpit probe +``` + +### Kontrolle + +```bash +ls -lh backups/ +# cockpit_....dump.enc und cockpit_....dump.enc.sha256 +``` + +> **Was das Backup nicht enthält:** den Schlüssel `ENC_KEY_V1` und die +> Dateien im Objektspeicher. Für die Dateien: +> +> ```bash +> docker compose exec minio mc mirror lokal/betreuungs-cockpit /backups/dokumente +> ``` +> +> Und kopier die Sicherungen regelmäßig **weg vom Server** — ein Backup, +> das neben dem Original liegt, hilft bei einem Serverausfall nicht. + +--- + +## Schritt 12 — Wartungslauf einrichten + +Einmal täglich: Fristerinnerungen verschicken, Papierkorb leeren, alte +Anmeldeversuche entfernen. + +```bash +# Schlüssel für den Endpunkt ausrechnen +source .env +docker compose exec app node -e " +console.log(require('crypto').createHmac('sha256', process.env.AUTH_SECRET) + .update('wartung').digest('base64url')) +" +``` + +Den ausgegebenen Wert in einen cron-Eintrag: + +```bash +crontab -e +``` + +Diese Zeile ans Ende: + +```cron +30 4 * * * curl -fsS -H "X-Wartung-Schluessel: " https://cockpit.deine-domain.de/api/wartung > /dev/null +``` + +### Kontrolle + +```bash +curl -s -H "X-Wartung-Schluessel: " \ + https://cockpit.deine-domain.de/api/wartung +# {"erinnerungen":0,"papierkorbGeleert":0,...,"fehler":[]} +``` + +Ohne gültigen Schlüssel antwortet die Route mit **404**, nicht mit 401 — +wer die Adresse errät, soll nicht einmal erfahren, dass es sie gibt. + +--- + +## Schritt 13 — Schlüssel in Sicherheit bringen + +Der letzte Schritt, und der wichtigste. + +**`ENC_KEY_V1` gehört an zwei Orte, keiner davon ist dieser Server:** + +1. In deinen **Passwortmanager**. +2. **Ausgedruckt** zu deinen Betreuungsunterlagen, im Ordner beim + Betreuerausweis. + +Dasselbe gilt für `SICHERUNG_PASSWORT` — ohne das nützt dir die +verschlüsselte Sicherungsdatei nichts. + +```bash +# Zum Abschreiben oder Kopieren +grep -E '^(ENC_KEY_V1|SICHERUNG_PASSWORT)=' .env +``` + +**Warum so umständlich?** Weil der Schlüssel bewusst nicht in der +Datenbank liegt. Ein gestohlenes Backup ist damit wertlos — und ein +verlorener Schlüssel macht auch dein eigenes Backup wertlos. Beides ist +dieselbe Eigenschaft, von zwei Seiten betrachtet. + +Denk an die letzten Pflichten: + +- **AV-Vertrag** mit Hoster und Objektspeicher abgeschlossen? +- [`verarbeitungsverzeichnis.md`](verarbeitungsverzeichnis.md) ausgefüllt? +- [`datenschutzhinweise.md`](datenschutzhinweise.md) ausgefüllt und der + betreuten Person ausgehändigt oder vorgelesen? + +--- + +## Aktualisieren + +```bash +cd /opt/betreuungs-cockpit + +# 1. IMMER zuerst sichern - eine Migration lässt sich nicht zurückrollen +docker compose exec sicherung /bin/sh /sicherung.sh + +# 2. Was ändert sich? Lies den Changelog, bevor du ziehst +git fetch --tags +git log --oneline HEAD..origin/main + +# 3. Auf die neue Fassung wechseln +git checkout v1.1.0 + +# 4. Neu bauen und starten +GIT_COMMIT=$(git rev-parse --short HEAD) \ +BUILD_ZEIT=$(date -Iseconds) \ +docker compose up -d --build +``` + +Die Migrationen laufen beim Start mit. **Sagt der Changelog, dass sich ein +SQL-Skript geändert hat, führ die beiden Befehle aus Schritt 7 erneut aus** — +sie sind so gebaut, dass mehrfaches Ausführen nichts kaputtmacht. + +### Kontrolle + +```bash +curl -s https://cockpit.deine-domain.de/api/gesundheit +# version muss die neue sein +``` + +Steht dort noch die alte Version, läuft der alte Behälter weiter — dann +hat der Bau nicht durchgezogen. Sieh in `docker compose logs app`. + +--- + +## Wenn etwas nicht klappt + +### Ein Dienst startet nicht + +```bash +docker compose ps +docker compose logs app --tail 100 +docker compose logs db --tail 50 +``` + +Fehlt eine Umgebungsvariable, sagt die Anwendung beim Start, welche. + +### „Umgebungsvariable ENC_KEY_V1 fehlt" + +Die `.env` liegt nicht neben der `docker-compose.yml`, oder die Zeile ist +leer. Prüf mit dem Befehl aus Schritt 5. + +### Die Anmeldung landet wieder auf dem Formular, ohne Fehlermeldung + +`AUTH_URL` stimmt nicht mit der aufgerufenen Adresse überein. Häufig: +`http` statt `https`, oder ein Schrägstrich am Ende. Nach dem Ändern: + +```bash +docker compose up -d +``` + +### „Zu viele Fehlversuche" + +Nach fünf Fehlversuchen wird für eine Viertelstunde gesperrt — auch dich. +Warte ab, oder setz die Zähler zurück: + +```bash +docker compose exec db psql -U cockpit betreuungs_cockpit \ + -c "DELETE FROM login_versuch WHERE erfolg = false;" +``` + +### Der Code aus der App wird nicht angenommen + +Die Uhr des Telefons geht falsch. TOTP-Codes hängen an der Uhrzeit. Stell +die automatische Zeitsynchronisierung ein und prüf, dass auch der Server +richtig geht: + +```bash +timedatectl +``` + +### Die Texterkennung findet nichts + +```bash +docker compose exec app tesseract --list-langs +# deu muss dabei sein +``` + +Fehlt sie, ist das Abbild nicht vollständig gebaut: +`docker compose build --no-cache app`. + +### Uploads schlagen bei großen Dateien fehl + +Der Proxy schneidet ab. Bei Caddy `request_body max_size`, bei nginx +`client_max_body_size` — beide auf mindestens 30 MB. + +### Die Datenbank ist voll + +```bash +df -h +docker system prune -a --volumes # ACHTUNG: entfernt ungenutzte Volumes +``` + +Vorher prüfen, was da ist. `docker system prune` ohne `--volumes` ist der +sichere Anfang. + +### Ganz von vorn + +```bash +docker compose down -v # ACHTUNG: löscht ALLE Daten +docker compose up -d --build +``` + +Das `-v` entfernt die Datenbank. Nur, wenn du das wirklich willst. + +--- + +## Abnahmeliste + +Hak das durch, bevor du echte Daten einträgst: + +- [ ] AV-Vertrag mit dem Hoster abgeschlossen +- [ ] AV-Vertrag für den Objektspeicher abgeschlossen (falls extern) +- [ ] Server steht in Deutschland oder mindestens in der EU +- [ ] Zeitzone auf `Europe/Berlin` +- [ ] Firewall aktiv, nur 22, 80 und 443 offen +- [ ] SSH-Anmeldung nur mit Schlüssel +- [ ] `https://` funktioniert, Zertifikat gültig +- [ ] `.env` hat Rechte `600` +- [ ] Alle vier Dienste laufen, Datenbank `healthy` +- [ ] `/api/gesundheit` antwortet mit der erwarteten Version +- [ ] **Audit-Schutz greift** — `UPDATE audit_log` endet mit Fehler +- [ ] Bucket im Objektspeicher angelegt +- [ ] Zweiter Faktor eingerichtet +- [ ] **Wiederherstellungscodes ausgedruckt** und weggelegt +- [ ] Bestellungsdatum eingetragen, Pflichtfristen abgeleitet +- [ ] Sicherung von Hand angestoßen, Selbstprüfung bestanden +- [ ] Sicherung **einmal zurückgespielt** und geprüft +- [ ] Sicherungen werden vom Server weg kopiert +- [ ] Wartungslauf im cron eingetragen und einmal getestet +- [ ] **`ENC_KEY_V1` im Passwortmanager und ausgedruckt** +- [ ] `SICHERUNG_PASSWORT` ebenso +- [ ] Verarbeitungsverzeichnis ausgefüllt +- [ ] Datenschutzhinweise ausgefüllt und ausgehändigt + +Ist alles abgehakt, kannst du anfangen. Die +[Bedienungsanleitung](ANLEITUNG.md) sagt dir, wie.