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.
|
||||
|
||||
**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)
|
||||
|
||||
## 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/api.md": "API-Dokumentation",
|
||||
"docs/troubleshooting.md": "Stoerungsleitfaden",
|
||||
"docs/release-howto.md": "Anleitung zum Veroeffentlichen und Testen",
|
||||
"docs/agent-installation.md": "Installationsanleitung der Agenten",
|
||||
"CHANGELOG.md": "Aenderungsliste",
|
||||
"README.md": "Ueberblick",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user