docs: Installationsanleitung Schritt fuer Schritt
Die README hatte Installationsabschnitte, aber keine durchgehende Anleitung fuer jemanden, der vor einem frischen Server sitzt. Genau da faengt diese hier an. Dreizehn Schritte vom leeren Server bis zur laufenden Anwendung, jeder mit einer Kontrolle am Ende - man geht nicht weiter, ohne geprueft zu haben, ob der letzte Schritt geklappt hat. Am Ende eine Abnahmeliste zum Abhaken, bevor echte Daten hineinkommen. Aufgenommen sind die Fallstricke, die tatsaechlich Zeit kosten: - AV-Vertrag klaeren, BEVOR der Server bestellt wird - Zeitzone setzen, sonst rechnet der Server in UTC und verschiebt Fristen - AUTH_URL muss exakt der aufgerufenen Adresse entsprechen - sonst landet die Anmeldung stumm wieder auf dem Formular, ohne Fehlermeldung. Das ist der Fehler, den man am laengsten sucht. - Der Proxy muss X-Forwarded-For weiterreichen, sonst zaehlt die Login-Sperre alle Zugriffe als denselben Anschluss. - Die beiden SQL-Skripte laufen nicht mit den Migrationen mit. Zwei Kontrollen sind keine Formalie, sondern Nachweise: Der Schutz des Audit-Logs wird geprueft, indem ein UPDATE ausdruecklich fehlschlagen MUSS. Und die Sicherung wird nicht nur angestossen, sondern einmal zurueckgespielt - solange noch nichts drinsteht, was weh tut. Der letzte Schritt bringt ENC_KEY_V1 in Sicherheit, an zwei Orte, von denen keiner der Server ist. Ein gestohlenes Backup ist ohne ihn wertlos - und ein verlorener Schluessel macht auch das eigene Backup wertlos. Das ist dieselbe Eigenschaft von zwei Seiten. Etappe: 8 Phase: Haertung
This commit is contained in:
@@ -15,6 +15,7 @@ eines Angehörigen: Fristen, Vorgänge, Belege, Vermögen, Sozialhilfe, Berichte
|
|||||||
## Inhalt
|
## Inhalt
|
||||||
|
|
||||||
- [Was die Anwendung kann](#was-die-anwendung-kann)
|
- [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)
|
- [Schnellstart zum Ausprobieren](#schnellstart-zum-ausprobieren)
|
||||||
- [Produktivbetrieb mit Docker Compose](#produktivbetrieb-mit-docker-compose)
|
- [Produktivbetrieb mit Docker Compose](#produktivbetrieb-mit-docker-compose)
|
||||||
- [Reverse Proxy](#reverse-proxy)
|
- [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
|
## Schnellstart zum Ausprobieren
|
||||||
|
|
||||||
Voraussetzungen: **Node.js ≥ 20.11**, **Docker** und **Git**.
|
Voraussetzungen: **Node.js ≥ 20.11**, **Docker** und **Git**.
|
||||||
@@ -113,6 +131,10 @@ laufender Antrag beim Bezirk Oberfranken mit acht Nachforderungen.
|
|||||||
|
|
||||||
## Produktivbetrieb mit Docker Compose
|
## 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
|
Das Compose-Setup bringt drei Dienste mit: die Anwendung, PostgreSQL und
|
||||||
MinIO als S3-kompatiblen Objektspeicher, dazu einen Sicherungsdienst.
|
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 test:e2e # Playwright: kritische Abläufe
|
||||||
npm run db:studio # Prisma Studio
|
npm run db:studio # Prisma Studio
|
||||||
npm run protokoll # Bauprotokoll aus der Git-Historie
|
npm run protokoll # Bauprotokoll aus der Git-Historie
|
||||||
|
npm run version -- patch # Version anheben, Changelog vorbereiten
|
||||||
```
|
```
|
||||||
|
|
||||||
Konventionen — verbindlich für alle Beiträge:
|
Konventionen — verbindlich für alle Beiträge:
|
||||||
|
|||||||
@@ -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=<aus Schritt 4>
|
||||||
|
|
||||||
|
# --- Anmeldung ---
|
||||||
|
AUTH_SECRET=<aus Schritt 4>
|
||||||
|
AUTH_URL=https://cockpit.deine-domain.de # exakt deine Adresse, mit https
|
||||||
|
AUTH_TRUST_HOST=true
|
||||||
|
|
||||||
|
# --- Verschlüsselung ---
|
||||||
|
ENC_KEY_V1=<aus Schritt 4>
|
||||||
|
ENC_KEY_AKTIV=1
|
||||||
|
INDEX_KEY=<aus Schritt 4>
|
||||||
|
|
||||||
|
# --- Objektspeicher ---
|
||||||
|
S3_ACCESS_KEY_ID=<aus Schritt 4>
|
||||||
|
S3_SECRET_ACCESS_KEY=<aus Schritt 4>
|
||||||
|
|
||||||
|
# --- Sicherung ---
|
||||||
|
SICHERUNG_PASSWORT=<aus Schritt 4>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **`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=<dein Mailpasswort>
|
||||||
|
SMTP_FROM="Betreuungs-Cockpit <cockpit@deine-domain.de>"
|
||||||
|
```
|
||||||
|
|
||||||
|
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 · <commit>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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_<datum>.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: <ausgegebener Wert>" https://cockpit.deine-domain.de/api/wartung > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kontrolle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-Wartung-Schluessel: <wert>" \
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user