Drei Betriebsskripte fuer Linux, im Auslieferungspaket neben den Programmen. setup.sh richtet eine Anlage vollstaendig ein: PostgreSQL auf Wunsch mit (apt/dnf/yum/zypper/pacman), Dienstkonto, Verschluesselungsschluessel, Schema, erster Administrator, gehaertetes Repository, gehaertete systemd-Einheit. Jeder Schritt vermerkt, was er angelegt hat; bricht der Lauf ab, wird genau das zurueckgebaut und nichts sonst. Eine bestehende Installation wird nicht ueberschrieben — dafuer gibt es update.sh, und der Unterschied ist, dass ein Update vorher sichert. update.sh haelt die Reihenfolge ein, um die es geht: sichern, anhalten, tauschen, migrieren, starten, pruefen. Kommt der Dienst danach nicht hoch, holt es die vorige Fassung zurueck. Ohne pg_dump wird gar nicht erst begonnen — ohne Sicherung gibt es nach einer misslungenen Migration keinen Weg zurueck. Repository und Verschluesselungsschluessel bleiben unberuehrt. uninstall.sh entfernt standardmaessig NUR Dienst und Programme. Datenbank, Repository und Konfiguration bleiben liegen; jede dieser Loeschungen verlangt ein woertlich getipptes Bestaetigungswort an einem Terminal. Ein Deinstallationsskript, das nebenbei die Backups mitnimmt, vernichtet genau das, wofuer jemand jahrelang Speicher bezahlt hat. Gegen Debian 12 im Container gefahren — Installation, Anmeldung, Repository eingetragen, Loeschschutz gemessen (advisory auf overlayfs, richtig), echter Sicherungslauf, Update mit unveraendertem Bestand, beide Abbauarten. Drei Fehler dabei gefunden und behoben, alle derselben Art: - "tr </dev/urandom | head -c 32" und "psql | grep -q": Der frueh geschlossene Pipe schickt dem Schreiber SIGPIPE, und mit "set -o pipefail" bricht das Skript mitten in der Einrichtung ab, ohne erkennbaren Grund. - update.sh las die laufende Fassung mit "grep -o" aus der Antwort von /health/ready. Die enthaelt gar kein Versionsfeld; grep endet mit 1, und das Skript nahm ein GELUNGENES Update wieder zurueck — wegen einer Zeile, die nur der Ausgabe dient. Dazu: SYNCOVA_ADMIN_PASSWORD stand in der Doku und existiert nicht — das Passwort kommt ueber die Standardeingabe. Und "syncova-repo break-lock" fehlte zwar nicht mehr, aber der Test auf die Uebereinstimmung der drei Skripte ist neu: Drei Skripte, die sich ueber den Installationsort uneinig sind, ergeben eine Anlage, die sich nicht mehr entfernen laesst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
443 lines
16 KiB
Markdown
443 lines
16 KiB
Markdown
# 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
|
|
./bin/syncova-admin create-admin --username admin # fragt das Passwort ab
|
|
|
|
# 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.
|