Anleitung zum Veroeffentlichen und Testen des Systems
Zwei Teile: Release bauen und in Gitea veroeffentlichen, und das System in vier Stufen durchtesten — Rauchtest, Rundlauf, Agent, Proxmox. Jede Stufe endet mit dem, was danach belegt ist, und der Rundlauf hat eine Gegenprobe: nicht leeres Ziel, rm -rf gegen ein gehaertetes Repository und ein gekippter Block muessen scheitern beziehungsweise erkannt werden. Ein Test, der nur den Erfolgsfall zeigt, belegt wenig. Der Vorschlag fuer den ersten Tag ist v1.0.0-rc1, nicht v1.0.0: Solange Windows-Dienst, systemd-Einheit und der Proxmox-Boot ungeprueft sind, waere 1.0.0 eine Zusage, die diese drei Punkte nicht deckt. Jeder genannte Endpunkt wurde gegen den eingefrorenen API-Vertrag gehalten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9c48cb1c1c
commit
c4f0b3e665
@ -345,6 +345,8 @@ Erzeugt je Zielplattform einen Verzeichnisbaum und ein Archiv — Programme, Obe
|
|||||||
|
|
||||||
macOS und ein vollständiger Windows-Server werden bewusst nicht ausgeliefert: Eine Plattform ohne Betriebskonzept weckt Erwartungen, die niemand einlöst.
|
macOS und ein vollständiger Windows-Server werden bewusst nicht ausgeliefert: Eine Plattform ohne Betriebskonzept weckt Erwartungen, die niemand einlöst.
|
||||||
|
|
||||||
|
**Release veröffentlichen und das System durchtesten:** [docs/release-howto.md](docs/release-howto.md) — vier Stufen vom Rauchtest bis zum offenen Proxmox-Meilenstein, jeweils mit Gegenprobe.
|
||||||
|
|
||||||
Für den Einstieg: [Installation](docs/installation.md) · [Wiederherstellung im Ernstfall](docs/recovery-runbook.md) · [Sicherheitsleitfaden](docs/security-guide.md) · [API](docs/api.md) · [Störungen](docs/troubleshooting.md)
|
Für den Einstieg: [Installation](docs/installation.md) · [Wiederherstellung im Ernstfall](docs/recovery-runbook.md) · [Sicherheitsleitfaden](docs/security-guide.md) · [API](docs/api.md) · [Störungen](docs/troubleshooting.md)
|
||||||
|
|
||||||
## Schnellstart
|
## Schnellstart
|
||||||
|
|||||||
442
docs/release-howto.md
Normal file
442
docs/release-howto.md
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
# Ersten Release veröffentlichen und das System testen
|
||||||
|
|
||||||
|
Zwei Teile: [Release veröffentlichen](#teil-1--release-veröffentlichen) und
|
||||||
|
[System testen](#teil-2--das-system-testen). Wer beides zum ersten Mal macht,
|
||||||
|
sollte Teil 2 zuerst lesen — ein Release, den man nicht ausprobiert hat, ist
|
||||||
|
eine Behauptung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Teil 1 — Release veröffentlichen
|
||||||
|
|
||||||
|
## 1. Vorbedingungen prüfen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Documents/development/syncova-backup
|
||||||
|
|
||||||
|
# Alles eingecheckt?
|
||||||
|
git status --porcelain # muss leer sein
|
||||||
|
|
||||||
|
# Alle Prüfungen grün?
|
||||||
|
set -a; . ./.env; set +a
|
||||||
|
export SYNCOVA_TEST_DATABASE_URL="postgres://syncova:${SYNCOVA_DB_PASSWORD}@127.0.0.1:5432/syncova?sslmode=prefer"
|
||||||
|
make check
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ohne `SYNCOVA_TEST_DATABASE_URL` überspringen neun Testdateien ihre
|
||||||
|
Datenbanktests still** — darunter Upgrade und Rollback. Ein übersprungener Test
|
||||||
|
sieht in der Zusammenfassung aus wie ein bestandener.
|
||||||
|
|
||||||
|
Und die CI muss grün sein:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://git.jfritzsche.de/jf/syncova-backup/actions
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Version festlegen
|
||||||
|
|
||||||
|
Die Version kommt aus dem Git-Tag. Ohne Tag heißt das Paket `0.1.0-dev` — für
|
||||||
|
einen Release nicht gut genug: Man kann eine Fassung dann nicht auf einen Stand
|
||||||
|
zurückführen.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a v1.0.0 -m "Syncova Backups V1"
|
||||||
|
git push https://jf:<token>@git.jfritzsche.de/jf/syncova-backup.git v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Bei `v1.0.0` bleiben oder `v1.0.0-rc1` nehmen? **Nehmen Sie `-rc1`**, solange
|
||||||
|
Windows-Dienst, systemd-Einheit und der Proxmox-Boot ungeprüft sind. Ein `1.0.0`
|
||||||
|
ist eine Zusage, die diese drei Punkte noch nicht decken.
|
||||||
|
|
||||||
|
## 3. Paket bauen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make release
|
||||||
|
```
|
||||||
|
|
||||||
|
Ergebnis in `dist/`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
syncova-v1.0.0-rc1-linux-amd64.tar.gz ~26 MB
|
||||||
|
syncova-v1.0.0-rc1-linux-arm64.tar.gz ~24 MB
|
||||||
|
syncova-v1.0.0-rc1-windows-amd64.zip ~4,4 MB
|
||||||
|
SHA256SUMS
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Version steckt danach in den Programmen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -xzf dist/syncova-v1.0.0-rc1-linux-amd64.tar.gz -C /tmp
|
||||||
|
/tmp/syncova-v1.0.0-rc1-linux-amd64/bin/syncova-api --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Das Paket ausprobieren, bevor es jemand bekommt
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Läuft es auf einem System, das nichts installiert hat?
|
||||||
|
docker run --rm -v "$PWD/dist/syncova-v1.0.0-rc1-linux-amd64:/paket:ro" \
|
||||||
|
debian:12-slim /paket/bin/syncova-api --version
|
||||||
|
|
||||||
|
# Sind die Prüfsummen im Paket stimmig?
|
||||||
|
cd dist/syncova-v1.0.0-rc1-linux-amd64 && shasum -a 256 -c SHA256SUMS | grep -c OK
|
||||||
|
```
|
||||||
|
|
||||||
|
Der erste Aufruf ist kein Formalismus. Ohne statische Bindung scheitert der
|
||||||
|
Start auf einer älteren Distribution mit einer GLIBC-Meldung, die niemand einem
|
||||||
|
Backupprogramm zuordnet.
|
||||||
|
|
||||||
|
## 5. Release in Gitea anlegen
|
||||||
|
|
||||||
|
**Über die Oberfläche:** Repository → *Releases* → *New Release* → Tag `v1.0.0-rc1`
|
||||||
|
wählen, Text aus `CHANGELOG.md` übernehmen, die drei Archive und `SHA256SUMS`
|
||||||
|
anhängen. Solange Hardware-Punkte offen sind: **„This is a pre-release"**
|
||||||
|
ankreuzen.
|
||||||
|
|
||||||
|
**Über die API:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GIT_TOKEN='<ihr-token>'
|
||||||
|
export RELEASE_TAG='v1.0.0-rc1'
|
||||||
|
|
||||||
|
# Release anlegen
|
||||||
|
RELEASE_ID=$(curl -s -X POST \
|
||||||
|
-H "Authorization: token $GIT_TOKEN" -H 'Content-Type: application/json' \
|
||||||
|
"https://git.jfritzsche.de/api/v1/repos/jf/syncova-backup/releases" \
|
||||||
|
-d "{\"tag_name\":\"$RELEASE_TAG\",\"name\":\"Syncova Backups V1 (RC1)\",
|
||||||
|
\"body\":\"Siehe CHANGELOG.md. Nicht auf echter Hardware geprüft: Windows-Dienst, systemd-Einheit, Proxmox-Boot.\",
|
||||||
|
\"prerelease\":true}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
||||||
|
|
||||||
|
# Archive anhängen
|
||||||
|
for ARCHIVE in dist/*.tar.gz dist/*.zip dist/SHA256SUMS; do
|
||||||
|
curl -s -X POST -H "Authorization: token $GIT_TOKEN" \
|
||||||
|
"https://git.jfritzsche.de/api/v1/repos/jf/syncova-backup/releases/$RELEASE_ID/assets?name=$(basename "$ARCHIVE")" \
|
||||||
|
-F "attachment=@$ARCHIVE" -o /dev/null -w " $(basename "$ARCHIVE"): %{http_code}\n"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Token braucht den Bereich `write:repository`. Reicht er nicht, meldet Gitea
|
||||||
|
`token does not have at least one of required scope(s)`.
|
||||||
|
|
||||||
|
## 6. Danach
|
||||||
|
|
||||||
|
- **`SHA256SUMS` gehört mit veröffentlicht**, sonst kann niemand prüfen, ob er
|
||||||
|
das Richtige heruntergeladen hat.
|
||||||
|
- **Sichtbarkeit prüfen:** Das Repository ist derzeit **öffentlich**. Es enthält
|
||||||
|
`PROMPT.md`, `CLAUDE.md` und den vollständigen Quellbestand. Falls das nicht
|
||||||
|
beabsichtigt ist: Settings → *Visibility*.
|
||||||
|
- **Den Verschlüsselungsschlüssel niemals mit ausliefern** — auch nicht als
|
||||||
|
Beispiel, das jemand übernimmt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Teil 2 — Das System testen
|
||||||
|
|
||||||
|
Vier Stufen, jede baut auf der vorigen auf. Steigen Sie nicht ein, wo es
|
||||||
|
interessant aussieht — steigen Sie unten ein.
|
||||||
|
|
||||||
|
| Stufe | Aufwand | Was danach belegt ist |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [A — Rauchtest](#a--rauchtest-15-minuten) | 15 min | Die Anlage läuft |
|
||||||
|
| [B — Der Rundlauf](#b--der-rundlauf-30-minuten) | 30 min | Sicherung und Wiederherstellung stimmen bitgenau |
|
||||||
|
| [C — Agent](#c--ein-zweites-system-über-den-agenten) | 1 h | Ein anderes System lässt sich sichern |
|
||||||
|
| [D — Proxmox](#d--proxmox-der-offene-meilenstein) | 2 h | Der offene Meilenstein der Phase 7 |
|
||||||
|
|
||||||
|
## A — Rauchtest (15 Minuten)
|
||||||
|
|
||||||
|
Auf Ihrem Dev-Server, mit dem entpackten Paket.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. PostgreSQL bereitstellen (falls noch nicht vorhanden)
|
||||||
|
docker run -d --name syncova-db -p 5432:5432 \
|
||||||
|
-e POSTGRES_DB=syncova -e POSTGRES_USER=syncova \
|
||||||
|
-e POSTGRES_PASSWORD=<passwort> postgres:17-alpine
|
||||||
|
|
||||||
|
# 2. Schlüssel erzeugen — und aufheben
|
||||||
|
./bin/syncova-admin generate-key
|
||||||
|
|
||||||
|
# 3. Umgebung
|
||||||
|
cat > /tmp/syncova.env <<'ENV'
|
||||||
|
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=disable
|
||||||
|
SYNCOVA_ENCRYPTION_KEYS=v1:<schluessel>
|
||||||
|
SYNCOVA_ENCRYPTION_CURRENT_KEY=v1
|
||||||
|
SYNCOVA_HTTP_LISTEN_ADDRESS=127.0.0.1:8080
|
||||||
|
ENV
|
||||||
|
|
||||||
|
set -a; . /tmp/syncova.env; set +a
|
||||||
|
|
||||||
|
# 4. Schema und erster Administrator
|
||||||
|
./bin/syncova-migrate up
|
||||||
|
./bin/syncova-migrate status
|
||||||
|
SYNCOVA_ADMIN_PASSWORD='<starkes-passwort>' ./bin/syncova-admin create-admin --username admin
|
||||||
|
|
||||||
|
# 5. Starten
|
||||||
|
./bin/syncova-api &
|
||||||
|
sleep 3
|
||||||
|
curl -s http://127.0.0.1:8080/health/ready | python3 -m json.tool | head -5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erwartet:** `"status": "healthy"`. Bei `503` sagt die Antwort, welche
|
||||||
|
Komponente fehlt — die Hülle trägt den vollständigen Bericht, auch im
|
||||||
|
Fehlerfall.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 6. Anmelden
|
||||||
|
TOKEN=$(curl -s -X POST http://127.0.0.1:8080/api/v1/auth/login \
|
||||||
|
-H 'Content-Type: application/json' -H "X-Correlation-ID: $(uuidgen)" \
|
||||||
|
-d '{"username":"admin","password":"<starkes-passwort>"}' \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['tokens']['access_token'])")
|
||||||
|
echo "${TOKEN:0:12}…"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Damit ist belegt:** Dienst, Datenbank, Schema, Verschlüsselung und Anmeldung
|
||||||
|
arbeiten. Mehr nicht — es wurde noch kein Byte gesichert.
|
||||||
|
|
||||||
|
## B — Der Rundlauf (30 Minuten)
|
||||||
|
|
||||||
|
Der Test, auf den es ankommt.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
api() { curl -s -H "Authorization: Bearer $TOKEN" -H "X-Correlation-ID: $(uuidgen)" \
|
||||||
|
-H 'Content-Type: application/json' "$@"; }
|
||||||
|
|
||||||
|
# 1. Testdaten — inkompressibel, sonst misst man Kompression statt Durchsatz
|
||||||
|
mkdir -p /srv/test/quelle
|
||||||
|
python3 - <<'PY'
|
||||||
|
import os, pathlib
|
||||||
|
root = pathlib.Path("/srv/test/quelle")
|
||||||
|
for index in range(60):
|
||||||
|
(root / f"ordner-{index % 6}").mkdir(exist_ok=True)
|
||||||
|
(root / f"ordner-{index % 6}" / f"datei-{index}.bin").write_bytes(os.urandom(512 * 1024))
|
||||||
|
(root / "notiz.txt").write_text("Testlauf\n")
|
||||||
|
os.symlink("notiz.txt", root / "verweis.txt")
|
||||||
|
PY
|
||||||
|
|
||||||
|
# 2. Repository — gehärtet
|
||||||
|
./bin/syncova-repo create --path /srv/test/repository --name "Testziel" --hardened
|
||||||
|
|
||||||
|
# 3. Eintragen
|
||||||
|
REPO=$(api -X POST http://127.0.0.1:8080/api/v1/repositories \
|
||||||
|
-d '{"name":"Testziel","location":"/srv/test/repository"}' \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")
|
||||||
|
|
||||||
|
# 4. Löschschutz MESSEN — nicht glauben
|
||||||
|
api -X POST "http://127.0.0.1:8080/api/v1/repositories/$REPO/enforcement/measure" \
|
||||||
|
| python3 -m json.tool | head -8
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erwartet:** `"level": "filesystem"`. Steht dort `advisory`, setzt Ihr
|
||||||
|
Dateisystem den Löschschutz **nicht** durch — auf einem gewöhnlichen ext4/xfs
|
||||||
|
mit root sollte `filesystem` herauskommen; in einem Container auf overlayfs
|
||||||
|
nicht. Das ist eine Auskunft über Ihre Anlage, kein Fehler der Software.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 5. Auftrag anlegen und starten
|
||||||
|
JOB=$(api -X POST http://127.0.0.1:8080/api/v1/jobs -H "Idempotency-Key: $(uuidgen)" -d "{
|
||||||
|
\"name\":\"Testlauf\",\"repository_id\":\"$REPO\",
|
||||||
|
\"schedule\":{\"type\":\"manual\"},
|
||||||
|
\"sources\":[{\"type\":\"filesystem\",\"id\":\"/srv/test/quelle\",\"name\":\"Testquelle\"}]}" \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")
|
||||||
|
|
||||||
|
api -X POST "http://127.0.0.1:8080/api/v1/jobs/$JOB/run" -H "Idempotency-Key: $(uuidgen)" \
|
||||||
|
-o /dev/null -w "HTTP %{http_code}\n" # 202, nicht 201: der Lauf ist eingereiht
|
||||||
|
|
||||||
|
sleep 15
|
||||||
|
api "http://127.0.0.1:8080/api/v1/jobs/$JOB/runs" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
r=json.load(sys.stdin)['data'][0]
|
||||||
|
print(f\" {r['status']} {r.get('bytes_processed',0)/1048576:.1f} MiB gelesen, {r.get('files_processed',0)} Objekte\")"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erwartet:** `succeeded`, rund 30 MiB, 68 Objekte. Steht dort
|
||||||
|
`partial_failure`, nennt `skip_reasons` den Grund — ein Teilfehler ist **kein**
|
||||||
|
Erfolg.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 6. Integrität prüfen
|
||||||
|
api -X POST "http://127.0.0.1:8080/api/v1/repositories/$REPO/integrity-scan" \
|
||||||
|
| python3 -c "import sys,json; print(' ', json.load(sys.stdin)['data']['details']['summary'])"
|
||||||
|
|
||||||
|
# 7. Wiederherstellbarkeit NACHWEISEN
|
||||||
|
BACKUP=$(api "http://127.0.0.1:8080/api/v1/backups" \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['data'][0]['id'])")
|
||||||
|
|
||||||
|
api -X POST http://127.0.0.1:8080/api/v1/verification -H "Idempotency-Key: $(uuidgen)" \
|
||||||
|
-d "{\"backup_id\":\"$BACKUP\",\"verification_type\":\"restore_test\"}" >/dev/null
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
api "http://127.0.0.1:8080/api/v1/backups/$BACKUP/assurance" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
d=json.load(sys.stdin)['data']
|
||||||
|
print(f\" {d['classification']} — {d['percentage']} %\")
|
||||||
|
print(f\" fehlende Messungen: {d['missing_measurements']}\")"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erwartet:** `recoverable — 70 %`. Vorher stand dort `unverified`. Die 70 %
|
||||||
|
sind richtig: RTO und RPO sind noch nicht gemessen, und Unbekanntes zählt
|
||||||
|
niemals als gut.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 8. Der Ernstfall: Prüfsummen merken, Quelle vernichten
|
||||||
|
find /srv/test/quelle -type f -print0 | sort -z | xargs -0 shasum -a 256 \
|
||||||
|
| sed 's|/srv/test/quelle/||' | sort -k2 > /tmp/vorher.sha
|
||||||
|
rm -rf /srv/test/quelle
|
||||||
|
|
||||||
|
# 9. Vorabprüfung — schreibt nichts
|
||||||
|
api -X POST http://127.0.0.1:8080/api/v1/restores/validate \
|
||||||
|
-d "{\"backup_id\":\"$BACKUP\",\"target_type\":\"filesystem\",\"target_path\":\"/srv/test/ziel\"}" \
|
||||||
|
| python3 -c "import sys,json; print(' ', json.load(sys.stdin)['data']['summary'])"
|
||||||
|
|
||||||
|
# 10. Wiederherstellen
|
||||||
|
RESTORE=$(api -X POST http://127.0.0.1:8080/api/v1/restores -H "Idempotency-Key: $(uuidgen)" \
|
||||||
|
-d "{\"backup_id\":\"$BACKUP\",\"target_type\":\"filesystem\",\"target_path\":\"/srv/test/ziel\"}" \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])")
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
# 11. DER NACHWEIS
|
||||||
|
cd /srv/test/ziel && find . -type f -print0 | sort -z | xargs -0 shasum -a 256 \
|
||||||
|
| sed 's|\./||' | sort -k2 > /tmp/nachher.sha
|
||||||
|
diff /tmp/vorher.sha /tmp/nachher.sha && echo " BITGENAU IDENTISCH"
|
||||||
|
ls -l /srv/test/ziel/verweis.txt # Symlink muss ein Symlink sein
|
||||||
|
```
|
||||||
|
|
||||||
|
**Damit ist belegt:** Die Kette trägt. Alles davor waren Indizien.
|
||||||
|
|
||||||
|
### Und jetzt die Gegenprobe
|
||||||
|
|
||||||
|
Ein Test, der nur den Erfolgsfall zeigt, belegt wenig. Diese drei **müssen**
|
||||||
|
scheitern:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ziel nicht leer → Ablehnung
|
||||||
|
api -X POST http://127.0.0.1:8080/api/v1/restores -H "Idempotency-Key: $(uuidgen)" \
|
||||||
|
-d "{\"backup_id\":\"$BACKUP\",\"target_type\":\"filesystem\",\"target_path\":\"/srv/test/ziel\"}" \
|
||||||
|
| python3 -c "import sys,json; print(' ', json.load(sys.stdin)['error']['code'])"
|
||||||
|
|
||||||
|
# Gehärtetes Repository löschen → verweigert
|
||||||
|
rm -rf /srv/test/repository 2>&1 | head -3
|
||||||
|
ls /srv/test/repository/format/repository.json && echo " Repository hat den Angriff überlebt"
|
||||||
|
|
||||||
|
# Beschädigter Block → wird erkannt
|
||||||
|
CHUNK=$(find /srv/test/repository/chunks -type f | head -1)
|
||||||
|
chattr -i "$CHUNK" 2>/dev/null; printf 'X' | dd of="$CHUNK" bs=1 seek=0 conv=notrunc 2>/dev/null
|
||||||
|
api -X POST "http://127.0.0.1:8080/api/v1/repositories/$REPO/integrity-scan" \
|
||||||
|
| python3 -c "import sys,json; d=json.load(sys.stdin)['data']['details']; print(' ', d['summary'])"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erwartet der Reihe nach:** `CONFLICT` oder `VALIDATION_FAILED`; das Repository
|
||||||
|
steht noch; der Scan meldet einen beschädigten Block und nennt das betroffene
|
||||||
|
Backup.
|
||||||
|
|
||||||
|
> Nach dem dritten Punkt ist Ihr Testrepository absichtlich kaputt. Für weitere
|
||||||
|
> Versuche neu anlegen.
|
||||||
|
|
||||||
|
## C — Ein zweites System über den Agenten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Am Server: Aufnahme-Token erzeugen
|
||||||
|
api -X POST http://127.0.0.1:8080/api/v1/agents/enrollment-tokens \
|
||||||
|
-H "Idempotency-Key: $(uuidgen)" -d '{"agent_name":"SRV-02"}' \
|
||||||
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['token'])"
|
||||||
|
```
|
||||||
|
|
||||||
|
Auf dem zu sichernden System:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./syncova-agent register --server http://<server>:8080 \
|
||||||
|
--token <aufnahme-token> --state /etc/syncova/agent.json
|
||||||
|
./syncova-agent run --state /etc/syncova/agent.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Am Server einen Auftrag mit `agent_id` in der Quelle anlegen. **Der Agent
|
||||||
|
schreibt selbst ins Repository** — es muss auf seinem System eingehängt und
|
||||||
|
beschreibbar sein. Fehlt das, meldet er `REPOSITORY_UNREACHABLE` und liefert
|
||||||
|
**keine** leere Sicherung ab.
|
||||||
|
|
||||||
|
Der Agent baut ausschließlich **ausgehende** Verbindungen auf. Eine eingehende
|
||||||
|
Freigabe ist nie nötig.
|
||||||
|
|
||||||
|
### Windows — der offene Punkt
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\syncova-agent.exe register --server http://<server>:8080 `
|
||||||
|
--token <token> --state C:\ProgramData\Syncova\agent.json
|
||||||
|
|
||||||
|
sc.exe create SyncovaAgent `
|
||||||
|
binPath= "C:\Program Files\Syncova\syncova-agent.exe run --state C:\ProgramData\Syncova\agent.json" `
|
||||||
|
start= auto
|
||||||
|
sc.exe start SyncovaAgent
|
||||||
|
```
|
||||||
|
|
||||||
|
**Der Dienstname muss `SyncovaAgent` lauten** — er steht im Programm. Weicht er
|
||||||
|
ab, meldet sich der Prozess nicht beim Dienstverwalter an, und Windows beendet
|
||||||
|
ihn nach der Startfrist mit einer Meldung, die wie ein Absturz aussieht.
|
||||||
|
|
||||||
|
Zu prüfen: Anmeldung am Dienstverwalter, Verhalten bei *Dienst beenden* und
|
||||||
|
Systemneustart, Rechte des Dienstkontos beim Lesen. **Das ist der Punkt, der die
|
||||||
|
Zeile „Windows file backup" der Akzeptanzmatrix offen hält.**
|
||||||
|
|
||||||
|
## D — Proxmox: der offene Meilenstein
|
||||||
|
|
||||||
|
Vier Schritte. Der erste ist rein lesend.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Nur nachsehen
|
||||||
|
export SYNCOVA_PROXMOX_TOKEN_SECRET='<geheimnis>'
|
||||||
|
./bin/syncova-proxmox discover --url https://pve.example:8006 \
|
||||||
|
--token 'syncova@pve!backup' --fingerprint <sha256> --disks
|
||||||
|
|
||||||
|
# 2. Verbund eintragen
|
||||||
|
api -X POST http://127.0.0.1:8080/api/v1/proxmox/clusters -d '{
|
||||||
|
"name":"pve-labor","api_endpoint":"https://pve.example:8006",
|
||||||
|
"api_token_id":"syncova@pve!backup","api_token_secret":"<geheimnis>",
|
||||||
|
"tls_fingerprint":"<sha256>","backup_storage_id":"local",
|
||||||
|
"archive_transport":"local","archive_mount_roots":{"local":"/var/lib/vz"}}'
|
||||||
|
|
||||||
|
# 3. Bestand aufnehmen, Auftrag mit proxmox_vm-Quelle anlegen, sichern
|
||||||
|
|
||||||
|
# 4. Test-VM löschen, wiederherstellen — NEBEN das Original
|
||||||
|
./bin/syncova-proxmox restore-guest --cluster <id> \
|
||||||
|
--repository /srv/test/repository --backup <backup-id> \
|
||||||
|
--target-guest qemu/900 --node pve-01
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann in Proxmox: **VM 900 starten und hineinsehen.** Bootet sie? Stimmen die
|
||||||
|
Daten? Ist die MAC-Adresse erhalten?
|
||||||
|
|
||||||
|
**Genau das ist der verpflichtende Meilenstein der Phase 7 und der einzige
|
||||||
|
Punkt, der ihn offen hält.** Alles davor läuft gegen einen Nachbau der API
|
||||||
|
durch; ob eine wiederhergestellte Maschine startet, kann nur echte Hardware
|
||||||
|
beantworten.
|
||||||
|
|
||||||
|
Rechnen Sie mit Nacharbeit — die Plattenzuordnung wird bewusst nicht aus der
|
||||||
|
gesicherten Konfiguration gesetzt (sie verwiese auf den alten Ort) und
|
||||||
|
erscheint als Warnung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Was danach in der Akzeptanzmatrix stehen darf
|
||||||
|
|
||||||
|
Nach Stufe B: `Linux file backup`, `file restore`, `folder restore`,
|
||||||
|
`restore validation`, `local repository`, `integrity scan`,
|
||||||
|
`corruption detection`.
|
||||||
|
|
||||||
|
Nach C: `agent restart` — und mit Windows die Zeile `Windows file backup`.
|
||||||
|
|
||||||
|
Nach D: `Proxmox VM backup` und `Proxmox restore`.
|
||||||
|
|
||||||
|
**Haken Sie nur ab, was Sie gefahren haben.** Eine Zeile, die man abhakt, weil
|
||||||
|
der Code vorhanden ist, ist die teuerste Zeile des ganzen Dokuments — sie
|
||||||
|
kostet genau dann, wenn man sich auf sie verlässt.
|
||||||
|
|
||||||
|
## Wenn etwas nicht geht
|
||||||
|
|
||||||
|
[docs/troubleshooting.md](troubleshooting.md) — nach Symptom geordnet. Für eine
|
||||||
|
Rückfrage nützlich: Version (`--version`), Schemastand
|
||||||
|
(`syncova-migrate status`), die `request_id` der fehlgeschlagenen Anfrage und
|
||||||
|
die Protokollzeilen dazu.
|
||||||
@ -28,6 +28,7 @@ func TestReleaseDocumentationIsComplete(testInstance *testing.T) {
|
|||||||
"docs/security-guide.md": "Sicherheitsleitfaden",
|
"docs/security-guide.md": "Sicherheitsleitfaden",
|
||||||
"docs/api.md": "API-Dokumentation",
|
"docs/api.md": "API-Dokumentation",
|
||||||
"docs/troubleshooting.md": "Stoerungsleitfaden",
|
"docs/troubleshooting.md": "Stoerungsleitfaden",
|
||||||
|
"docs/release-howto.md": "Anleitung zum Veroeffentlichen und Testen",
|
||||||
"docs/agent-installation.md": "Installationsanleitung der Agenten",
|
"docs/agent-installation.md": "Installationsanleitung der Agenten",
|
||||||
"CHANGELOG.md": "Aenderungsliste",
|
"CHANGELOG.md": "Aenderungsliste",
|
||||||
"README.md": "Ueberblick",
|
"README.md": "Ueberblick",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user