syncova-backup/docs/installation.md
Jerrit Fritzsche 4762fa29c3
Some checks failed
CI / Backend (Go) (push) Failing after 31s
CI / Frontend (React/TypeScript) (push) Successful in 34s
CI / Sicherheitsprüfungen (push) Successful in 24s
Fehlervorlage und ein Diagnoseskript, das sie fuellt
Eine Vorlage allein bringt wenig — sie stellt Fragen, die der Meldende meist
nicht beantworten kann. Deshalb zwei Teile:

diagnose.sh sammelt in einem Zug, was zur Analyse gebraucht wird: Fassungen
aller acht Programme, Betriebssystem, Container ja/nein, Dateisystem des
Repositorys, PostgreSQL-Fassung, Schemastand, Dienstzustand, Gesundheitsbericht
(der auch bei 503 den vollstaendigen Bericht traegt), Bestand, die letzten nicht
erfolgreichen Laeufe mit Fehlercode UND Fehlerklasse, die gemessene
Durchsetzungsstufe und die letzten Fehlerzeilen. Es liest nur.

Geheimnisse kommen nicht hinein, und der Weg dahin ist umgekehrt: Es gibt eine
Liste der Werte, die gezeigt werden duerfen. Eine Sperrliste vergaesse den
naechsten neuen Wert. Zusaetzlich werden die tatsaechlichen Geheimnisse gelesen
und aus JEDER Ausgabe entfernt — auch aus Protokollzeilen, in die sie auf einem
unvorhergesehenen Weg geraten sind. Real geprueft: weder Datenbankpasswort noch
Schluessel noch Administratorpasswort stehen im Bericht.

Die Vorlage beginnt mit sieben Faellen, die wie ein Fehler aussehen und gewolltes
Verhalten sind — "advisory" statt "filesystem", ein Teilfehler, "geloescht aber
nichts frei", 503 mit vollstaendigem Bericht. Das ist keine Abwehr, sondern
spart beiden Seiten einen halben Tag. Pflichtfelder sind Beobachtung, Erwartung,
Schritte, Bereich, Datenrisiko, Haeufigkeit und der Diagnosebericht; Fehlercode
und request_id stehen eigens da, weil sie die beiden wertvollsten Angaben sind.

Beim Erproben zwei Funde:

- Die Installationsanleitung verlangte PostgreSQL 17. setup.sh installiert auf
  Debian 12 aber 15 — und alles lief, bis hin zu einem echten Sicherungslauf.
  Die Anforderung lautet jetzt 15 (geprueft gegen 17 in CI und Entwicklung,
  gegen 15 auf Debian 12), und setup.sh lehnt aeltere Fassungen ab statt sie
  stillschweigend zu nehmen.
- Ein Repository auf der Platte, das nicht in der Control Plane eingetragen ist,
  faellt niemandem auf: Die Sicherung laeuft nie, weil der Server das Ziel nicht
  kennt. Der Bericht benennt diesen Fall jetzt ausdruecklich.

Gegen das echte v1.0.0-rc1-Paket gefahren: Installation, erzeugte Stoerung
(ALL_SOURCES_FAILED / source), Bericht zeigt Code, Klasse, "overlayfs" und
"nie gemessen".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 15:58:00 +02:00

335 lines
11 KiB
Markdown

# Installation
Diese Anleitung führt von einem leeren Server zu einer Anlage, die sichert und
wiederherstellt. Sie beschreibt den Serverteil; die Agenten stehen in
[agent-installation.md](agent-installation.md).
## Voraussetzungen
| | |
| --- | --- |
| Betriebssystem | Linux (amd64 oder arm64). Der Server wird für Windows **nicht** ausgeliefert |
| PostgreSQL | **15 oder neuer**, erreichbar vom Server. Geprüft gegen 17 (Entwicklung und CI) und 15 (Debian 12) |
| Speicher für das Repository | eigener Datenträger oder eigene Freigabe — nicht dasselbe Gerät wie die zu sichernden Daten |
| Reverse Proxy | optional, aber empfohlen, sofern der Dienst nicht selbst TLS bedient |
**Das Repository gehört nicht auf denselben Datenträger wie die Quelle.** Ein
Datenträgerausfall nähme sonst Original und Sicherung gemeinsam mit. Das ist
keine Feinheit der Einrichtung, sondern der Zweck der Übung.
## Der kurze Weg: setup.sh
Das Paket bringt ein Einrichtungsskript mit. Es geht genau die Schritte dieser
Anleitung, und was es angelegt hat, baut es bei einem Abbruch wieder zurück.
```bash
tar -xzf syncova-<version>-linux-amd64.tar.gz
cd syncova-<version>-linux-amd64
sudo ./setup.sh
```
Es fragt nach Datenbank, Repository und dem ersten Administrator, installiert
PostgreSQL auf Wunsch mit, erzeugt den Verschlüsselungsschlüssel und richtet den
Dienst ein. Für einen unbeaufsichtigten Lauf: `sudo ./setup.sh --unbeaufsichtigt`
mit den Werten aus der Umgebung (`./setup.sh --hilfe` zeigt sie).
Später aktualisieren: `sudo ./update.sh` — es sichert vorher Datenbank und
Konfiguration und nimmt sich selbst zurück, wenn der Dienst danach nicht
hochkommt. Entfernen: `sudo ./uninstall.sh` — es lässt Datenbank, Repository
und Konfiguration liegen, sofern man nicht ausdrücklich etwas anderes verlangt.
**Eine bestehende Installation überschreibt `setup.sh` nicht.** Es bricht ab und
verweist auf `update.sh`; der Unterschied ist, dass ein Update vorher sichert.
Der Rest dieses Dokuments beschreibt dieselben Schritte von Hand — für alle, die
wissen wollen, was das Skript tut, oder die davon abweichen müssen.
## 1. Paket auspacken
```bash
tar -xzf syncova-<version>-linux-amd64.tar.gz
cd syncova-<version>-linux-amd64
# Prüfsummen des Inhalts kontrollieren
shasum -a 256 -c SHA256SUMS
./bin/syncova-api --version
```
Die Versionsabfrage braucht keine Konfiguration. Sie ist der schnellste Weg
festzustellen, welche Fassung auf einem Server liegt.
Der Paketinhalt:
```text
bin/ Programme
web/ Weboberfläche (statische Dateien)
migrations/ SQL-Migrationen zum Nachlesen (sie stecken zusätzlich in den Programmen)
docs/ diese Dokumentation
deployment/ Beispiel für die Entwicklungsumgebung und die systemd-Einheit des Agenten
```
## 2. Datenbank anlegen
```sql
CREATE USER syncova WITH PASSWORD '<starkes-passwort>';
CREATE DATABASE syncova OWNER syncova;
```
Syncova legt das Schema **nicht** beim Start an. Das ist Absicht: Ein Dienst,
der beim Hochfahren stillschweigend das Schema ändert, macht aus einem
versehentlichen Neustart eine Migration.
## 3. Verschlüsselungsschlüssel erzeugen
```bash
./bin/syncova-admin generate-key
```
Ausgegeben wird ein Wert der Form `v1:<base64>`.
> **Ohne diesen Schlüssel sind die abgelegten Geheimnisse verloren** — MFA-Geheimnisse,
> Zugangsdaten der Virtualisierungsverbünde und die Datenschlüssel der
> Repositories. Er gehört an einen Ort **außerhalb** dieser Anlage: in einen
> Passwortspeicher oder einen Tresor. In ein Backup, das Syncova selbst
> erzeugt, gehört er ausdrücklich nicht — dann bräuchte man ihn, um an ihn
> heranzukommen.
## 4. Konfiguration
Syncova liest ausschließlich Umgebungsvariablen; eine Konfigurationsdatei gibt
es nicht. Alle tragen das Präfix `SYNCOVA_`.
```bash
# /etc/syncova/syncova.env — nur für root lesbar (chmod 0600)
SYNCOVA_ENV=production
SYNCOVA_DB_HOST=127.0.0.1
SYNCOVA_DB_PORT=5432
SYNCOVA_DB_NAME=syncova
SYNCOVA_DB_USER=syncova
SYNCOVA_DB_PASSWORD=<passwort>
SYNCOVA_DB_SSLMODE=require
SYNCOVA_ENCRYPTION_KEYS=v1:<base64-schluessel>
SYNCOVA_ENCRYPTION_CURRENT_KEY=v1
SYNCOVA_HTTP_LISTEN_ADDRESS=127.0.0.1:8080
SYNCOVA_LOG_LEVEL=info
SYNCOVA_LOG_FORMAT=json
```
Die wichtigsten weiteren Werte:
| Variable | Vorgabe | Wozu |
| --- | --- | --- |
| `SYNCOVA_HTTP_TLS_CERT_FILE` / `_KEY_FILE` | leer | TLS im Dienst selbst. **Nur eines von beiden zu setzen verweigert den Start** — sonst liefe der Dienst im Klartext, obwohl der Betreiber Verschlüsselung eingerichtet zu haben glaubt |
| `SYNCOVA_HTTP_REQUESTS_PER_MINUTE` | 600 | allgemeine Ratenbegrenzung |
| `SYNCOVA_AUTH_REQUIRE_MFA_FOR_PRIVILEGED_USERS` | false | zweiter Faktor für Konten mit Benutzer-, Rollen- oder Sicherheitsrechten |
| `SYNCOVA_RESTORE_ALLOWED_ROOTS` | leer | begrenzt Wiederherstellungsziele auf bestimmte Verzeichnisbäume |
**Lauscht der Dienst ohne TLS auf allen Schnittstellen, warnt er beim Start in
Großbuchstaben.** Der Betrieb hinter einem Reverse Proxy ist der Normalfall und
völlig in Ordnung — solange der Dienst dann nur lokal erreichbar ist.
## 5. Schema anlegen
```bash
set -a; . /etc/syncova/syncova.env; set +a
./bin/syncova-migrate up
./bin/syncova-migrate status
```
Passt das Schema später nicht zur Programmversion, verweigert `syncova-api` den
Start mit einer erklärenden Meldung. Das ist kein Schikane, sondern der Schutz
davor, dass eine neue Fassung auf ein altes Schema schreibt.
## 6. Ersten Administrator anlegen
```bash
./bin/syncova-admin create-admin --username admin
```
Das Passwort wird abgefragt und wiederholt — es steht **nicht** in einer
Umgebungsvariablen und nicht in den Aufrufparametern. Beides landete sonst in
der Prozessliste und in der Shell-Historie.
In einem Skript geht es über die Standardeingabe:
```bash
printf '%s\n%s\n' "$PASSWORD" "$PASSWORD" | ./bin/syncova-admin create-admin --username admin
```
**Es gibt kein Standardkonto im Programm.** Ein ausgeliefertes Kennwort wäre
bekannt, sobald es einmal jemand nachschlägt.
Das Kommando legt nur den **ersten** Administrator an; danach entstehen weitere
Konten über die Oberfläche oder die API.
## 7. Dienst einrichten
```ini
# /etc/systemd/system/syncova-api.service
[Unit]
Description=Syncova Control Plane
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=syncova
Group=syncova
EnvironmentFile=/etc/syncova/syncova.env
ExecStart=/opt/syncova/bin/syncova-api
Restart=on-failure
RestartSec=5
# Härtung: Der Dienst braucht Netz und sein Repository, sonst nichts.
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/srv/syncova-repository
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
```
`AF_UNIX` gehört in die Liste: Mit `systemd-resolved` läuft die Namensauflösung
über einen Unix-Socket. Ohne diesen Eintrag findet der Dienst seine Datenbank
nicht und meldet einen Netzfehler, während das Netz einwandfrei arbeitet.
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now syncova-api
curl -s http://127.0.0.1:8080/health/ready
```
## 8. Oberfläche ausliefern
Die Oberfläche ist ein Satz statischer Dateien unter `web/`. Sie braucht einen
**SPA-Fallback**, sonst ergibt ein Neuladen auf `/recovery-points` einen 404:
```nginx
server {
listen 443 ssl;
server_name syncova.example;
root /opt/syncova/web;
location / {
try_files $uri /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
```
## 9. Erstes Repository
Ein Repository entsteht **auf einem Datenträger**, nicht in einer
Datenbankzeile:
```bash
sudo install -d -o syncova -g syncova -m 0700 /srv/syncova-repository
sudo -u syncova ./bin/syncova-repo create \
--path /srv/syncova-repository --name "Hauptziel" --hardened
```
`--hardened` setzt den Aufbewahrungsschutz: Manifeste, Blöcke, Schutzvermerke,
Descriptor und Datenschlüssel bekommen das Unveränderlich-Kennzeichen des
Dateisystems. **Ein so angelegtes Repository lässt sich nicht mit `rm -rf`
entfernen** — auch nicht von root, ohne den Schutz vorher aufzuheben.
Danach wird es in der Control Plane eingetragen:
```bash
curl -X POST https://syncova.example/api/v1/repositories \
-H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
-d '{"name":"Hauptziel","location":"/srv/syncova-repository"}'
```
Der Endpunkt **legt nichts an, sondern übernimmt**: Er öffnet das vorhandene
Repository und liest dessen Kennung aus dem Descriptor. Liegt dort keines,
lehnt er ab, statt einen Eintrag ohne Ablage dahinter zu erzeugen.
Anschließend die Durchsetzungsstufe messen — sie wird gemessen, nicht behauptet:
```bash
curl -X POST https://syncova.example/api/v1/repositories/<id>/enforcement/measure \
-H "Authorization: Bearer <token>"
```
## 10. Erster Auftrag
Über die Oberfläche (Backup-Assistent, zehn Schritte) oder die API:
```bash
curl -X POST https://syncova.example/api/v1/jobs \
-H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" -d '{
"name": "Dateiserver täglich",
"repository_id": "<repository>",
"schedule": {"type": "daily", "time": "02:00", "time_zone": "Europe/Berlin"},
"sources": [{"type": "filesystem", "id": "/srv/daten", "name": "Daten"}]
}'
```
**Ohne Zeitzone rechnet der Server in UTC**, nicht in der Ortszeit des Servers.
Sonst liefe dieselbe Konfiguration auf zwei Servern zu verschiedenen Zeiten.
## 11. Der Schritt, den die meisten auslassen
```bash
# Eine Wiederherstellung durchführen, bevor man sie braucht
curl -X POST https://syncova.example/api/v1/verification \
-H "Authorization: Bearer <token>" -H 'Content-Type: application/json' \
-d '{"backup_id":"<id>","verification_type":"restore_test"}'
```
Erst danach steigt ein Wiederherstellungspunkt auf `recoverable`. Alles davor
ist ein Indiz, kein Nachweis.
## Aktualisierung auf eine neue Fassung
```bash
# 1. Datenbank sichern — ein Rollback ersetzt keine Sicherung
pg_dump -Fc syncova > syncova-vor-update.dump
# 2. Dienst anhalten
sudo systemctl stop syncova-api
# 3. Programme austauschen
sudo tar -xzf syncova-<neu>-linux-amd64.tar.gz -C /opt/syncova --strip-components=1
# 4. Migrationen anwenden
sudo -u syncova /opt/syncova/bin/syncova-migrate up
# 5. Starten und nachsehen
sudo systemctl start syncova-api
curl -s http://127.0.0.1:8080/health/ready
```
Reihenfolge einhalten: Die Migrationen laufen mit dem **neuen** Programm, und
der Dienst startet erst danach. Läuft er noch, während sich das Schema ändert,
arbeitet er auf einem Stand, den er nicht kennt.
Geht etwas schief: `syncova-migrate down` nimmt **genau eine** Migration zurück
und verlangt in der Produktion `SYNCOVA_MIGRATE_CONFIRM_DOWN=yes`. Daten in
Tabellen, die es vorher nicht gab, sind danach weg — deshalb Schritt 1.
## Was nach der Installation zu tun bleibt
- **Zweiten Faktor einrichten**, mindestens für alle Konten mit Löschrecht.
- **Benachrichtigungsweg einrichten**, sonst erfährt niemand von einem Ausfall.
- **`syncova-dr export` regelmäßig laufen lassen** — die Konfigurationssicherung
im Repository ist das, was nach einem Totalverlust des Servers übrig bleibt.
- **Den Verschlüsselungsschlüssel außerhalb der Anlage hinterlegen.**