diff --git a/.gitignore b/.gitignore index e1638f9..f51df0f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ __pycache__/ *.lock state.json pvesnap.conf + +# Handbuch +handbuch/site/ +handbuch/.venv/ +handbuch/werkstatt/demo/ diff --git a/README.md b/README.md index 0880a6a..187f0a9 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,25 @@ Dazu drei Werkzeuge, um wieder **an die Daten im Snapshot** zu kommen: --- +## Handbuch + +Diese Datei ist die Kurzfassung. Ausführlich, mit Bildschirmfotos und +Volltextsuche, steht alles im **Handbuch** unter [`handbuch/`](handbuch/): + +```bash +handbuch/bauen.sh # baut handbuch/site/ (reines HTML, ohne Netz nutzbar) +handbuch/bauen.sh ansehen # Vorschau auf http://127.0.0.1:8000/ +``` + +`install.sh` nimmt das gebaute Handbuch mit auf den Host, nach +`/usr/share/doc/pvesnap/handbuch/index.html` — genau dort, wo man es im Notfall +braucht, ohne Netz und ohne Suchmaschine. + +Die Bildschirmfotos werden aus dem echten Programmcode erzeugt, gegen einen +erfundenen Proxmox-Host; siehe [`handbuch/werkstatt/`](handbuch/werkstatt/). + +--- + ## Installation Auf dem Proxmox-Host als root: diff --git a/handbuch/bauen.sh b/handbuch/bauen.sh new file mode 100755 index 0000000..4d22475 --- /dev/null +++ b/handbuch/bauen.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# --------------------------------------------------------------------- +# Das Handbuch bauen. +# +# Ergebnis ist reines HTML in handbuch/site/ - ohne Netzzugang benutzbar, +# mit Volltextsuche. install.sh kopiert es von dort nach +# /usr/share/doc/pvesnap/handbuch, wenn es vorliegt. +# +# Gebaut wird auf dem Arbeitsplatz, nicht auf dem Proxmox-Host: mkdocs +# gehoert nicht auf einen Hypervisor. +# --------------------------------------------------------------------- +set -euo pipefail + +HIER="$(cd "$(dirname "$0")" && pwd)" +VENV="${HIER}/.venv" + +rot() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +info() { printf '\033[36m%s\033[0m\n' "$*"; } + +BEDARF=(mkdocs mkdocs-material) + +if ! command -v python3 >/dev/null 2>&1; then + rot "python3 wird gebraucht." + exit 1 +fi + +# Eigene Umgebung, damit nichts am System haengenbleibt. +if [ ! -x "${VENV}/bin/mkdocs" ]; then + info "Richte Bau-Umgebung in ${VENV} ein ..." + python3 -m venv "${VENV}" + "${VENV}/bin/pip" install --quiet --upgrade pip + "${VENV}/bin/pip" install --quiet "${BEDARF[@]}" +fi + +case "${1:-bauen}" in + bauen) + info "Baue Handbuch ..." + "${VENV}/bin/mkdocs" build --strict --config-file "${HIER}/mkdocs.yml" + echo + info "Fertig: ${HIER}/site/index.html" + echo " Auf den Host bringen: ./install.sh (kopiert es mit)" + ;; + ansehen|serve) + info "Starte Vorschau auf http://127.0.0.1:8000/ (Strg-C beendet)" + "${VENV}/bin/mkdocs" serve --config-file "${HIER}/mkdocs.yml" + ;; + bilder) + # Die Bildschirmfotos neu aufnehmen - siehe werkstatt/LIESMICH.md + exec "${HIER}/werkstatt/aufnehmen.sh" + ;; + sauber) + rm -rf "${HIER}/site" "${VENV}" + info "site/ und .venv/ entfernt." + ;; + *) + cat < + + + + + +root@pve1 — pvesnap config → Globales + + pvesnap - Globale Einstellungen + /etc/pvesnap.conf + +Praefix im Snapshot-Namen auto + Pruefintervall des Dienstes 1m + Zustandsdatei /var/lib/pvesnap/state.json + Protokollstufe INFO + Zusaetzliche Logdatei - + Zeitlimit je Snapshot-Task 15m + Wiederholungen bei belegter Spe… 2 + Wartezeit vor Wiederholung 1m + Pause zwischen zwei VMs keine + Beim Start sofort ausfuehren nein + Testlauf (nichts wirklich tun) nein + Standard-Beschreibung pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time… + Platzhalter der Beschreibung: {group} {vmid} {name} {node} {type} {datetime} {date} {time} {keep_time} {keep_count} + + Enter Aendern | q zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/config-gruppe.svg b/handbuch/docs/bilder/config-gruppe.svg new file mode 100644 index 0000000..8f9f463 --- /dev/null +++ b/handbuch/docs/bilder/config-gruppe.svg @@ -0,0 +1,40 @@ + + + + + + +root@pve1 — pvesnap config → Gruppe taeglich + + pvesnap - Gruppe: taeglich + Kurzname im Snapshot: auto-taeglich-JJJJMMTT-HHMMSS +-- Allgemein ----------------------------------------------------------------------------------------------------- + +Name taeglich + Aktiv ja +-- Zeitplan ------------------------------------------------------------------------------------------------------ + Art des Zeitplans taeglich + Uhrzeit (HH:MM) 02:30 + Naechster Termin (Vorschau) Mo 10.08.2026 02:30 (taeglich um 02:30) +-- Vorhaltezeit -------------------------------------------------------------------------------------------------- + Anzahl behalten (je VM) 14 + Maximales Alter 3w + Mindestens behalten 0 (keine Untergrenze) +-- Welche VMs? --------------------------------------------------------------------------------------------------- + Alle VMs/Container nein + VMs aus Liste waehlen ... - + Namensmuster (z.B. web-*) - + Tags produktion + Pools - + Gasttypen alle (VMs und Container) + Ausgeschlossene VMIDs - + Ausgeschlossene Namensmuster - + Ausgeschlossene Tags nosnap + Trifft aktuell zu auf 6 Gast/Gaeste (100, 101, 102, 105, 110, 130) +-- Snapshot-Optionen --------------------------------------------------------------------------------------------- + RAM mitsichern (nur laufende … nein + Gestoppte ueberspringen nein + Beschreibung (Vorlage) pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time} … + + Enter Aendern | Leer Umschalten | v VM-Auswahl | q zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/config-gruppen.svg b/handbuch/docs/bilder/config-gruppen.svg new file mode 100644 index 0000000..8f4c99f --- /dev/null +++ b/handbuch/docs/bilder/config-gruppen.svg @@ -0,0 +1,21 @@ + + + + + + +root@pve1 — pvesnap config + + pvesnap - Gruppen + Datei: /etc/pvesnap.conf [Dienst: laeuft] +Gruppe Aktiv Zeitplan Behalte Auswahl + ------------------------------------------------------------------------------------------------------------------ + +stuendlich ja alle 1h (an der Uhr ausgeric… 24 / 2d Tags: stuendlich + taeglich ja taeglich um 02:30 14 / 3w Tags: produktion + monatlich ja monatlich am 1. um 04:00 6 / 57w1d alle VMs + + Enter Bearbeiten | n Neu | c Kopieren | d Loeschen | Leer An/Aus + + g Global | v VMs | s Speichern | r Dienst neu laden | q Ende + \ No newline at end of file diff --git a/handbuch/docs/bilder/config-uebersicht.svg b/handbuch/docs/bilder/config-uebersicht.svg new file mode 100644 index 0000000..028f911 --- /dev/null +++ b/handbuch/docs/bilder/config-uebersicht.svg @@ -0,0 +1,24 @@ + + + + + + +root@pve1 — pvesnap config → Uebersicht + + pvesnap - Uebersicht: welche VM in welcher Gruppe? + 10 Gaeste +VMID Name Status Gruppen + 100 web01 running stuendlich, taeglich, monatlich + 101 db01 running stuendlich, taeglich, monatlich + 102 fileserver running taeglich, monatlich + 105 warenwirtschaft running taeglich, monatlich + 110 mailgw running taeglich, monatlich + 111 gitlab running monatlich + 120 build-test stopped (keine Gruppe) + 130 dc01 running taeglich, monatlich + 9101 db01-live running (keine Gruppe) + 9102 warenwirtschaft-w stopped (keine Gruppe) + + Pfeiltasten Blaettern | r Neu laden | q zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/config-vms.svg b/handbuch/docs/bilder/config-vms.svg new file mode 100644 index 0000000..91120cc --- /dev/null +++ b/handbuch/docs/bilder/config-vms.svg @@ -0,0 +1,25 @@ + + + + + + +root@pve1 — pvesnap config → VMs waehlen + + pvesnap - VMs fuer Gruppe 'taeglich' + 0 ausgewaehlt + VMID Typ Name Node Status Tags + +[ ] 100 VM web01 pve1 running produktion,stuendli… + [ ] 101 VM db01 pve1 running produktion,datenban… + [ ] 102 VM fileserver pve2 running produktion + [ ] 105 VM warenwirtschaft pve2 running produktion,dongle + [ ] 110 LXC mailgw pve1 running produktion + [ ] 111 LXC gitlab pve2 running entwicklung + [ ] 120 VM build-test pve2 stopped nosnap + [ ] 130 VM dc01 pve1 running produktion + [ ] 9101 VM db01-live pve1 running pvesnap-recovery + [ ] 9102 VM warenwirtschaft-w pve2 stopped pvesnap-recovery + + Leer Auswaehlen | a Alle | n Keine | i Umkehren | / Filter | Enter Uebernehmen | q Abbruch + \ No newline at end of file diff --git a/handbuch/docs/bilder/dongle-vorhaben.svg b/handbuch/docs/bilder/dongle-vorhaben.svg new file mode 100644 index 0000000..4d2b756 --- /dev/null +++ b/handbuch/docs/bilder/dongle-vorhaben.svg @@ -0,0 +1,28 @@ + + + + + + +root@pve2 — pvesnap-recovery → einrichten + + Vorhaben pruefen + Quelle: VM 105 (warenwirtschaft) auf pve2 + Snapshot: auto-taeglich-20260809-023000 +Neu: VM 9104 auf pve2 + Betriebsart: live - abgeschottet + Datentraeger (Copy-on-Write-Klone, das Original bleibt unberuehrt): + scsi0 ceph-vm:vm-105-disk-0 250G + Netzwerk: keine Netzwerkkarte + net0 virtio=BC:24:11:00:69:40,bridge=vmbr0,firewall=1 +Anzeige: noVNC und SPICE (vga: qxl) +USB: 2 Weiterleitung(en) ueber SPICE +Transfer: werkzeuge 4.0G im Gast als WERKZEUGE + - Zugang auf zwei Wegen: noVNC im Browser und SPICE ueber + 'pvesnap-recovery spice 9104' (remote-viewer). + - Im remote-viewer unter 'USB-Geraeteauswahl' das Geraet anhaken - es + kommt vom eigenen Rechner, nicht ueber das Netz des Gastes. Guest-Tools + braucht es dafuer nicht. + + j = anlegen | n = abbrechen + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-ansehen.svg b/handbuch/docs/bilder/explorer-ansehen.svg new file mode 100644 index 0000000..4d8ebf0 --- /dev/null +++ b/handbuch/docs/bilder/explorer-ansehen.svg @@ -0,0 +1,23 @@ + + + + + + +root@pve1 — pvesnap-explorer 101 + + /run/pvesnap/mnt/101-auto-taeglich-20260809-023000/etc/fstab 699B +# /etc/fstab: static file system information. +# +# Use 'blkid' to print the universally unique identifier for a device; this may +# be used with UUID= as a more robust way to name devices that works even if +# disks are added and removed. See fstab(5). +# +# <file system> <mount point> <type> <options> <dump> <pass> +UUID=4c1a9f3e-2b77-4d18-9a5c-7e3f0d1b8a26 / ext4 errors=remount-ro 0 1 +UUID=9f2c-31AD /boot/efi vfat umask=0077 0 1 +/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1 /srv/dumps xfs defaults 0 2 +/swap.img none swap sw 0 0 + + Pfeiltasten/Bild blaettern | Pos1/Ende | q zurueck (Zeile 1 von 11) + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-fenster.svg b/handbuch/docs/bilder/explorer-fenster.svg new file mode 100644 index 0000000..2e474df --- /dev/null +++ b/handbuch/docs/bilder/explorer-fenster.svg @@ -0,0 +1,50 @@ + + + + + + +root@pve1 — pvesnap-explorer 101 + + pvesnap-explorer - VM 101 (db01) - Snapshot auto-taeglich-20260809-023000 + + VM 101 (db01) @ auto-taeglich-20260809-023000 +│ Lokaler Rechner +/run/pvesnap/mnt/101-auto-taeglich-20260809-023000/srv/du… +│/root + + .. <hoch> + + .. <hoch> + db01-20260808-0200.sql.gz 3.8G 08.08.26 02:42│ + holen <DIR> 06.08.26 11:42 + db01-20260809-0200.sql.gz 3.9G 09.08.26 02:30│ + werkzeuge <DIR> 06.08.26 11:42 + kunden-export.csv 84.3M 07.08.26 07:42│ notizen.md 3.1K 06.08.26 11:42 + + + + + + + + + + + + + + + + + + + + + 4 Eintraege │ 4 Eintraege + /run/pvesnap/mnt/101-auto-taeglich-20260809-023000/srv + + Tab Fenster | Enter Oeffnen | Leer Markieren | F5 Kopieren + + F3 Ansehen | F7 Neuer Ordner | F2 Laufwerk | F10 Zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-gastauswahl.svg b/handbuch/docs/bilder/explorer-gastauswahl.svg new file mode 100644 index 0000000..e08037d --- /dev/null +++ b/handbuch/docs/bilder/explorer-gastauswahl.svg @@ -0,0 +1,27 @@ + + + + + + +root@pve1 — pvesnap-explorer + ┌─ + Welcher Gast? (Esc beendet) +────────────────────────┐ + │ │ + + + VM 100 web01 running pve1 + + │ VM 101 db01 running pve1 │ + │ VM 102 fileserver running pve2 │ + │ VM 105 warenwirtschaft running pve2 │ + │ LXC 110 mailgw running pve1 │ + │ LXC 111 gitlab running pve2 │ + │ VM 120 build-test stopped pve2 │ + │ VM 130 dc01 running pve1 │ + │ VM 9101 db01-live running pve1 │ + │ VM 9102 warenwirtschaft-w stopped pve2 │ + │ │ + └───────────────────────────────────────────────────────┘ + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-hilfe.svg b/handbuch/docs/bilder/explorer-hilfe.svg new file mode 100644 index 0000000..20165c5 --- /dev/null +++ b/handbuch/docs/bilder/explorer-hilfe.svg @@ -0,0 +1,31 @@ + + + + + + +root@pve1 — pvesnap-explorer 101 + + Tastenbelegung + Tab zwischen den Fenstern wechseln + Pfeiltasten bewegen, Enter oeffnet Verzeichnis oder zeigt Datei + Backspace ein Verzeichnis hoch + Leertaste markieren, * umkehren, a alle, u keine + F5 / c markiertes ins andere Fenster kopieren + F3 / v Datei ansehen (Text oder Hex) + F7 / n neues Verzeichnis (nur im lokalen Fenster) + F6 / g Verzeichnis direkt eingeben + F2 / m anderes Dateisystem des Snapshots waehlen + r neu einlesen + F10 / q zurueck zur Snapshot-Auswahl + Der Snapshot ist schreibgeschuetzt eingebunden - es kann nichts + kaputtgehen. Kopiert wird immer vom aktiven ins andere Fenster. + Geraetedateien und Sockets werden uebersprungen, symbolische + Verweise bleiben Verweise. + Es gibt drei Ebenen: Gastauswahl -> Snapshot-Auswahl -> Dateien. + F10 oder q geht jeweils eine Ebene zurueck; der Snapshot wird + dabei wieder ausgehaengt. Das rechte Fenster behaelt sein + Verzeichnis, auch wenn man den naechsten Snapshot oeffnet. + + beliebige Taste = zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-kopieren.svg b/handbuch/docs/bilder/explorer-kopieren.svg new file mode 100644 index 0000000..d3da706 --- /dev/null +++ b/handbuch/docs/bilder/explorer-kopieren.svg @@ -0,0 +1,52 @@ + + + + + + +root@pve1 — pvesnap-explorer 101 + + pvesnap-explorer - VM 101 (db01) - Snapshot auto-taeglich-20260809-023000 + + VM 101 (db01) @ auto-taeglich-20260809-023000 +│ Lokaler Rechner +/run/pvesnap/mnt/101-auto-taeglich-20260809-023000/srv/du… +│/root + .. <hoch> + + .. <hoch> +*db01-20260808-0200.sql.gz 3.8G 08.08.26 02:42 + + holen <DIR> 06.08.26 11:42 + + db01-20260809-0200.sql.gz 3.9G 09.08.26 02:30 + + werkzeuge <DIR> 06.08.26 11:42 + kunden-export.csv 84.3M 07.08.26 07:42│ notizen.md 3.1K 06.08.26 11:42 + + + + + + + + + + + + + + + + + + + + + 4 Eintraege 1 markiert │ 4 Eintraege + /run/pvesnap/mnt/101-auto-taeglich-20260809-023000/srv/dumps/db01-20260809-0200.sql.gz + + db01-20260808-0200.sql.gz (3.8G) nach /root kopieren? [j/n] + + F3 Ansehen | F7 Neuer Ordner | F2 Laufwerk | F10 Zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-markiert.svg b/handbuch/docs/bilder/explorer-markiert.svg new file mode 100644 index 0000000..cd6746f --- /dev/null +++ b/handbuch/docs/bilder/explorer-markiert.svg @@ -0,0 +1,53 @@ + + + + + + +root@pve1 — pvesnap-explorer 101 + + pvesnap-explorer - VM 101 (db01) - Snapshot auto-taeglich-20260809-023000 + + VM 101 (db01) @ auto-taeglich-20260809-023000 +│ Lokaler Rechner +/run/pvesnap/mnt/101-auto-taeglich-20260809-023000/srv/du… +│/root + .. <hoch> + + .. <hoch> +*db01-20260808-0200.sql.gz 3.8G 08.08.26 02:42 + + holen <DIR> 06.08.26 11:42 +*db01-20260809-0200.sql.gz 3.9G 09.08.26 02:30 + + werkzeuge <DIR> 06.08.26 11:42 + + kunden-export.csv 84.3M 07.08.26 07:42 +│ notizen.md 3.1K 06.08.26 11:42 + + + + + + + + + + + + + + + + + + + + + 4 Eintraege 2 markiert │ 4 Eintraege + /run/pvesnap/mnt/101-auto-taeglich-20260809-023000/srv/dumps/kunden-export.csv + + Tab Fenster | Enter Oeffnen | Leer Markieren | F5 Kopieren + + F3 Ansehen | F7 Neuer Ordner | F2 Laufwerk | F10 Zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/explorer-snapshotauswahl.svg b/handbuch/docs/bilder/explorer-snapshotauswahl.svg new file mode 100644 index 0000000..a334d5c --- /dev/null +++ b/handbuch/docs/bilder/explorer-snapshotauswahl.svg @@ -0,0 +1,43 @@ + + + + + + +root@pve1 — pvesnap-explorer + ┌─ + Welcher Snapshot? (Esc = zurueck zur Gastauswahl) +──────────────────────────────────────────────┐ + │ │ + + + auto-stuendlich-20260809-100000 09.08.2026 10:00 pvesnap | Gruppe: stuendlich | erstellt… + + │ auto-stuendlich-20260809-090000 09.08.2026 09:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-080000 09.08.2026 08:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-070000 09.08.2026 07:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-060000 09.08.2026 06:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-050000 09.08.2026 05:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-040000 09.08.2026 04:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-030000 09.08.2026 03:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-020000 09.08.2026 02:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-010000 09.08.2026 01:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260809-000000 09.08.2026 00:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-230000 08.08.2026 23:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-220000 08.08.2026 22:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-210000 08.08.2026 21:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-200000 08.08.2026 20:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-190000 08.08.2026 19:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-180000 08.08.2026 18:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-170000 08.08.2026 17:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-160000 08.08.2026 16:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-150000 08.08.2026 15:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-140000 08.08.2026 14:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-130000 08.08.2026 13:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-120000 08.08.2026 12:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-stuendlich-20260808-110000 08.08.2026 11:00 pvesnap | Gruppe: stuendlich | erstellt… │ + │ auto-taeglich-20260808-023000 08.08.2026 02:30 pvesnap | Gruppe: taeglich | erstellt: … │ + │ auto-taeglich-20260807-023000 07.08.2026 02:30 pvesnap | Gruppe: taeglich | erstellt: … │ + │ │ + └───────────────────────────────────────────────────────────────────────────────────────────────────┘ + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-betriebsart.svg b/handbuch/docs/bilder/recovery-betriebsart.svg new file mode 100644 index 0000000..3a72ed6 --- /dev/null +++ b/handbuch/docs/bilder/recovery-betriebsart.svg @@ -0,0 +1,23 @@ + + + + + + +root@pve1 — pvesnap-recovery → einrichten + + Wiederherstellung einrichten - VM 101 (db01) + + Betriebsart: recovery - mit Netz, gleiche Identitaet + Netzwerk: automatisch (nach Modus) + Arbeitsspeicher: wenn im Snapshot vorhanden + Transfer-Laufwerke: werkzeuge + SPICE-Anzeige: nein - nur noVNC im Browser + USB-Weiterleitung: keine + Neue VMID: 9103 + Node: pve1 + Danach starten: ja + live = zum Hineinschauen. recovery = die Maschine wirklich wieder in Betrieb nehmen. + + Pfeile Auswaehlen | Enter/Leer Aendern | v Laufwerke verwalten | F10 Anlegen | q Abbrechen + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-detail.svg b/handbuch/docs/bilder/recovery-detail.svg new file mode 100644 index 0000000..8ac9a59 --- /dev/null +++ b/handbuch/docs/bilder/recovery-detail.svg @@ -0,0 +1,29 @@ + + + + + + +root@pve1 — pvesnap-recovery + + Wiederherstellung VM 9101 +Maschine: VM 9101 auf pve1 +Zustand: running + Herkunft: VM 101 @ auto-stuendlich-20260809-100000 + Betriebsart: live + Angelegt: 09.08.2026 11:04 + Konsole im Browser: + https://10.20.0.11:8006/?console=kvm&novnc=1&vmid=9101&node=pve1&resize=off&cmd= + SPICE (remote-viewer): pvesnap-recovery spice 9101 + Der Arbeitsspeicher des Snapshots wurde geladen - beim ersten Start lief die Maschine genau weiter. + Transfer-Laufwerke: + dumps eingesteckt als scsi1 + e = auswerfen / einklinken, im laufenden Betrieb +Datentraeger: Linked Clone - haengt am Quell-Snapshot + Belegt nur, was seither geschrieben wurde. Der Quell-Snapshot ist dafuer unloeschbar. + Fuer den Dauerbetrieb mit f loesen (kopiert die Daten wirklich). + + s Starten | h Herunterfahren | e Auswerfen/Einklinken | v Laufwerke + + p SPICE/USB | f Loesen | x Verwerfen | q zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-loesen.svg b/handbuch/docs/bilder/recovery-loesen.svg new file mode 100644 index 0000000..84c8fd1 --- /dev/null +++ b/handbuch/docs/bilder/recovery-loesen.svg @@ -0,0 +1,13 @@ + + + + + + +root@pve1 — pvesnap-recovery + + pvesnap-recovery +Ermittle, wieviel kopiert werden muss ... + + 2.4G kopieren und dauerhaft loesen? [j/n] + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-neustart.svg b/handbuch/docs/bilder/recovery-neustart.svg new file mode 100644 index 0000000..628b294 --- /dev/null +++ b/handbuch/docs/bilder/recovery-neustart.svg @@ -0,0 +1,36 @@ + + + + + + +root@pve1 — pvesnap-recovery + + Neustart noetig + +Dafuer muss VM 9101 neu starten - die Grafikkarte laesst sich im Betrieb + +nicht wechseln. + +Gleich kannst du waehlen, wie: entweder du faehrst im Gast selbst herunter + +und pvesnap startet ihn danach wieder, oder Proxmox versucht es per ACPI. + +Der erste Weg ist der verlaesslichere - Windows ignoriert den Aus-Knopf + +gern. + +Ein Neustart von innen im Gast wuerde nichts bringen - dabei setzt sich + +nur die Maschine zurueck, der QEMU-Prozess laeuft weiter mit der alten + +Grafikkarte. + +ACHTUNG: der geladene Arbeitsspeicher ist danach endgueltig weg. Die + +Maschine bootet kalt, offene Programme und ungespeicherte Daten sind + +verloren. + + j = trotzdem | andere Taste = abbrechen + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-neustartwahl.svg b/handbuch/docs/bilder/recovery-neustartwahl.svg new file mode 100644 index 0000000..03fb6a7 --- /dev/null +++ b/handbuch/docs/bilder/recovery-neustartwahl.svg @@ -0,0 +1,25 @@ + + + + + + +root@pve1 — pvesnap-recovery + + Stelle Anzeige um +Setze usb0=spice, usb1=spice, vga=qxl + ┌─ + Wie soll VM 9101 neu starten? +───────────────────────────────────┐ + │ │ + + + Ich fahre im Gast herunter - pvesnap wartet und startet dann + + │ Proxmox herunterfahren lassen (ACPI) - Windows blockt das oft │ + │ Gar nicht - spaeter selbst │ + │ │ + └───────────────────────────────────────────────────────────────────┘ + + bitte warten ... + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-optionen.svg b/handbuch/docs/bilder/recovery-optionen.svg new file mode 100644 index 0000000..38c2560 --- /dev/null +++ b/handbuch/docs/bilder/recovery-optionen.svg @@ -0,0 +1,23 @@ + + + + + + +root@pve1 — pvesnap-recovery → einrichten + + Wiederherstellung einrichten - VM 101 (db01) + + Betriebsart: live - abgeschottet, ohne Netzwerk + Netzwerk: automatisch (nach Modus) + Arbeitsspeicher: wenn im Snapshot vorhanden + Transfer-Laufwerke: werkzeuge + SPICE-Anzeige: nein - nur noVNC im Browser + USB-Weiterleitung: keine + Neue VMID: 9103 + Node: pve1 + Danach starten: ja + live = zum Hineinschauen. recovery = die Maschine wirklich wieder in Betrieb nehmen. + + Pfeile Auswaehlen | Enter/Leer Aendern | v Laufwerke verwalten | F10 Anlegen | q Abbrechen + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-spice.svg b/handbuch/docs/bilder/recovery-spice.svg new file mode 100644 index 0000000..e880a74 --- /dev/null +++ b/handbuch/docs/bilder/recovery-spice.svg @@ -0,0 +1,37 @@ + + + + + + +root@pve1 — pvesnap-recovery + + Wiederherstellung VM 9101 +Maschine: VM 9101 auf pve1 +Zustand: running + Herkunft: VM 101 @ auto-stuendlich-20260809-100000 + Betriebsart: live + Angelegt: 09.08.2026 11:04 + Konsole im Browser: + https://10.20.0.11:8006/?console=kvm&novnc=1&vmid=9101&node=pve1&resize=off&cmd= + SPICE (remote-viewer): pvesnap-recovery spice 9101 + Der Arbeitsspeicher des Snap┌─ + SPICE-Anzeige (jetzt: aus) +─────────────────────────┐ genau weiter. + │ │ + Transfer-Laufwerke: │ + + ein - zusaetzlich remote-viewer, noetig fuer USB + + dumps eingesteck +│ aus - nur noVNC im Browser │ + e = auswerfen / einklinke│ │ + └───────────────────────────────────────────────────────┘ +Datentraeger: Linked Clone - haengt am Quell-Snapshot + Belegt nur, was seither geschrieben wurde. Der Quell-Snapshot ist dafuer unloeschbar. + Fuer den Dauerbetrieb mit f loesen (kopiert die Daten wirklich). + + s Starten | h Herunterfahren | e Auswerfen/Einklinken | v Laufwerke + + p SPICE/USB | f Loesen | x Verwerfen | q zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-uebersicht.svg b/handbuch/docs/bilder/recovery-uebersicht.svg new file mode 100644 index 0000000..f6f455f --- /dev/null +++ b/handbuch/docs/bilder/recovery-uebersicht.svg @@ -0,0 +1,16 @@ + + + + + + +root@pve1 — pvesnap-recovery + + pvesnap-recovery 1.0.0 - Snapshots als Maschine starten +Maschine Zustand Node Herkunft Modus + + VM 9101 running pve1 VM 101 @ auto-stuendlich-20… live + VM 9102 stopped pve2 VM 105 @ auto-taeglich-2026… recover + + Enter Details | n Neu | s Starten | h Herunterfahren | x Verwerfen | v Laufwerke | r Aktualisieren | q Beenden + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-usb.svg b/handbuch/docs/bilder/recovery-usb.svg new file mode 100644 index 0000000..b581278 --- /dev/null +++ b/handbuch/docs/bilder/recovery-usb.svg @@ -0,0 +1,47 @@ + + + + + + +root@pve1 — pvesnap-recovery + + Wiederherstellung VM 9101 +Maschine: VM 9101 auf pve1 +Zustand: running + Herkunft: VM 101 @ auto-stuendlich-20260809-100000 + Betriebsart: live + Angelegt: 09.08.2026 11:04 + Konsole im Browser: + https://10.20.0.11:8006/?console=kvm&novnc=1&vmid=9101&node=pve1&resize=off&cmd= + SPICE (remote-viewer): pvesnap-recovery spice 9101 + ┌─ + USB-Weiterleitung (jetzt: 0) +───┐ + Der Arbeitsspeicher des Snap┌─ + SPICE-A +│ │─────────┐ genau weiter. + │ │ + + keine + │ │ + Transfer-Laufwerke: │ + + ein - +│ 1 Anschluss │ + +r USB + + dumps eingesteck +│ aus - │ 2 Anschluesse │ │ + e = auswerfen / einklinke│ │ 4 Anschluesse │ │ + └─────────│ │─────────┘ +Datentraeger: Linked Clone - haengt am +└───────────────────────────────────┘ + Belegt nur, was seither geschrieben wurde. Der Quell-Snapshot ist dafuer unloeschbar. + Fuer den Dauerbetrieb mit f loesen (kopiert die Daten wirklich). + + s Starten | h Herunterfahren | e Auswerfen/Einklinken | v Laufwerke + + p SPICE/USB | f Loesen | x Verwerfen | q zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-verwerfen.svg b/handbuch/docs/bilder/recovery-verwerfen.svg new file mode 100644 index 0000000..48c9172 --- /dev/null +++ b/handbuch/docs/bilder/recovery-verwerfen.svg @@ -0,0 +1,16 @@ + + + + + + +root@pve1 — pvesnap-recovery + + pvesnap-recovery 1.0.0 - Snapshots als Maschine starten +Maschine Zustand Node Herkunft Modus + + VM 9101 running pve1 VM 101 @ auto-stuendlich-20… live + VM 9102 stopped pve2 VM 105 @ auto-taeglich-2026… recover + + VM 9101 wirklich verwerfen? Alle Aenderungen darin sind weg. [j/n] + \ No newline at end of file diff --git a/handbuch/docs/bilder/recovery-vorhaben.svg b/handbuch/docs/bilder/recovery-vorhaben.svg new file mode 100644 index 0000000..be5a3ed --- /dev/null +++ b/handbuch/docs/bilder/recovery-vorhaben.svg @@ -0,0 +1,28 @@ + + + + + + +root@pve1 — pvesnap-recovery → einrichten + + Vorhaben pruefen + Quelle: VM 101 (db01) auf pve1 + Snapshot: auto-stuendlich-20260809-100000 +Neu: VM 9103 auf pve1 + Betriebsart: live - abgeschottet + Datentraeger (Copy-on-Write-Klone, das Original bleibt unberuehrt): + scsi0 ceph-vm:vm-101-disk-0 400G + Netzwerk: Karte vorhanden, Leitung abgeklemmt + net0 virtio=BC:24:11:00:65:40,bridge=vmbr0,firewall=1 +Arbeitsspeicher: wird geladen (32.0G) - laeuft weiter statt zu booten +Transfer: werkzeuge 4.0G im Gast als WERKZEUGE + - Netzwerkkarte bleibt vorhanden, aber abgeklemmt (link_down): mit + geladenem Arbeitsspeicher darf sich die Geraeteausstattung nicht aendern. + - Der Arbeitsspeicher (32.0G) wird kopiert; die Maschine laeuft danach + genau dort weiter, wo sie beim Snapshot stand. + - Proxmox gibt den kopierten Arbeitsspeicher nach dem ersten Start wieder + frei - ein spaeterer Neustart bootet dann kalt. + + j = anlegen | n = abbrechen + \ No newline at end of file diff --git a/handbuch/docs/bilder/transfer-auswahl.svg b/handbuch/docs/bilder/transfer-auswahl.svg new file mode 100644 index 0000000..b2ee5b3 --- /dev/null +++ b/handbuch/docs/bilder/transfer-auswahl.svg @@ -0,0 +1,19 @@ + + + + + + +root@pve1 — pvesnap-recovery → einrichten + + Welche Transfer-Laufwerke anhaengen? + Name Groesse Zustand Wofuer + + [ ] dumps 20.0G in Benutzung von 9101 Datenbank-Dumps und Expor… + [x] werkzeuge 4.0G frei Skripte, Treiber, Install… + + [ ] austausch 8.0G am Host eingehaengt Alles andere + Belegte Laufwerke lassen sich nicht anhaengen - erst dort aushaengen. + + Leertaste Aus/Abwaehlen | Enter Uebernehmen | q Abbrechen + \ No newline at end of file diff --git a/handbuch/docs/bilder/transfer-commander.svg b/handbuch/docs/bilder/transfer-commander.svg new file mode 100644 index 0000000..4eb0a28 --- /dev/null +++ b/handbuch/docs/bilder/transfer-commander.svg @@ -0,0 +1,52 @@ + + + + + + +root@pve1 — pvesnap-recovery → dumps + + pvesnap-explorer - Transfer-Laufwerk dumps - beide Seiten beschreibbar + + Transfer: dumps (DUMPS) +│ Lokaler Rechner +/run/pvesnap/transfer/dumps +│/root + + ausgang <DIR> 29.06.26 11:42 + + .. <hoch> + eingang <DIR> 29.06.26 11:42 + + holen <DIR> 06.08.26 11:42 + werkzeuge <DIR> 29.06.26 11:42 + + werkzeuge <DIR> 06.08.26 11:42 + LIESMICH.txt 401B 29.06.26 11:42│ notizen.md 3.1K 06.08.26 11:42 + + + + + + + + + + + + + + + + + + + + + 4 Eintraege │ 4 Eintraege + /run/pvesnap/transfer/dumps/ausgang + + Tab Fenster | Enter Oeffnen | Leer Markieren | F5 Kopieren + + F3 Ansehen | F7 Neuer Ordner | F2 Laufwerk | F10 Zurueck + \ No newline at end of file diff --git a/handbuch/docs/bilder/transfer-neu.svg b/handbuch/docs/bilder/transfer-neu.svg new file mode 100644 index 0000000..0d91f19 --- /dev/null +++ b/handbuch/docs/bilder/transfer-neu.svg @@ -0,0 +1,17 @@ + + + + + + +root@pve1 — pvesnap-recovery → Laufwerke + + Transfer-Laufwerke - Austausch mit der Wiederherstellung +Name Groesse belegt Zustand Wofuer + + dumps 20.0G 4.0M in Benutzung von 9101 Datenbank-Dumps und Exporte + werkzeuge 4.0G 4.0M frei Skripte, Treiber, Installer + austausch 8.0G 0B am Host eingehaengt Alles andere + + Name (kurz, klein, z.B. dumps): + \ No newline at end of file diff --git a/handbuch/docs/bilder/transfer-uebersicht.svg b/handbuch/docs/bilder/transfer-uebersicht.svg new file mode 100644 index 0000000..19157d1 --- /dev/null +++ b/handbuch/docs/bilder/transfer-uebersicht.svg @@ -0,0 +1,17 @@ + + + + + + +root@pve1 — pvesnap-recovery → Laufwerke + + Transfer-Laufwerke - Austausch mit der Wiederherstellung +Name Groesse belegt Zustand Wofuer + + dumps 20.0G 4.0M in Benutzung von 9101 Datenbank-Dumps und Exporte + werkzeuge 4.0G 4.0M frei Skripte, Treiber, Installer + austausch 8.0G 0B am Host eingehaengt Alles andere + + Enter Oeffnen | n Neu | u Aushaengen | l Loeschen | r Aktualisieren | q Zurueck + \ No newline at end of file diff --git a/handbuch/docs/holen/explorer.md b/handbuch/docs/holen/explorer.md new file mode 100644 index 0000000..9acd3eb --- /dev/null +++ b/handbuch/docs/holen/explorer.md @@ -0,0 +1,164 @@ +# Der Explorer + +```bash +pvesnap-explorer # Gast und Snapshot auswählen +pvesnap-explorer 101 # Snapshot von VM 101 auswählen +pvesnap-explorer 101 # direkt öffnen +``` + +Braucht `root` — Snapshots einbinden und mounten geht nicht anders. + +--- + +## Drei Ebenen + +
+ +**Gastauswahl** → **Snapshot-Auswahl** → **Dateien** + +
+ +Mit ++q++ geht es jeweils eine Ebene zurück. Beim Verlassen des Dateibrowsers +wird der Snapshot wieder ausgehängt, sodass man gleich den nächsten öffnen kann. + +### 1. Welcher Gast? + +![Gastauswahl](../bilder/explorer-gastauswahl.svg) + +Alle VMs und Container des Clusters, mit Zustand und Node. Ein gestoppter Gast +ist kein Hindernis — sein Snapshot lässt sich genauso lesen. + +### 2. Welcher Snapshot? + +![Snapshot-Auswahl](../bilder/explorer-snapshotauswahl.svg) + +Neueste zuerst, mit Datum und Beschreibung. Die Beschreibung ist der Grund, +warum es sich lohnt, sie in der Konfiguration ordentlich zu setzen — hier sucht +man später danach. + +Halbfertige Einträge aus abgebrochenen Läufen werden ausgeblendet; wenn welche +da sind, sagt eine Meldung, wie man sie loswird. + +### 3. Die Dateien + +![Der Explorer mit zwei Fenstern](../bilder/explorer-fenster.svg) + +Links der Snapshot, rechts der lokale Rechner. Oben steht, welcher Gast und +welcher Snapshot offen ist, unten der volle Pfad des markierten Eintrags. + +--- + +## Tastenbelegung + +| Taste | | +|---|---| +| ++tab++ | Fenster wechseln | +| ++up++ ++down++ | bewegen | +| ++enter++ | Verzeichnis öffnen oder Datei ansehen | +| ++backspace++ ++left++ | ein Verzeichnis hoch | +| ++home++ ++end++ | Anfang / Ende | +| ++page-up++ ++page-down++ | seitenweise | +| ++space++ | markieren | +| ++asterisk++ | Markierung umkehren | +| ++a++ / ++u++ | alle markieren / keine | +| ++f5++ oder ++c++ | Markiertes ins andere Fenster kopieren | +| ++f3++ oder ++v++ | Datei ansehen (Text oder Hex) | +| ++f7++ oder ++n++ | neues Verzeichnis (nur im lokalen Fenster) | +| ++f6++ oder ++g++ | Verzeichnis direkt eingeben | +| ++f2++ oder ++m++ | anderes Dateisystem des Snapshots wählen | +| ++r++ | neu einlesen | +| ++f10++ oder ++q++ | eine Ebene zurück | +| ++question++ oder ++f1++ | Hilfe | + +Die Hilfe ist jederzeit erreichbar: + +![Die Tastenhilfe](../bilder/explorer-hilfe.svg) + +--- + +## Kopieren + +Markieren mit ++space++ — markierte Einträge stehen in Gelb: + +![Markierte Dateien](../bilder/explorer-markiert.svg) + +Dann ++f5++: + +![Der Kopierdialog](../bilder/explorer-kopieren.svg) + +Kopiert wird **immer vom aktiven ins andere Fenster**. In den Snapshot hinein +geht nichts — er ist schreibgeschützt eingebunden. + +Während des Kopierens läuft ein Fortschrittsbalken; ++esc++ bricht ab. Bereits +kopierte Dateien bleiben dabei liegen, die gerade laufende wird entfernt, damit +kein halbes Stück zurückbleibt. + +Existiert eine Datei am Ziel schon, wird gefragt: + +| Antwort | | +|---|---| +| `ja` | diese überschreiben | +| `nein` | diese überspringen | +| `alle` | alles Weitere überschreiben, nicht mehr fragen | +| `keine` | alles Weitere überspringen | + +**Was übersprungen wird:** Gerätedateien, Sockets und FIFOs. Symbolische +Verweise bleiben Verweise — sie werden nicht aufgelöst. + +!!! tip "Aus mehreren Snapshots ins selbe Ziel" + + Das rechte Fenster behält sein Verzeichnis, auch wenn man zurückgeht und + den nächsten Snapshot öffnet. Damit lassen sich mehrere Stände bequem + nebeneinander sammeln — etwa dieselbe Konfigurationsdatei von gestern, + vorgestern und vom Monatsersten. + +--- + +## Dateien ansehen + +++f3++ zeigt eine Datei, ohne sie zu kopieren: + +![Eine Datei ansehen](../bilder/explorer-ansehen.svg) + +Text wird als Text dargestellt, alles andere als Hexdump. Das reicht meist +schon, um die Frage „ist das die richtige Fassung?" zu beantworten — ohne die +Datei erst auf den Host zu holen. + +--- + +## Mehrere Dateisysteme + +Eine VM hat oft mehr als eine Partition: `/` und `/boot`, dazu vielleicht ein +eigenes Datenlaufwerk. Hat der Snapshot mehrere brauchbare Dateisysteme, fragt +der Explorer beim Öffnen, welches er zeigen soll. + +Wechseln geht später mit ++f2++, ohne den Snapshot zu schließen. + +!!! note "Was nicht auftaucht" + + Swap-Partitionen, EFI-Partitionen ohne lesbaren Inhalt und alles, was der + Kernel nicht kennt. LVM innerhalb der VM wird erkannt und aktiviert; + verschlüsselte Volumes bleiben verschlüsselt. + +--- + +## Aufrufparameter + +| Parameter | Vorgabe | Bedeutung | +|---|---|---| +| `vmid` | — | Gast direkt vorwählen | +| `snapshot` | — | Snapshot direkt vorwählen | +| `-l`, `--local ` | aktuelles Verzeichnis | Startverzeichnis im rechten Fenster | +| `-p`, `--path ` | — | beliebiges Verzeichnis öffnen, ohne Proxmox zu fragen | +| `--cleanup` | — | hängengebliebene Einbindungen lösen und beenden | +| `-v`, `--verbose` | — | ausführliches Protokoll | +| `-V`, `--version` | — | Version anzeigen | + +```bash +pvesnap-explorer --local /srv/restore # rechtes Fenster startet dort +pvesnap-explorer --path /mnt/x # ohne Proxmox, zum Ausprobieren +pvesnap-explorer --cleanup # nach einem Absturz aufräumen +``` + +`--path` ist auch der Weg, den Commander auf etwas ganz anderes loszulassen — +ein gemountetes Backup etwa. Dann braucht es weder Proxmox noch root. diff --git a/handbuch/docs/holen/index.md b/handbuch/docs/holen/index.md new file mode 100644 index 0000000..8346605 --- /dev/null +++ b/handbuch/docs/holen/index.md @@ -0,0 +1,95 @@ +# Daten zurückholen + +Ein Snapshot ist kein Verzeichnis. Er liegt als Blockgerät oder als Eintrag im +qcow2-Kopf im Storage, und dort kann man nicht einfach hineinsehen. + +pvesnap nimmt einem das ab: Der Snapshot wird **schreibgeschützt** eingebunden +und gemountet, und danach ist er ein ganz gewöhnlicher Pfad. + +--- + +## Drei Wege hinein + +| | wofür | +|---|---| +| [**Der Explorer**](explorer.md) | Auf dem Host, im Terminal. Zwei Fenster wie im Midnight Commander. Der schnellste Weg, wenn man ohnehin per SSH angemeldet ist. | +| [**Die Web-Oberfläche**](web.md) | Im Browser, auch vom Arbeitsplatz. Anmeldung mit den Proxmox-Benutzern. Verzeichnisse kommen als ZIP. | +| [**Als Maschine starten**](../wiederherstellen/index.md) | Wenn Dateien nicht reichen — eine Datenbank etwa wird erst brauchbar, wenn ihr Server läuft und selbst einen Dump schreibt. | + +Die ersten beiden lesen nur. Sie können nichts kaputt machen. + +--- + +## Was dabei im Hintergrund passiert + +Je nach Storage geht es unterschiedlich hinein: + +| Storage | Weg | +|---|---| +| `rbd` (Ceph) | `rbd map pool/image@snap`; bei nicht unterstützten Image-Features über `rbd-nbd` | +| `zfspool` | Container direkt über `.zfs/snapshot/…`, VMs über einen temporären Klon | +| `lvmthin`, `lvm` | die Snapshot-LV `snap__` aktivieren | +| `dir`, `nfs`, `cifs` | `qemu-nbd --load-snapshot` — nur bei qcow2 | + +Danach werden die Partitionen erkannt und gemountet, und zwar mit +`ro,noload` bzw. `ro,norecovery,nouuid`. + +!!! info "Warum diese Optionen wichtig sind" + + Der Snapshot einer **laufenden** Maschine hat fast immer ein unsauberes + Journal — die VM war ja mitten in der Arbeit. Ohne `noload` bzw. + `norecovery` würde der Kernel das Journal abspielen wollen, also + **schreiben**. In einen Snapshot, der schreibgeschützt sein soll. + + Deshalb wird bewusst darauf verzichtet. Die Folge: Die letzten Sekunden vor + dem Snapshot können in den Dateien fehlen, auch wenn sie im Journal stünden. + Für eine Datenbank ist das der Grund, den Weg über + [`pvesnap-recovery`](../wiederherstellen/index.md) zu nehmen — dort startet + der Datenbankserver und räumt selbst auf. + +--- + +## Aufräumen + +Beim Beenden wird alles wieder ausgehängt — automatisch, auch beim Verlassen +über ++q++ oder ++ctrl+c++. + +Bleibt nach einem Absturz etwas hängen: + +```bash +pvesnap-explorer --cleanup +pvesnap-web --cleanup +``` + +Beide räumen dieselben Reste weg: Einbindungen unter `/run/pvesnap/`, +`rbd`-Zuordnungen, NBD-Geräte, temporäre ZFS-Klone. + +Nachsehen, ob wirklich nichts mehr liegt: + +```bash +findmnt | grep pvesnap +rbd showmapped +losetup -a +ls /run/pvesnap/ +``` + +--- + +## Was nicht geht + +!!! warning "Schreiben" + + In einen Snapshot hinein geht nichts. Kein Kopieren, kein Löschen, kein + Umbenennen. Das ist keine Vorsichtsmaßnahme, sondern eine Eigenschaft der + Sache: Ein Snapshot ist ein festgehaltener Stand. + + Wer einen Stand **ändern** will, um daraus weiterzuarbeiten, braucht + [`pvesnap-recovery`](../wiederherstellen/index.md) — dort entsteht ein Klon, + der voll beschreibbar ist. + +!!! warning "Verschlüsselte Dateisysteme" + + LUKS, BitLocker oder eine verschlüsselte Datenbankpartition bleiben auch im + Snapshot verschlüsselt. Es gibt keinen Weg daran vorbei — die Daten müssen + im laufenden Gast entschlüsselt werden. Auch das ist ein Fall für + `pvesnap-recovery`. diff --git a/handbuch/docs/holen/web.md b/handbuch/docs/holen/web.md new file mode 100644 index 0000000..d18143a --- /dev/null +++ b/handbuch/docs/holen/web.md @@ -0,0 +1,223 @@ +# Die Web-Oberfläche + +`pvesnap-web` zeigt Snapshots im Browser. Gedacht für die Fälle, in denen der +[Explorer](explorer.md) nicht passt: Jemand ohne SSH-Zugang soll etwas +herausbekommen, oder die Dateien sollen direkt auf dem Arbeitsplatzrechner +landen statt auf dem Hypervisor. + +Sie liest **ausschließlich**. Ändern oder löschen kann sie nichts. + +--- + +## Als Dienst einrichten + +```bash +./install.sh --with-webexplorer --port 8823 +``` + +Danach läuft die Oberfläche dauerhaft auf `http://:8823/` und startet mit +dem System. + +```bash +./install.sh --with-webexplorer --port 8443 --bind 10.0.0.5 # nur auf einer IP +./install.sh --with-webexplorer --port 8823 --no-start # erst später starten +``` + +Alle Aufrufparameter des Dienstes stehen in **`/etc/pvesnap/web.conf`**: + +``` +PVESNAP_WEB_ARGS=--bind 0.0.0.0 --port 8823 --auth pve +``` + +Dort lässt sich nachträglich alles ändern — Port, Adresse, Anmeldeart, Rechte: + +```bash +nano /etc/pvesnap/web.conf +systemctl restart pvesnap-web +``` + +### Dienst verwalten + +```bash +systemctl status pvesnap-web # läuft er? +systemctl restart pvesnap-web # nach Änderungen in web.conf +systemctl stop pvesnap-web # anhalten (hängt offene Snapshots aus) +systemctl disable pvesnap-web # nicht mehr automatisch starten +journalctl -u pvesnap-web -f # Protokoll, auch fehlgeschlagene Anmeldungen +``` + +--- + +## Von Hand starten + +Für einen einmaligen Einsatz, ohne etwas zu installieren: + +```bash +pvesnap-web # Gast und Snapshot im Browser wählen +pvesnap-web 101 # nur diesen Gast anbieten +pvesnap-web 101 auto-taeglich-20260809-023000 # Snapshot gleich öffnen +``` + +++ctrl+c++ beendet und hängt den Snapshot wieder aus. + +--- + +## Wer darf was sehen? + +Das ist der Punkt, an dem sich diese Oberfläche von einem schnell +hingeworfenen Dateiserver unterscheidet. + +### Anmeldung mit den Proxmox-Benutzern + +Die Oberfläche zeigt dieselbe Anmeldemaske wie Proxmox — Benutzername, +Passwort, Realm-Auswahl. Geprüft wird über `POST /access/ticket` auf der +lokalen Proxmox-API, also genau den Weg, den auch das Proxmox-Webinterface +geht. Alle Realms (`pam`, `pve`, LDAP, AD …) funktionieren damit automatisch. + +Zwei-Faktor-Anmeldungen werden **abgewiesen** statt halb durchgewinkt. + +### Ein Passwort allein reicht nicht + +Sonst könnte jeder Proxmox-Benutzer sämtliche Dateien aller Gäste lesen. +Zusätzlich muss der Benutzer auf dem jeweiligen Gast das Recht **`VM.Snapshot`** +besitzen — geprüft auf `/`, `/vms`, `/vms/` und dem Pool des Gastes. + +In der Gastliste tauchen nur Gäste auf, für die das zutrifft. `root@pam` sieht +wie in Proxmox alles. + +Anpassen: + +``` +--require-privilege VM.Backup # anderes Recht verlangen +--allow-user backup@pve # einzelne Benutzer generell zulassen +--auth token # stattdessen Schlüssel in der Adresse +--auth none # ohne Anmeldung (nur im vertrauten Netz) +``` + +Ohne Proxmox (`--path`) gibt es keine Benutzer — dort wird automatisch ein +Zugangsschlüssel erzeugt und beim Start als vollständige Adresse ausgegeben. + +--- + +## Bedienung + +Snapshot anklicken, durchklicken. + +| | | +|---|---| +| **Einzelne Datei** | wird direkt heruntergeladen | +| **Verzeichnis** | kommt als ZIP | +| **Mehrfachauswahl** | ebenfalls als ZIP | + +Die ZIP-Dateien werden **im Strom erzeugt** — es wird nichts auf dem Host +zwischengespeichert. Ein 200-GB-Verzeichnis braucht also keinen freien Platz, +nur Geduld. + +--- + +## Parameter + +| Parameter | Vorgabe | Bedeutung | +|---|---|---| +| `-P`, `--port ` | `8823` | Port, auf dem gelauscht wird | +| `-b`, `--bind ` | `0.0.0.0` | Netzwerkadresse; `127.0.0.1` = nur lokal | +| `-a`, `--auth pve\|token\|none` | `pve` | Anmeldeart | +| `--require-privilege ` | `VM.Snapshot` | nötiges Recht auf dem Gast | +| `--allow-user ` | — | dieser Benutzer darf alles (mehrfach möglich) | +| `-t`, `--token ` | zufällig | Zugangsschlüssel selbst vorgeben | +| `--no-token` | — | Kurzform für `--auth none` | +| `-p`, `--path ` | — | beliebiges Verzeichnis statt eines Snapshots | +| `--cleanup` | — | hängengebliebene Einbindungen lösen und beenden | +| `-v`, `--verbose` | — | ausführliches Protokoll | +| `-V`, `--version` | — | Version anzeigen | + +--- + +## Beispiele + +=== "Über einen SSH-Tunnel" + + Der sicherste Weg — nichts liegt im Netz offen: + + ```bash + # auf dem Host + pvesnap-web --bind 127.0.0.1 --port 8823 + + # am eigenen Rechner + ssh -L 8823:localhost:8823 root@pve + # dann im Browser: http://localhost:8823/ + ``` + + Als Dienst: in `web.conf` `--bind 127.0.0.1` setzen. + +=== "Ein Team ohne Snapshot-Rechte" + + ```bash + pvesnap-web --port 8823 --require-privilege VM.Backup + ``` + + Wer sichern darf, darf auch aus einer Sicherung lesen — das ist meist die + passendere Grenze. + +=== "Ein einzelner Dienstleister" + + ```bash + pvesnap-web --port 8823 --allow-user dienstleister@pve + ``` + + Unabhängig von den Proxmox-Rechten. Danach wieder entfernen. + +=== "Schnell etwas herausgeben" + + ```bash + pvesnap-web --port 8823 --auth token + ``` + + Der Zugangsschlüssel steht in der Adresse, die beim Start ausgegeben wird. + Kein Proxmox-Benutzer nötig. + +=== "Ein beliebiges Verzeichnis" + + ```bash + pvesnap-web --path /mnt/restore --port 8823 + ``` + + Etwa ein bereits gemountetes Backup. Braucht kein Proxmox. + +--- + +## Sicherheit + +!!! danger "HTTP, nicht HTTPS" + + Die Verbindung ist **unverschlüsselt**. Passwörter gehen im Klartext über + das Netz. + + Über unsichere Netze deshalb immer einen SSH-Tunnel legen — siehe oben. + Innerhalb eines vertrauenswürdigen Verwaltungsnetzes ist es vertretbar, an + einer offenen Firewall-Regel nach draußen nicht. + +Was sonst gilt: + +* Es wird ausschließlich **gelesen**. Die Oberfläche kann nichts ändern oder + löschen. +* Pfade außerhalb des eingebundenen Snapshots werden abgewiesen — über die + Adresszeile kommt man nicht an den Rest des Hosts. +* **Fehlgeschlagene Anmeldungen** landen mit Absender-IP im Journal: + + ```bash + journalctl -u pvesnap-web | grep -i "anmeldung\|failed" + ``` + +* Beim Beenden (++ctrl+c++ oder `systemctl stop pvesnap-web`) wird ein offener + Snapshot wieder ausgehängt. + +!!! tip "Nicht dauerhaft laufen lassen, wenn nicht nötig" + + Wer die Oberfläche nur gelegentlich braucht, installiert sie ohne + Autostart: + + ```bash + systemctl disable pvesnap-web + systemctl start pvesnap-web # bei Bedarf + ``` diff --git a/handbuch/docs/index.md b/handbuch/docs/index.md new file mode 100644 index 0000000..4698a7e --- /dev/null +++ b/handbuch/docs/index.md @@ -0,0 +1,107 @@ +# pvesnap + +**pvesnap** legt nach Zeitplan Snapshots von Proxmox-VMs und -Containern an, +räumt alte wieder weg — und hilft danach dabei, an die Daten darin zu kommen. + +Das ist der Teil, den andere Werkzeuge gern offen lassen. Einen Snapshot +anzulegen ist leicht. Ihn im Ernstfall zu *benutzen*, wenn die Datenbank +gelöscht wurde und der Chef daneben steht, ist die eigentliche Arbeit. Dafür +gibt es hier drei Wege — vom einzelnen Verzeichnis bis zur kompletten Maschine, +die läuft, als wäre nie etwas gewesen. + +--- + +## Die vier Werkzeuge + +
+ +- **`pvesnap`** — der Dienst + + Legt Snapshots nach Zeitplan an und löscht sie nach Vorhaltezeit wieder. + Eingestellt wird alles in einer INI-Datei oder im + [Konfigurationseditor](sichern/editor.md). + +- **`pvesnap-explorer`** — zwei Fenster im Terminal + + Wie der Midnight Commander: links der Snapshot, rechts der Host. Dateien + herüberkopieren, fertig. → [Kapitel](holen/explorer.md) + +- **`pvesnap-web`** — im Browser + + Dasselbe im Webbrowser, auch vom Arbeitsplatz aus, mit den Proxmox-Benutzern + als Anmeldung. Verzeichnisse kommen als ZIP. → [Kapitel](holen/web.md) + +- **`pvesnap-recovery`** — der Snapshot als laufende Maschine + + Startet den Snapshot als eigenständige VM — abgeschottet zum Hineinschauen + oder mit vollem Netz als echte Wiederherstellung. + → [Kapitel](wiederherstellen/index.md) + +
+ +--- + +## Wo fange ich an? + +| Ich will … | → | +|---|---| +| das Ganze erst einmal zum Laufen bringen | [Schnellstart](schnellstart.md) | +| es sauber installieren | [Installation](installation.md) | +| einen Zeitplan einrichten | [Sichern](sichern/index.md) | +| eine einzelne Datei zurückholen | [Der Explorer](holen/explorer.md) | +| jemandem ohne Shell-Zugang etwas herausgeben | [Die Web-Oberfläche](holen/web.md) | +| einen Datenbank-Dump aus einem Snapshot ziehen | [Wiederherstellen](wiederherstellen/index.md) | +| wissen, was ein bestimmter Parameter tut | [Nachschlagen](nachschlagen/index.md) | +| verstehen, warum etwas nicht geht | [Fehlersuche](nachschlagen/fehlersuche.md) | + +--- + +## Das Wichtigste vorweg + +!!! warning "Ein Snapshot ist kein Backup" + + Snapshots liegen auf demselben Storage wie die VM. Stirbt das Storage, + sind beide weg. pvesnap ersetzt kein `vzdump` und keinen Proxmox Backup + Server — es ergänzt sie um etwas, das die nicht können: einen Stand von vor + zwanzig Minuten, in Sekunden verfügbar. + + Die beiden Werkzeuge lösen unterschiedliche Probleme: + + | | Backup | Snapshot | + |---|---|---| + | Schützt vor | Ausfall des Storages, Brand, Verschlüsselung | Fehlbedienung, missratenem Update, gelöschter Tabelle | + | Liegt | woanders | daneben | + | Zurück in | Stunden | Sekunden | + +!!! tip "pvesnap fasst fremde Snapshots nie an" + + Gelöscht wird ausschließlich, was exakt auf das eigene + [Namensschema](sichern/index.md#namensschema) passt. Ein von Hand angelegter + Snapshot `vor-update` bleibt liegen, bis du ihn selbst entfernst. + +--- + +## Ein Blick voraus + +So sieht die Übersicht aus, wenn zwei Snapshots als Maschinen laufen: + +![Übersicht der Wiederherstellungen](bilder/recovery-uebersicht.svg) + +Und so der Weg an die Dateien darin — links der Snapshot, rechts der Host: + +![Der Explorer mit zwei Fenstern](bilder/explorer-fenster.svg) + +--- + +## Voraussetzungen + +* **Proxmox VE**, Version 7 oder neuer +* **root** auf dem Host — `pvesh` und das Einbinden von Snapshots verlangen es +* **Python 3.9+** — bringt Proxmox selbst mit +* Ein Storage, das Snapshots kann: Ceph/RBD, ZFS, LVM-thin oder qcow2 auf + `dir`/`nfs`. Was jeweils geht, steht unter + [Speicherarten](nachschlagen/speicher.md). + +Der Dienst selbst braucht **keine zusätzlichen Python-Pakete**. Für die +Austauschlaufwerke werden `parted` und `exfatprogs` nachinstalliert — das +erledigt das Installationsskript. diff --git a/handbuch/docs/installation.md b/handbuch/docs/installation.md new file mode 100644 index 0000000..2e4f858 --- /dev/null +++ b/handbuch/docs/installation.md @@ -0,0 +1,156 @@ +# Installation + +## Der übliche Weg + +Auf dem Proxmox-Host als `root`: + +```bash +git clone pvesnap && cd pvesnap +./install.sh # nur der Snapshot-Dienst +./install.sh --with-webexplorer --port 8823 # zusätzlich die Web-Oberfläche +./install.sh --help # alle Optionen +``` + +Das Skript prüft zuerst, ob es wirklich auf einem Proxmox-VE-Host läuft, und +bricht sonst ab. Wer es trotzdem will — etwa zum Ausprobieren in einer VM — +nimmt `--force`. + +### Optionen + +| Option | | +|---|---| +| `--with-webexplorer` | Web-Oberfläche als Dienst einrichten (**erfordert `--port`**) | +| `--port ` | Port der Web-Oberfläche | +| `--bind ` | Adresse, auf der sie lauscht (Vorgabe `0.0.0.0`) | +| `--no-start` | installieren, aber Dienste nicht starten | +| `--no-install-deps` | fehlende Werkzeuge (`parted`, `exfatprogs`) nicht nachinstallieren | +| `--force` | auch ohne erkanntes Proxmox VE installieren | + +`--port` hat bewusst **keine Vorgabe**. Es gibt keine Portnummer, die auf jedem +Host frei ist, und ein stillschweigend gewählter Port wäre genau die Art +Überraschung, die man auf einem Hypervisor nicht braucht. + +--- + +## Was installiert wird + +| Pfad | Inhalt | +|---|---| +| `/usr/lib/pvesnap/` | Programmcode | +| `/usr/local/bin/pvesnap` | Dienst und Konfiguration | +| `/usr/local/bin/pvesnap-explorer` | Zwei-Fenster-Explorer | +| `/usr/local/bin/pvesnap-web` | Web-Oberfläche | +| `/usr/local/bin/pvesnap-recovery` | Snapshot als Maschine starten | +| `/etc/pvesnap/pvesnap.conf` | Konfiguration — bei Updates **nicht** überschrieben | +| `/etc/pvesnap/web.conf` | Port und Anmeldeart der Web-Oberfläche | +| `/var/lib/pvesnap/state.json` | merkt sich die letzten Läufe | +| `/var/lib/pvesnap/transfer/` | [Austauschlaufwerke](wiederherstellen/transfer.md) | +| `/etc/systemd/system/pvesnap.service` | Unit des Snapshot-Dienstes | +| `/etc/systemd/system/pvesnap-web.service` | Unit der Web-Oberfläche | + +### Zusätzliche Pakete + +Zwei Werkzeuge werden bei Bedarf nachinstalliert: + +| Paket | wofür | +|---|---| +| `parted` | Partitionstabelle der Austauschlaufwerke | +| `exfatprogs` | deren Dateisystem (exFAT) | + +Ohne sie funktioniert alles außer den Austauschlaufwerken. Mit +`--no-install-deps` bleibt das System unangetastet; die Laufwerksverwaltung +sagt dann beim Öffnen, was fehlt. + +!!! info "Warum kein `ntfs-3g`?" + + exFAT reicht: Windows und Linux lesen und schreiben es von Haus aus, es + kennt keine 4-GB-Grenze pro Datei und speichert keine Besitzrechte — was + beim Austausch zwischen Systemen ein Vorteil ist, kein Mangel. Mehr dazu + unter [Austauschlaufwerke](wiederherstellen/transfer.md#warum-exfat). + +--- + +## Aktualisieren + +```bash +cd pvesnap +git pull +./install.sh --with-webexplorer --port 8823 +``` + +Dieselben Parameter wie beim ersten Mal — sonst wird die Web-Oberfläche nicht +wieder mit eingerichtet. + +**Die Konfiguration bleibt.** `/etc/pvesnap/pvesnap.conf` wird nie +überschrieben. + +**Eine laufende Web-Oberfläche wird sauber angehalten**, bevor der Code +ausgetauscht wird, und danach wieder gestartet. Das ist wichtig, weil sie +möglicherweise noch einen Snapshot eingebunden hat — den Prozess einfach unter +den Füßen wegzuziehen würde eine Einbindung zurücklassen. + +Sitzt ein **fremder** Prozess auf dem Port, gibt es eine Warnung samt PID, aber +kein automatisches Beenden. Es könnte schließlich etwas ganz anderes sein. + +--- + +## Deinstallation + +```bash +./uninstall.sh # Programm und Dienst entfernen, Konfiguration bleibt +./uninstall.sh --purge # zusätzlich /etc/pvesnap und /var/lib/pvesnap löschen +``` + +!!! danger "Was `--purge` mitnimmt" + + Auch die **Austauschlaufwerke** unter `/var/lib/pvesnap/transfer/`. Was + dort an Werkzeugen und Dumps liegt, ist danach weg. Ohne `--purge` bleibt + alles liegen. + +**Bereits angelegte Snapshots werden nie angerührt** — weder beim Entfernen +noch beim Aktualisieren. Sie gehören Proxmox, nicht pvesnap. + +Laufen noch [Wiederherstellungen](wiederherstellen/index.md), warnt +`uninstall.sh` davor. Die sollten vorher mit `pvesnap-recovery destroy` +verworfen werden, sonst bleiben Klone und geschützte Snapshots liegen. + +--- + +## Die systemd-Unit + +Ein Punkt, der später viel Zeit sparen kann: Die mitgelieferte Unit enthält +**bewusst keine Sandbox-Optionen**. + +```ini +# NICHT in die Unit aufnehmen: +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +``` + +Der Grund ist unangenehm indirekt. `pvesh` führt die Proxmox-API im eigenen +Prozess aus — der Snapshot-Task ist also ein *Kindprozess von pvesnap* und erbt +dessen Einschränkungen. `ProtectSystem=` hängt `/etc` schreibgeschützt ein, und +pmxcfs legt seine Sperren als Verzeichnisse unter `/etc/pve/priv/lock/` an. Das +`mkdir` scheitert, Proxmox versucht es erfolglos weiter — und meldet am Ende +keinen Rechtefehler, sondern: + +``` +TASK ERROR: cfs-lock 'storage-data' error: got lock request timeout +``` + +Man sucht dann tagelang am Storage. `pvesnap check` weist von sich aus darauf +hin; mehr dazu unter [Fehlersuche](nachschlagen/fehlersuche.md#storage-lock). + +--- + +## Nach der Installation + +```bash +systemctl status pvesnap # läuft der Dienst? +pvesnap check # ist die Konfiguration stimmig? +journalctl -u pvesnap -f # was tut er gerade? +``` + +Weiter geht es beim [Schnellstart](schnellstart.md#2-gaste-markieren) oder +direkt im Kapitel [Sichern](sichern/index.md). diff --git a/handbuch/docs/nachschlagen/befehle.md b/handbuch/docs/nachschlagen/befehle.md new file mode 100644 index 0000000..3bd50b7 --- /dev/null +++ b/handbuch/docs/nachschlagen/befehle.md @@ -0,0 +1,243 @@ +# Alle Befehle + +## `pvesnap` + +Der Snapshot-Dienst und seine Verwaltung. + +```bash +pvesnap [globale Optionen] [optionen] +``` + +### Globale Optionen + +| Option | Vorgabe | | +|---|---|---| +| `-c`, `--config ` | `/etc/pvesnap/pvesnap.conf` | andere Konfiguration verwenden (auch über `PVESNAP_CONFIG`) | +| `-n`, `--dry-run` | — | nichts wirklich anlegen oder löschen, nur anzeigen | +| `-v`, `--verbose` | — | ausführliche Ausgabe | +| `-V`, `--version` | — | Version anzeigen | + +### Unterbefehle + +| Befehl | | +|---|---| +| `status` | Übersicht über Gruppen, letzte und nächste Läufe — **auch die Vorgabe ohne Befehl** | +| `vms` | alle VMs und Container mit den Gruppen, in denen sie stecken | +| `list` | vorhandene pvesnap-Snapshots | +| `check` | Konfiguration prüfen | +| `config` (oder `edit`) | [ncurses-Editor](../sichern/editor.md) | +| `run` | fällige Gruppen jetzt ausführen | +| `prune` | nur aufräumen, nichts anlegen | +| `daemon` | Dienst im Vordergrund starten (für systemd) | + +### `run` + +| Option | | +|---|---| +| `-g`, `--group ` | nur diese Gruppe (mehrfach möglich) | +| `-f`, `--force` | unabhängig vom Zeitplan ausführen | +| `--no-prune` | nicht aufräumen | + +```bash +pvesnap run # was fällig ist +pvesnap run --force -g taeglich # diese Gruppe sofort +pvesnap run --force --dry-run # Probelauf +pvesnap run -g taeglich -g monatlich # zwei Gruppen +``` + +### `prune` + +| Option | | +|---|---| +| `-g`, `--group ` | nur diese Gruppe | + +### `list` + +| Option | | +|---|---| +| `-g`, `--group ` | nur diese Gruppe | +| `-a`, `--all` | auch fremde und von Hand angelegte Snapshots anzeigen | + +--- + +## `pvesnap-explorer` + +```bash +pvesnap-explorer [vmid] [snapshot] [optionen] +``` + +| Parameter | Vorgabe | | +|---|---|---| +| `vmid` | — | Gast direkt vorwählen | +| `snapshot` | — | Snapshot direkt vorwählen | +| `-l`, `--local ` | aktuelles Verzeichnis | Startverzeichnis im rechten Fenster | +| `-p`, `--path ` | — | beliebiges Verzeichnis öffnen, ohne Proxmox zu fragen | +| `--cleanup` | — | hängengebliebene Einbindungen lösen und beenden | +| `-v`, `--verbose` | — | ausführliches Protokoll | +| `-V`, `--version` | — | Version anzeigen | + +```bash +pvesnap-explorer # alles auswählen +pvesnap-explorer 101 # Snapshot von VM 101 wählen +pvesnap-explorer 101 auto-taeglich-20260809-023000 # direkt öffnen +pvesnap-explorer --local /srv/restore # rechtes Fenster startet dort +pvesnap-explorer --path /mnt/x # ohne Proxmox +pvesnap-explorer --cleanup # aufräumen +``` + +Braucht `root`, außer bei `--path`. + +--- + +## `pvesnap-web` + +```bash +pvesnap-web [vmid] [snapshot] [optionen] +``` + +| Parameter | Vorgabe | | +|---|---|---| +| `-P`, `--port ` | `8823` | Port, auf dem gelauscht wird | +| `-b`, `--bind ` | `0.0.0.0` | Netzwerkadresse; `127.0.0.1` = nur lokal | +| `-a`, `--auth pve\|token\|none` | `pve` | Anmeldeart | +| `--require-privilege ` | `VM.Snapshot` | nötiges Recht auf dem Gast | +| `--allow-user ` | — | dieser Benutzer darf alles (mehrfach möglich) | +| `-t`, `--token ` | zufällig | Zugangsschlüssel selbst vorgeben | +| `--no-token` | — | Kurzform für `--auth none` | +| `-p`, `--path ` | — | beliebiges Verzeichnis statt eines Snapshots | +| `--cleanup` | — | hängengebliebene Einbindungen lösen und beenden | +| `-v`, `--verbose` | — | ausführliches Protokoll | +| `-V`, `--version` | — | Version anzeigen | + +Als Dienst stehen dieselben Parameter in `/etc/pvesnap/web.conf`: + +``` +PVESNAP_WEB_ARGS=--bind 0.0.0.0 --port 8823 --auth pve +``` + +--- + +## `pvesnap-recovery` + +```bash +pvesnap-recovery # ncurses-Oberfläche +pvesnap-recovery [optionen] +``` + +### Unterbefehle + +| Befehl | | +|---|---| +| `live [snapshot]` | abgeschottet starten (ohne Snapshot: der neueste) | +| `recover [snapshot]` | mit Netzwerk und gleicher Identität starten | +| `list` | vorhandene Wiederherstellungen anzeigen | +| `start ` | starten | +| `stop ` | herunterfahren | +| `console ` | noVNC-Adresse ausgeben | +| `spice ` | Verbindungsdatei für `remote-viewer` | +| `flatten ` | [vom Quell-Snapshot lösen](../wiederherstellen/loesen.md) | +| `cleanup` | [Klone entfernen, zu denen es keinen Gast mehr gibt](../wiederherstellen/verwerfen.md#cleanup) | +| `destroy ` | [restlos verwerfen](../wiederherstellen/verwerfen.md) | + +### Optionen von `live` und `recover` + +| Parameter | | +|---|---| +| `--newid ` | VMID der neuen Maschine (Vorgabe: nächste freie) | +| `--node ` | auf welchem Node sie laufen soll | +| `--net none\|down\|on` | keine Karte / Karte ohne Leitung / voll am Netz | +| `--resume` / `--no-resume` | Arbeitsspeicher laden bzw. bewusst kalt starten | +| `--transfer ` | [Austauschlaufwerk](../wiederherstellen/transfer.md) anhängen (mehrfach möglich) | +| `--spice` | SPICE-Anzeige (`vga: qxl`), zusätzlich zu noVNC | +| `--usb ` | so viele USB-Weiterleitungen über SPICE (schaltet `--spice` mit ein) | +| `--iso ` | Abbild als CD einlegen, z. B. `local:iso/virtio-win.iso` | +| `--memory ` | abweichender Arbeitsspeicher (nicht mit `--resume`) | +| `--cores ` | abweichende Kernzahl (nicht mit `--resume`) | +| `--name ` | Name der neuen Maschine | +| `--keep-binds` | durchgereichte Host-Verzeichnisse des Originals übernehmen | +| `--no-start` | nur einrichten, nicht starten | +| `-y`, `--yes` | Routinefragen überspringen | +| `--force` | auch anlegen, wenn das Original noch läuft | + +!!! danger "`-y` deckt `--force` nicht ab" + + `recover` mit `-y` bei laufendem Original wird **abgewiesen** (Rückgabewert + 2). Siehe [Mit Netzwerk](../wiederherstellen/netzwerk.md). + +### Optionen der übrigen Befehle + +| Befehl | Option | | +|---|---|---| +| `spice` | `-o ` | in eine Datei schreiben statt auf die Standardausgabe | +| `flatten` | `--force` | auch bei knappem Speicherplatz | +| `cleanup` | `--all` | auch Datenträger ohne Elternteil | +| `cleanup`, `destroy`, `flatten` | `-y`, `--yes` | ohne Rückfrage | + +### Beispiele + +```bash +# Kurz hineinschauen, mit Werkzeugen +pvesnap-recovery live 101 --transfer werkzeuge + +# Ein bestimmter Snapshot, kalt gestartet, mit Dongle +pvesnap-recovery live 105 auto-taeglich-20260809-023000 --usb 2 --no-resume + +# Ernstfall: Original steht, Wiederherstellung übernimmt +qm stop 101 +pvesnap-recovery recover 101 --newid 9101 +pvesnap-recovery flatten 9101 + +# Verbindungsdatei für den Arbeitsplatz +ssh root@pve2 pvesnap-recovery spice 9104 > vm.vv && remote-viewer vm.vv + +# Aufräumen +pvesnap-recovery destroy 9101 +pvesnap-recovery cleanup +``` + +--- + +## systemd + +```bash +systemctl status pvesnap +systemctl reload pvesnap # Konfiguration neu einlesen (SIGHUP) +systemctl restart pvesnap +journalctl -u pvesnap -f + +systemctl status pvesnap-web +systemctl restart pvesnap-web # nach Änderungen in web.conf +systemctl stop pvesnap-web +journalctl -u pvesnap-web -f +``` + +--- + +## Installation + +```bash +./install.sh [optionen] +./uninstall.sh [--purge] +``` + +| Option | | +|---|---| +| `--with-webexplorer` | Web-Oberfläche als Dienst einrichten (erfordert `--port`) | +| `--port ` | Port der Web-Oberfläche | +| `--bind ` | Adresse, auf der sie lauscht (Vorgabe `0.0.0.0`) | +| `--no-start` | installieren, aber Dienste nicht starten | +| `--no-install-deps` | `parted` und `exfatprogs` nicht nachinstallieren | +| `--force` | auch ohne erkanntes Proxmox VE installieren | +| `--help` | Übersicht | + +--- + +## Hilfswerkzeug + +```bash +tools/get-guest-tools.sh +``` + +Holt die virtio-win-Treiber auf einen ISO-Storage — für Windows-Gäste, die +Treiber brauchen, oder für Zwischenablage und Mauszeiger unter +[SPICE](../wiederherstellen/dongle.md). diff --git a/handbuch/docs/nachschlagen/fehlersuche.md b/handbuch/docs/nachschlagen/fehlersuche.md new file mode 100644 index 0000000..0d57daf --- /dev/null +++ b/handbuch/docs/nachschlagen/fehlersuche.md @@ -0,0 +1,281 @@ +# Fehlersuche + +## Es passiert gar nichts + +Der Dienst läuft, das Protokoll ist ruhig, und trotzdem entstehen keine +Snapshots. + +```bash +pvesnap status # hat eine Gruppe einen nächsten Termin? +pvesnap vms # steht überhaupt eine VM in einer Gruppe? +pvesnap check +``` + +| Ursache | | +|---|---| +| **Alle Gruppen auf `enabled = no`** | So wird ausgeliefert. Der häufigste Fall. | +| Auswahl trifft auf nichts zu | Tag falsch geschrieben? `pvesnap vms` zeigt es. | +| `dry_run = yes` in `[global]` | Es wird nur protokolliert. | +| Konfiguration nach der Änderung nicht neu geladen | `systemctl reload pvesnap` | + +--- + +## Storage-Lock + +``` +trying to acquire cfs lock 'storage-data' ... +TASK ERROR: cfs-lock 'storage-data' error: got lock request timeout +``` + +!!! note "`storage-data` ist kein falsch gelesener Name" + + In pmxcfs heißen Storage-Sperren immer `storage-`. Gemeint ist also + das Storage `data`. + +Proxmox nimmt diese Sperre beim Anlegen eines Snapshots und gibt nach 60 +Sekunden auf, wenn sie jemand anderes hält. + +pvesnap geht damit so um: + +* Der Aufruf **wartet, bis der Proxmox-Task wirklich fertig ist**, bevor die + nächste VM drankommt — sonst würden sich die eigenen Läufe aussperren +* Sperr-Fehler gelten als vorübergehend und werden `retries`-mal mit + `retry_delay` Abstand wiederholt +* Echte Fehler (etwa „storage does not support snapshots") werden **nicht** + wiederholt +* `pause_between = 10s` nimmt zusätzlich Druck vom Storage + +### Der Dienst scheitert, von Hand geht es + +Das ist der Fall, an dem man am längsten sucht — und er hat mit dem Storage +nichts zu tun. + +!!! danger "Sandbox-Optionen in der systemd-Unit" + + `pvesh` führt die Proxmox-API im eigenen Prozess aus. Der Snapshot-Task ist + also ein **Kindprozess von pvesnap** und erbt alles, was in der Unit + eingeschränkt wurde. + + `ProtectSystem=` hängt `/etc` schreibgeschützt ein — und pmxcfs legt seine + Sperren als **Verzeichnisse** unter `/etc/pve/priv/lock/` an. Das `mkdir` + scheitert, Proxmox wiederholt es erfolglos und meldet am Ende einen + Lock-Timeout statt eines Rechtefehlers. + +Prüfen: + +```bash +systemctl show pvesnap -p ProtectSystem -p ProtectHome -p PrivateTmp -p ReadOnlyPaths +pvesnap check # meldet so etwas von sich aus +``` + +Alle müssen leer bzw. `no` sein. Die mitgelieferte Unit enthält deshalb +**bewusst keine** Sandbox-Optionen. + +`ProtectHome=yes` hat denselben Effekt an anderer Stelle: Es ersetzt `/root` +durch ein leeres Verzeichnis — und Proxmox erreicht andere Cluster-Nodes über +die SSH-Schlüssel in `/root/.ssh`. + +### Wenn die Sperre wirklich belegt ist + +```bash +pvesm status # ist das Storage online und erreichbar? +grep -A6 "^[a-z]*: data" /etc/pve/storage.cfg +pvesh get /cluster/tasks --output-format json | head # hängt noch ein Task? +systemctl status pvestatd pve-cluster +journalctl -u pvestatd -n 50 +``` + +Häufigste Ursachen: ein hängender Backup- oder Replikationsjob auf demselben +Storage, ein nicht erreichbares NFS/CIFS-Storage (dann blockiert `pvestatd`), +oder ein abgebrochener Task, der die Sperre nicht freigegeben hat. + +--- + +## `snapshot is protected` + +``` +TASK ERROR: rbd snapshot 'auto-taeglich-20260809-023000' is protected from removal +``` + +Am Snapshot hängt ein Klon — also eine +[Wiederherstellung](../wiederherstellen/index.md). Das ist zunächst richtig so: +Ceph verlangt den Schutz, solange ein Klon existiert. + +| Lage | | +|---|---| +| Die Wiederherstellung wird noch gebraucht | Nichts tun. Der Snapshot bleibt eben liegen. | +| Sie wird nicht mehr gebraucht | `pvesnap-recovery destroy ` | +| Sie soll bleiben | `pvesnap-recovery flatten ` | +| Sie wurde in der **Weboberfläche** gelöscht | `pvesnap-recovery cleanup` | + +Der letzte Fall ist der, den man sonst nicht findet: Der Klon ist weg, der +Schutz steht noch, und die Vorhaltezeit scheitert für immer. + +Nachsehen: + +```bash +rbd -p snap ls vm-101-disk-0 # Spalte PROTECTED +rbd -p ls -l | grep vm-101 # gibt es noch Klone? +``` + +--- + +## `storage does not support snapshots` + +Die VM liegt auf LVM-thick oder auf einem `raw`-Image in einem +Verzeichnis-Storage. Dort kann Proxmox keine Snapshots — pvesnap überspringt sie +und macht mit den übrigen weiter. + +Der Ausweg ist ein Umzug: + +```bash +qm move-disk 101 scsi0 +``` + +Siehe [Speicherarten](speicher.md). + +--- + +## Der Snapshot lässt sich nicht einbinden + +```bash +pvesnap-explorer --cleanup # erst einmal aufräumen +pvesnap-explorer -v 101 # dann mit ausführlichem Protokoll +``` + +| Meldung | | +|---|---| +| `rbd: image uses unsupported features` | Der Kernel kann das Image nicht direkt; pvesnap weicht auf `rbd-nbd` aus. Fehlt das Paket `rbd-nbd`, nachinstallieren. | +| `unknown filesystem type` | Das Dateisystem im Gast kennt der Host nicht (z. B. Btrfs ohne Modul, oder ReFS). | +| `wrong fs type … or superblock corrupt` | Meist eine verschlüsselte Partition (LUKS, BitLocker). Dort führt nur der Weg über eine [laufende Maschine](../wiederherstellen/index.md). | +| Es wird gar kein Dateisystem angeboten | Reine Datenplatte ohne Partitionstabelle, oder LVM im Gast, das nicht aktiviert werden konnte. | + +Reste nach einem Absturz aufspüren: + +```bash +findmnt | grep pvesnap +rbd showmapped +losetup -a +ls /run/pvesnap/ +``` + +--- + +## Die Maschine startet, aber der RAM-Zustand fehlt + +Symptom: Die Wiederherstellung bootet, statt weiterzulaufen. Die Uhr im Gast +zeigt die aktuelle Zeit statt der Snapshot-Zeit. + +!!! warning "Proxmox meldet das als `TASK OK`" + + Schlägt das Laden fehl, quittiert Proxmox den Start trotzdem mit Erfolg und + lässt die Maschine in `prelaunch` stehen. `pvesnap-recovery` liest das + Task-Protokoll mit, setzt sie fort und sagt es deutlich. + +| Meldung im Task-Protokoll | | +|---|---| +| `Size mismatch: vga.vram` | SPICE (`vga: qxl`) und geladener Arbeitsspeicher schließen sich aus. Siehe [Dongle und USB](../wiederherstellen/dongle.md#spice-und-geladener-arbeitsspeicher-schlieen-sich-aus). | +| `Unknown savevm section or instance 'vmgenid'` | Die Geräteausstattung passt nicht zum gespeicherten Zustand. Tritt bei von Hand veränderten Konfigurationen auf. | +| `unable to open vmstate` | Der Arbeitsspeicher-Datenträger ist nicht mehr da — Proxmox gibt ihn nach dem **ersten** Start frei. Ein zweiter Start bootet immer kalt. | + +Der letzte Punkt ist kein Fehler, sondern das erwartete Verhalten. Wer den +warmen Zustand braucht, arbeitet damit **beim ersten Start**. + +--- + +## Das Austauschlaufwerk lässt sich nicht auswerfen + +``` +'dumps' liess sich nicht abziehen: ... +Meist haelt der Gast es noch - dort erst aushaengen +(Linux: umount, Windows: Auswerfen im Explorer). +``` + +Genau das ist es meistens. Im Gast aushängen, dann noch einmal ++e++. + +Weitere Fälle: + +| Meldung | | +|---|---| +| `ist gerade in Benutzung von 9101` | Das Laufwerk steckt in einer anderen Maschine. Dort auswerfen. | +| `ist gerade am Host eingehängt` | Mit ++u++ in der Laufwerksübersicht aushängen. | +| `Bei Containern gibt es nichts auszuwerfen` | Dort ist es ein durchgereichtes Verzeichnis — Host und Gast sehen dieselben Dateien ohnehin gleichzeitig. | + +--- + +## SPICE oder USB gehen nicht + +| Symptom | | +|---|---| +| Maschine startet nicht, `no spice port` | `vga: qxl` fehlt. Detailansicht ++p++ → SPICE ein. | +| Kein Eintrag unter „USB-Geräteauswahl" | Der `remote-viewer` läuft nicht auf dem Rechner, an dem der Dongle steckt. | +| `.vv`-Datei wird abgewiesen | Sie ist zu alt — das Kennwort darin gilt nur wenige Sekunden. Neu erzeugen. | +| `remote-viewer` nicht gefunden | Paket `virt-viewer` installieren. | + +Mehr unter [Dongle und USB](../wiederherstellen/dongle.md#wenn-der-dongle-nicht-auftaucht). + +--- + +## Die Web-Oberfläche zeigt keine Gäste + +Ein angemeldeter Benutzer sieht nur Gäste, auf denen er das Recht +**`VM.Snapshot`** hat. Ist die Liste leer, fehlt genau das. + +```bash +pveum user permissions @ +``` + +Anpassen mit `--require-privilege VM.Backup` oder +`--allow-user @` in `/etc/pvesnap/web.conf`, danach +`systemctl restart pvesnap-web`. + +Fehlgeschlagene Anmeldungen stehen mit Absender-IP im Journal: + +```bash +journalctl -u pvesnap-web -n 50 +``` + +--- + +## Der Port ist belegt + +Beim Installieren: + +``` +Auf Port 8823 laeuft bereits ein anderer Prozess (PID 12345) +``` + +Das ist eine Warnung, kein Abbruch — es könnte etwas ganz anderes sein. Nachsehen: + +```bash +ss -lntp | grep 8823 +``` + +Eine bereits laufende `pvesnap-web` wird dagegen von selbst sauber angehalten +und danach wieder gestartet. + +--- + +## Zeitpläne stimmen nicht + +| Beobachtung | | +|---|---| +| Der Termin liegt eine Stunde daneben | Zeitumstellung. Es wird mit lokaler Zeit gerechnet. | +| Nach einem Neustart kam sofort ein Lauf | Ein verpasster Termin wird nachgeholt — einmal, nicht für jeden ausgefallenen. | +| Im Februar fiel der Monatslauf aus | Sollte nicht passieren: `day_of_month = 31` wird auf den Monatsletzten gezogen. Prüfen mit der Vorschauzeile in der [Gruppenmaske](../sichern/editor.md#eine-gruppe-bearbeiten). | +| Die Snapshots liegen auf krummen Zeiten | `align = no`. Mit `align = yes` richten sie sich an der Uhr aus. | + +--- + +## Wenn gar nichts mehr hilft + +```bash +pvesnap -v check # ausführlich +pvesnap -v run --force --dry-run # zeigt jeden pvesh-Aufruf +journalctl -u pvesnap -n 200 --no-pager +``` + +Mit `log_level = DEBUG` in `[global]` protokolliert der Dienst jeden +`pvesh`-Aufruf mit allen Argumenten. Damit lässt sich der fehlschlagende Aufruf +von Hand nachstellen — und dann sieht man die echte Meldung von Proxmox statt +der aufbereiteten. diff --git a/handbuch/docs/nachschlagen/index.md b/handbuch/docs/nachschlagen/index.md new file mode 100644 index 0000000..49a6d33 --- /dev/null +++ b/handbuch/docs/nachschlagen/index.md @@ -0,0 +1,89 @@ +# Nachschlagen + +Der Teil zum Blättern, wenn man schon weiß, was man sucht. + +
+ +- **[Alle Befehle](befehle.md)** + + Jeder Unterbefehl und jeder Parameter der vier Werkzeuge. + +- **[Alle Einstellungen](konfiguration.md)** + + Jeder Schlüssel der INI-Datei, mit Vorgabe und Bedeutung. + +- **[Tastenkürzel](tasten.md)** + + Alle Tasten aller Oberflächen auf einer Seite. + +- **[Speicherarten](speicher.md)** + + Was Ceph, ZFS, LVM-thin und Datei-Storages jeweils können. + +- **[Fehlersuche](fehlersuche.md)** + + Meldungen, ihre Ursachen und was dagegen hilft. + +
+ +--- + +## Wo was liegt + +| Pfad | | +|---|---| +| `/etc/pvesnap/pvesnap.conf` | die Konfiguration | +| `/etc/pvesnap/web.conf` | Parameter der Web-Oberfläche | +| `/var/lib/pvesnap/state.json` | wann welche Gruppe zuletzt lief | +| `/var/lib/pvesnap/recovery.json` | Merkliste der Wiederherstellungen | +| `/var/lib/pvesnap/transfer/` | Austauschlaufwerke (Abbilddateien) | +| `/run/pvesnap/mnt/` | eingebundene Snapshots | +| `/run/pvesnap/transfer/` | am Host eingehängte Austauschlaufwerke | +| `/usr/lib/pvesnap/` | Programmcode | +| `/usr/local/bin/pvesnap*` | die vier Werkzeuge | + +--- + +## Rückgabewerte + +Für Skripte: + +| Wert | | +|---|---| +| `0` | in Ordnung | +| `1` | abgebrochen (Rückfrage verneint) | +| `2` | Fehler in der Konfiguration oder in den Parametern | +| `3` | Proxmox-Fehler | +| `4` | Laufzeitfehler | +| `130` | mit ++ctrl+c++ abgebrochen | + +--- + +## Aufbau des Programms + +``` +pvesnap/ + config.py INI lesen und schreiben, Gruppen- und Globaleinstellungen + schedule.py Berechnung des nächsten Termins + naming.py Namensschema und Beschreibungs-Vorlagen + proxmox.py pvesh-Anbindung (Inventar, Snapshots anlegen/löschen) + engine.py Auswahl der Gäste, Anlegen, Aufräumen + daemon.py Hauptschleife, Signale, Sperren + state.py merkt sich die letzten Läufe + preflight.py prüft die Umgebung (root, /etc/pve beschreibbar, /root) + cli.py Kommandozeile + curses_util.py gemeinsame curses-Bausteine + tui.py ncurses-Konfigurationseditor + snapfs.py Snapshots einbinden und mounten (rbd/zfs/lvm/qcow2) + explorer.py Zwei-Fenster-Explorer im Terminal + recovery.py Snapshot als Maschine starten (Klone, Konfiguration, Aufräumen) + recovery_ui.py ncurses-Oberfläche und Kommandozeile dazu + transfer.py Austauschlaufwerke: anlegen, ein-/aushängen, Verriegelung + transfer_ui.py deren Bildschirm (Taste v in pvesnap-recovery) + web/ Web-Oberfläche zum Herunterladen +``` + +Alle vier Werkzeuge teilen sich `proxmox.py`. `snapfs.py` liefert Explorer und +Web-Oberfläche einen ganz gewöhnlichen Pfad, sodass sie nichts über Ceph, ZFS +oder LVM wissen müssen; `recovery.py` geht den anderen Weg und überlässt das +Klonen der Storage-Schicht von Proxmox selbst. diff --git a/handbuch/docs/nachschlagen/konfiguration.md b/handbuch/docs/nachschlagen/konfiguration.md new file mode 100644 index 0000000..9ece36a --- /dev/null +++ b/handbuch/docs/nachschlagen/konfiguration.md @@ -0,0 +1,256 @@ +# Alle Einstellungen + +Die Datei liegt unter `/etc/pvesnap/pvesnap.conf` und ist eine gewöhnliche +INI-Datei mit drei Arten von Abschnitten: + +```ini +[global] # gilt für alles +[defaults] # Vorgaben für alle Gruppen (optional) +[group:NAME] # eine Gruppe, beliebig viele davon +``` + +Nach jeder Änderung: + +```bash +pvesnap check +systemctl reload pvesnap +``` + +!!! note "Zeitangaben" + + Überall dasselbe Format: `30s`, `15m`, `1h`, `6h`, `2d12h`, `1w`. + `0` heißt „unbegrenzt" bzw. „keine". + +!!! note "Kommentare am Zeilenende" + + Hinter jedem Wert darf ein Kommentar stehen: + + ```ini + keep_time = 7d # eine Woche + ``` + + Einzige Ausnahme ist `description` — dort bleibt die Zeile unverändert, + damit ein `#` in der Beschreibung erhalten bleibt. + +--- + +## `[global]` + +| Schlüssel | Vorgabe | | +|---|---|---| +| `prefix` | `auto` | Namenspräfix aller pvesnap-Snapshots. Bestimmt zugleich, was jemals gelöscht wird — siehe [Namensschema](../sichern/index.md#namensschema). | +| `check_interval` | `60s` | wie oft der Dienst nach Fälligem schaut | +| `state_file` | `/var/lib/pvesnap/state.json` | merkt sich die letzten Läufe | +| `log_level` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` | +| `log_file` | — | zusätzliche Protokolldatei; ohne sie geht alles ins Journal | +| `task_timeout` | `15m` | Geduld mit einem einzelnen Proxmox-Task | +| `retries` | `2` | zusätzliche Versuche, wenn eine Sperre belegt ist | +| `retry_delay` | `60s` | Wartezeit vor dem nächsten Versuch | +| `pause_between` | `0s` | Pause zwischen zwei Gästen | +| `run_on_start` | `no` | beim Dienststart sofort einen Durchlauf machen | +| `dry_run` | `no` | `yes` = nichts wirklich tun, nur protokollieren | +| `description` | siehe unten | Standard-Beschreibung aller Snapshots | + +```ini +[global] +prefix = auto +check_interval = 60s +state_file = /var/lib/pvesnap/state.json +log_level = INFO +task_timeout = 15m +retries = 2 +retry_delay = 60s +pause_between = 0s +run_on_start = no +dry_run = no +description = pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time} | max: {keep_count} +``` + +--- + +## `[defaults]` + +Vorgaben für **alle** Gruppen. Jede Gruppe darf sie einzeln überschreiben. + +```ini +[defaults] +enabled = yes +skip_stopped = no +vmstate = no +``` + +Erlaubt sind hier dieselben Schlüssel wie in einer Gruppe. + +!!! warning "Der Editor löst `[defaults]` auf" + + Beim Speichern im [ncurses-Editor](../sichern/editor.md) verschwindet der + Abschnitt — die Werte stehen danach in jeder Gruppe einzeln. Inhaltlich + ändert sich nichts. + +--- + +## `[group:NAME]` + +### Allgemein + +| Schlüssel | Vorgabe | | +|---|---|---| +| `enabled` | `yes` | Gruppe aktiv. In der Auslieferung stehen alle auf `no`. | + +### Zeitplan + +Entweder `interval` **oder** `schedule` — nicht beides. + +| Schlüssel | Vorgabe | | +|---|---|---| +| `interval` | — | `30m`, `1h`, `6h`, `2d12h`, `1w` | +| `align` | `no` | `yes` = an der Uhr ausgerichtet (00:00, 00:30 …) | +| `schedule` | — | `hourly`, `daily`, `weekly`, `monthly`, `yearly` | +| `at` | — | Uhrzeit `HH:MM` bei `daily` und größer | +| `minute` | `0` | Minute bei `hourly` | +| `day_of_week` | `mo` | `mo di mi do fr sa so` bei `weekly` | +| `day_of_month` | `1` | 1–31 bei `monthly`; zu große Werte fallen auf den Monatsletzten | +| `month` | `1` | 1–12 bei `yearly` | + +Einzelheiten unter [Zeitpläne](../sichern/zeitplan.md). + +### Vorhaltezeit + +| Schlüssel | Vorgabe | | +|---|---|---| +| `keep_count` | `0` | höchstens so viele je VM und Gruppe (`0` = unbegrenzt) | +| `keep_time` | `0` | nichts älter als (`0` = unbegrenzt) | +| `keep_min` | `0` | so viele bleiben in jedem Fall stehen | + +**Mindestens eine von `keep_count` und `keep_time` muss gesetzt sein.** +Einzelheiten unter [Vorhaltezeit](../sichern/vorhaltezeit.md). + +### Auswahl der Gäste + +| Schlüssel | Vorgabe | | +|---|---|---| +| `all` | `no` | alle VMs und Container | +| `vmids` | — | `100,101,105-110` | +| `names` | — | `web-*, db-0?` — mit `*`, `?`, `[abc]` | +| `tags` | — | `produktion, wichtig` | +| `pools` | — | `Kunden` | +| `types` | — | `qemu` oder `lxc`; leer = beides | +| `exclude_vmids` | — | | +| `exclude_names` | — | | +| `exclude_tags` | — | | + +Einschlüsse wirken als **ODER**, Ausschlüsse gewinnen immer. Einzelheiten unter +[Welche Gäste?](../sichern/auswahl.md). + +### Snapshot-Optionen + +| Schlüssel | Vorgabe | | +|---|---|---| +| `vmstate` | `no` | Arbeitsspeicher mitsichern — nur QEMU, nur bei laufender VM | +| `skip_stopped` | `no` | gestoppte Gäste überspringen | +| `description` | aus `[global]` | eigene Beschreibung für diese Gruppe | + +--- + +## Platzhalter der Beschreibung + +| | | | +|---|---|---| +| `{group}` | Name der Gruppe | `täglich` | +| `{group_slug}` | Kurzname im Snapshot | `taeglich` | +| `{vmid}` | VMID | `101` | +| `{name}` | Name des Gastes | `db01` | +| `{type}` | `VM` oder `LXC` | `VM` | +| `{node}` | Cluster-Knoten | `pve1` | +| `{pool}` | Proxmox-Pool | `Hausnetz` | +| `{tags}` | Tags des Gastes | `produktion,datenbank` | +| `{date}` | Datum | `2026-08-09` | +| `{time}` | Uhrzeit | `02:30:00` | +| `{datetime}` | beides | `2026-08-09 02:30:00` | +| `{timestamp}` | Unix-Zeit | `1786257000` | +| `{keep_time}` | Vorhaltezeit der Gruppe | `3w` | +| `{keep_count}` | Anzahl | `14` | +| `{schedule}` | Zeitplan im Klartext | `täglich um 02:30` | + +Ein unbekannter Platzhalter führt nicht zum Abbruch — dann wird eine +Ersatzbeschreibung eingesetzt. + +--- + +## Deutsche Schreibweisen + +Damit die Datei lesbar bleibt, versteht pvesnap eine Reihe deutscher Synonyme: + +| deutsch | entspricht | +|---|---| +| `aktiv`, `aktiviert` | `enabled` | +| `intervall` | `interval` | +| `zeitplan` | `schedule` | +| `uhrzeit`, `zeit` | `at` | +| `wochentag` | `day_of_week` | +| `monatstag` | `day_of_month` | +| `monat` | `month` | +| `anzahl`, `max_anzahl`, `behalte_anzahl` | `keep_count` | +| `vorhaltezeit`, `behalte_zeit`, `max_alter` | `keep_time` | +| `mindestens` | `keep_min` | +| `alle` | `all` | +| `namen` | `names` | +| `typen` | `types` | +| `beschreibung` | `description` | +| `ausschluss_vmids` | `exclude_vmids` | +| `ausschluss_namen` | `exclude_names` | +| `gestoppte_ueberspringen` | `skip_stopped` | + +Bindestriche und Unterstriche sind austauschbar, Groß- und Kleinschreibung egal. + +--- + +## Eine vollständige Beispieldatei + +```ini +[global] +prefix = auto +check_interval = 60s +state_file = /var/lib/pvesnap/state.json +log_level = INFO +task_timeout = 15m +retries = 2 +retry_delay = 60s +pause_between = 0s +run_on_start = no +dry_run = no +description = pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time} | max: {keep_count} + +[defaults] +enabled = yes +skip_stopped = no +vmstate = no + +[group:stuendlich] +interval = 1h +align = yes +keep_count = 24 +keep_time = 2d +keep_min = 1 +tags = stuendlich +skip_stopped = yes + +[group:taeglich] +schedule = daily +at = 02:30 +keep_count = 14 +keep_time = 21d +keep_min = 1 +tags = produktion +exclude_tags = nosnap + +[group:monatlich] +schedule = monthly +day_of_month = 1 +at = 04:00 +keep_count = 6 +keep_time = 400d +all = yes +exclude_tags = nosnap, pvesnap-recovery +description = Monatssicherung {name} ({vmid}) vom {date} +``` diff --git a/handbuch/docs/nachschlagen/speicher.md b/handbuch/docs/nachschlagen/speicher.md new file mode 100644 index 0000000..53d0857 --- /dev/null +++ b/handbuch/docs/nachschlagen/speicher.md @@ -0,0 +1,192 @@ +# Speicherarten + +Nicht jedes Proxmox-Storage kann alles. Diese Seite fasst zusammen, was wo geht. + +--- + +## Die Übersicht + +| Storage | Snapshots | Dateien lesen | Als Maschine starten | Vom Snapshot lösen | +|---|---|---|---|---| +| **`rbd`** (Ceph) | ✅ | ✅ | ✅ | ✅ nötig | +| **`zfspool`** | ✅ | ✅ | ✅ | ❌ nicht möglich | +| **`lvmthin`** | ✅ | ✅ | ✅ | — nicht nötig | +| **`lvm`** (dick) | ❌ | — | — | — | +| **`dir`, `nfs`, `cifs`** (qcow2) | ✅ | ✅ | ❌ | — | +| **`dir`, `nfs`, `cifs`** (raw) | ❌ | — | — | — | + +--- + +## Ceph / RBD + +**Die beste Grundlage für pvesnap.** Alles geht, und es geht clusterweit. + +| | | +|---|---| +| Snapshot | `rbd snap create`, sofort | +| Hineinsehen | `rbd map pool/image@snap`, bei nicht unterstützten Image-Features über `rbd-nbd` | +| Klonen | `rbd clone`, in etwa einer Sekunde — unabhängig von der Größe | +| Wiederherstellung startbar auf | **jedem Node des Clusters** (geteiltes Storage) | + +### Die eine Eigenheit + +RBD verlangt für einen Klon, dass der Quell-Snapshot **geschützt** ist +(`rbd snap protect`). Solange ein Klon existiert, ist der Snapshot damit +unlöschbar, und die [Vorhaltezeit](../sichern/vorhaltezeit.md) scheitert an ihm +mit `snapshot is protected`. + +pvesnap geht damit sauber um: Beim [Verwerfen](../wiederherstellen/verwerfen.md) +wird der Schutz wieder aufgehoben, und +[`flatten`](../wiederherstellen/loesen.md) löst die Abhängigkeit ganz. + +### Platz im Blick + +```bash +ceph df # MAX AVAIL beachten +rbd -p du +rbd -p ls -l # Spalte PARENT zeigt Klone +``` + +!!! danger "`% RAW USED` täuscht" + + Es berücksichtigt die Replikation nicht. Bei dreifacher Replikation kosten + 13 GB Nutzdaten 39 GB im Pool. **`MAX AVAIL`** ist die Zahl, die zählt. + + Läuft ein Pool voll, blockiert er jeden Schreibvorgang — und damit alle VMs + darauf. Selbst das Aufräumen wird dann schwierig, weil Löschen ebenfalls + ein Schreibvorgang ist. + +--- + +## ZFS + +| | | +|---|---| +| Snapshot | `zfs snapshot`, sofort | +| Hineinsehen | Container direkt über `.zfs/snapshot/…`, VMs über einen temporären Klon | +| Klonen | `zfs clone`, sofort | +| Wiederherstellung startbar auf | dem Node mit dem Pool (bzw. clusterweit bei ZFS over iSCSI) | + +### Lösen geht nicht + +Der Klon hängt wie bei Ceph am Snapshot. `zfs promote` würde die Abhängigkeit +nur **umdrehen** — danach hinge das Original am Klon. Das verschiebt das +Problem, statt es zu lösen. + +Echte Unabhängigkeit ginge nur über `zfs send | zfs recv` in einen neuen +Datenträger. Das macht `flatten` nicht und sagt es auch so. + +```bash +zfs list -o space # USEDSNAP zeigt, was die Snapshots kosten +zfs list -t snapshot +``` + +--- + +## LVM-thin + +**In einem Punkt angenehmer als Ceph:** Ein Thin-Snapshot ist ein +eigenständiges Volume, das sich mit anderen nur die Blöcke im Pool teilt. + +``` +Belegung im Thin-Pool nach dem Klonen (256 MB Nutzdaten): + data <19.84g 1.26 % ← Pool hält die Daten nur EINMAL + snap_..._wdhtest 1.00g + vm-9998-disk-0 1.00g 25.00 % Klon + vm-9999-disk-0 1.00g 25.00 % Original + +Quell-Snapshot löschen, während der Klon existiert → erlaubt +Original löschen → erlaubt +Prüfsumme des Klons danach → unverändert +``` + +Platzsparend geteilt **und** trotzdem frei löschbar. Es gibt dort weder +geschützte Snapshots noch etwas zu flatten. + +### Der Preis: lokaler Speicher + +`lvmthin` liegt auf einem einzelnen Node. Die Wiederherstellung muss auf +demselben Node laufen wie das Original; `pvesnap-recovery` prüft das und lehnt +einen anderen Node ab. + +!!! warning "Auch Thin-Pools laufen voll" + + Und dann stehen alle Volumes darin. Überprovisionierung im Blick behalten: + + ```bash + lvs -o lv_name,data_percent,metadata_percent + ``` + + Besonders die **Metadaten** — läuft der Metadatenbereich voll, ist der Pool + nicht mehr zu retten, auch wenn noch Datenplatz frei ist. + +--- + +## LVM (dick) + +Proxmox kann dort **gar keine Snapshots**. Betroffene VMs melden im Protokoll: + +``` +storage does not support snapshots +``` + +Sie werden übersprungen, die übrigen laufen normal weiter. + +Der Ausweg ist ein Umzug auf `lvmthin` — technisch derselbe Speicher, nur mit +Thin-Provisioning: + +```bash +qm move-disk 101 scsi0 +``` + +--- + +## Verzeichnis-Storages (`dir`, `nfs`, `cifs`) + +Hier kommt es auf das **Dateiformat** an: + +| Format | Snapshots | | +|---|---|---| +| `qcow2` | ✅ | interne Snapshots im Abbild selbst | +| `raw` | ❌ | keine Snapshot-Fähigkeit | +| `vmdk` | ❌ | | + +### Hineinsehen: ja + +`qemu-nbd --load-snapshot` bindet den Snapshot schreibgeschützt ein. Explorer +und Web-Oberfläche funktionieren also. + +### Als Maschine starten: nein + +!!! warning "Kein Klon aus einem qcow2-internen Snapshot" + + Ein qcow2-interner Snapshot lässt sich nicht als eigenständiges Volume + klonen. `pvesnap-recovery` lehnt das ab und verweist auf den Explorer. + + Wer die Fähigkeit braucht, verschiebt die VM auf ein Storage, das Klone + kann — Ceph, ZFS oder LVM-thin. + +--- + +## Was passiert bei gemischten Datenträgern? + +Hat eine VM Platten auf verschiedenen Storages, gilt für die +**Wiederherstellung** die strengste Regel: + +* Liegt eine Platte auf einem Storage, das keine Klone kann, geht es gar nicht +* Liegt eine Platte auf lokalem Storage, ist der Node festgelegt +* Ist ein Storage auf dem Zielnode nicht verfügbar, wird abgelehnt + +Die [Zusammenfassung](../wiederherstellen/einrichten.md#die-zusammenfassung) +nennt den Grund, bevor etwas angelegt wird. + +--- + +## Empfehlung + +| Lage | | +|---|---| +| **Cluster mit Ceph** | Ideal. Alles geht, von jedem Node aus. Nur an `flatten` denken, wenn eine Wiederherstellung bleibt. | +| **Einzelner Host** | `lvmthin` oder ZFS. LVM-thin ist beim Lösen unkomplizierter, ZFS bietet dafür Prüfsummen und `zfs send`. | +| **NFS-Storage** | qcow2 statt raw verwenden, dann geht wenigstens das Lesen. Für Wiederherstellungen ist es die falsche Grundlage. | +| **LVM dick** | Auf `lvmthin` umstellen. Ohne Snapshot-Fähigkeit hat pvesnap dort nichts zu tun. | diff --git a/handbuch/docs/nachschlagen/tasten.md b/handbuch/docs/nachschlagen/tasten.md new file mode 100644 index 0000000..00a541c --- /dev/null +++ b/handbuch/docs/nachschlagen/tasten.md @@ -0,0 +1,148 @@ +# Tastenkürzel + +Alle Oberflächen auf einer Seite. Zum Ausdrucken und Danebenlegen. + +--- + +## `pvesnap config` — Gruppenliste + +| Taste | | +|---|---| +| ++enter++ | Gruppe bearbeiten | +| ++space++ | Gruppe ein- oder ausschalten | +| ++n++ | neue Gruppe | +| ++c++ | Gruppe kopieren | +| ++d++ | Gruppe löschen | +| ++g++ | globale Einstellungen | +| ++v++ | Übersicht: welche VM in welcher Gruppe | +| ++s++ | speichern | +| ++r++ | Dienst neu laden | +| ++q++ | Ende | + +### Gruppenmaske + +| Taste | | +|---|---| +| ++up++ ++down++ | Feld wählen | +| ++page-up++ ++page-down++ | zehn Felder weiter | +| ++enter++ ++space++ | Feld ändern bzw. umschalten | +| ++v++ | VM-Auswahl öffnen | +| ++q++ ++esc++ | zurück | + +### VM-Auswahl + +| Taste | | +|---|---| +| ++space++ | Gast an- oder abwählen | +| ++a++ | alle | +| ++n++ | keine | +| ++i++ | Auswahl umkehren | +| ++slash++ | filtern | +| ++enter++ | übernehmen | +| ++esc++ | abbrechen | + +--- + +## `pvesnap-explorer` + +| Taste | | +|---|---| +| ++tab++ | Fenster wechseln | +| ++up++ ++down++ | bewegen | +| ++enter++ | Verzeichnis öffnen oder Datei ansehen | +| ++backspace++ ++left++ | ein Verzeichnis hoch | +| ++home++ ++end++ | Anfang / Ende | +| ++page-up++ ++page-down++ | seitenweise | +| ++space++ | markieren | +| ++asterisk++ | Markierung umkehren | +| ++a++ | alle markieren | +| ++u++ | Markierung aufheben | +| ++f5++ ++c++ | Markiertes ins andere Fenster kopieren | +| ++f3++ ++v++ | Datei ansehen (Text oder Hex) | +| ++f7++ ++n++ | neues Verzeichnis (nur lokal) | +| ++f6++ ++g++ | Verzeichnis direkt eingeben | +| ++f2++ ++m++ | anderes Dateisystem des Snapshots wählen | +| ++r++ | neu einlesen | +| ++f1++ ++question++ | Hilfe | +| ++f10++ ++q++ | eine Ebene zurück | + +Während des Kopierens bricht ++esc++ ab. + +--- + +## `pvesnap-recovery` — Übersicht + +| Taste | | +|---|---| +| ++enter++ | Detailansicht | +| ++n++ | neue Wiederherstellung | +| ++s++ | starten | +| ++h++ | herunterfahren | +| ++x++ | verwerfen | +| ++v++ | Austauschlaufwerke | +| ++r++ | neu einlesen | +| ++q++ | Ende | + +### Detailansicht + +| Taste | | +|---|---| +| ++s++ | starten | +| ++h++ | herunterfahren | +| ++e++ | Austauschlaufwerk auswerfen / einklinken | +| ++v++ | Austauschlaufwerke verwalten | +| ++p++ | SPICE und USB | +| ++f++ | vom Quell-Snapshot lösen | +| ++x++ | verwerfen | +| ++q++ | zurück | + +### Optionsmaske + +| Taste | | +|---|---| +| ++up++ ++down++ | Feld wählen | +| ++enter++ ++space++ | Feld ändern | +| ++v++ | Austauschlaufwerke verwalten | +| ++f10++ | anlegen | +| ++q++ | abbrechen | + +### Zusammenfassung + +| Taste | | +|---|---| +| ++j++ | anlegen | +| ++n++ ++q++ | abbrechen | +| beliebige Taste | weiterblättern, wenn der Text länger ist als der Bildschirm | + +--- + +## Austauschlaufwerke + +| Taste | | +|---|---| +| ++enter++ | am Host einhängen und im Commander öffnen | +| ++n++ | neues Laufwerk anlegen | +| ++u++ | am Host aushängen | +| ++l++ | löschen | +| ++r++ | neu einlesen | +| ++q++ | zurück | + +### Mehrfachauswahl beim Anlegen einer Maschine + +| Taste | | +|---|---| +| ++space++ | aus- oder abwählen | +| ++enter++ | übernehmen | +| ++q++ | abbrechen | + +--- + +## Überall gleich + +| Taste | | +|---|---| +| ++up++ ++down++ oder ++k++ ++j++ | in Auswahlfenstern bewegen | +| ++enter++ | bestätigen | +| ++esc++ ++q++ | abbrechen / zurück | +| ++j++ ++n++ | Ja/Nein-Rückfragen | +| ++ctrl+u++ | Eingabezeile leeren | diff --git a/handbuch/docs/schnellstart.md b/handbuch/docs/schnellstart.md new file mode 100644 index 0000000..3651ac2 --- /dev/null +++ b/handbuch/docs/schnellstart.md @@ -0,0 +1,137 @@ +# Schnellstart + +Vom leeren Host zur laufenden Sicherung. Rechne mit einer Viertelstunde, +davon zehn Minuten Nachdenken darüber, was wie oft gesichert werden soll. + +--- + +## 1. Installieren + +Auf dem Proxmox-Host, als `root`: + +```bash +git clone pvesnap && cd pvesnap +./install.sh --with-webexplorer --port 8823 +``` + +Ohne `--with-webexplorer` wird nur der Snapshot-Dienst eingerichtet; die +Web-Oberfläche lässt sich jederzeit nachrüsten. Einzelheiten: +[Installation](installation.md). + +!!! note "Es passiert erst einmal nichts" + + In der mitgelieferten Konfiguration stehen **alle Gruppen auf `enabled = no`**. + Der Dienst läuft, legt aber nichts an, bis du eine Gruppe einschaltest. Das + ist Absicht — niemand soll nach der Installation überrascht feststellen, + dass sein Storage vollläuft. + +--- + +## 2. Gäste markieren + +pvesnap sucht sich die Gäste über Proxmox-**Tags** aus. Das ist der bequemste +Weg, weil du danach nie wieder die Konfiguration anfassen musst: Eine neue VM +bekommt ihr Tag, und sie ist dabei. + +In der Proxmox-Oberfläche bei jeder VM unter **Optionen → Tags**, oder auf der +Shell: + +```bash +qm set 101 --tags produktion,datenbank +pct set 110 --tags produktion +``` + +Für den Anfang reicht ein einziges Tag, etwa `produktion`. + +--- + +## 3. Eine Gruppe einschalten + +```bash +pvesnap config +``` + +![Die Gruppenliste](bilder/config-gruppen.svg) + +Mit den Pfeiltasten auf `taeglich`, dann: + +
+ +`Leertaste` — schaltet die Gruppe ein (Spalte **Aktiv** springt auf `ja`) + +`Enter` — öffnet sie, um Zeitplan und Auswahl zu prüfen + +`s` — speichern + +`r` — Dienst neu laden + +
+ +In der Gruppe siehst du unten sofort, auf wie viele Gäste die Auswahl gerade +zutrifft — du musst also nicht raten, ob das Tag richtig geschrieben ist: + +![Eine Gruppe bearbeiten](bilder/config-gruppe.svg) + +--- + +## 4. Nachsehen, ob es stimmt + +```bash +pvesnap check # meckert über Widersprüche in der Konfiguration +pvesnap vms # welche VM landet in welcher Gruppe? +pvesnap status # letzte und nächste Läufe +``` + +`pvesnap vms` ist der ehrlichste Test: Dort steht schwarz auf weiß, welche +Maschine gesichert wird und welche nicht. + +--- + +## 5. Einmal von Hand auslösen + +Nicht bis morgen früh warten wollen: + +```bash +pvesnap run --force -g taeglich --dry-run # Probelauf, ändert nichts +pvesnap run --force -g taeglich # jetzt wirklich +``` + +Danach stehen die Snapshots in der Proxmox-Oberfläche unter **Snapshots** — +mit Namen wie `auto-taeglich-20260809-023000` und einer Beschreibung, an der +man sie wiedererkennt. + +--- + +## 6. Den Ernstfall einmal proben + +Das ist der Schritt, den fast alle auslassen — und der im Ernstfall den +Unterschied macht. Hol dir jetzt, in Ruhe, eine Datei aus einem Snapshot: + +```bash +pvesnap-explorer 101 +``` + +Snapshot auswählen, mit den Pfeiltasten durch das Dateisystem, `Leertaste` zum +Markieren, `F5` kopiert nach rechts auf den Host. + +![Dateien kopieren](bilder/explorer-kopieren.svg) + +Wenn du das einmal gemacht hast, weißt du im Notfall, wo die Tasten liegen. + +--- + +## Fertig. Und dann? + +| | | +|---|---| +| Mehrere Zeitpläne nebeneinander | [Sichern](sichern/index.md) — stündlich für die Datenbank, monatlich für den Rest | +| Etwas herausgeben, ohne Shell-Zugang | [Die Web-Oberfläche](holen/web.md) | +| Eine ganze Maschine zurückholen | [Wiederherstellen](wiederherstellen/index.md) | +| Austauschlaufwerke vorbereiten | [Austauschlaufwerke](wiederherstellen/transfer.md) — *vor* dem Notfall, nicht mittendrin | + +!!! tip "Der eine Rat, wenn du nur einen mitnimmst" + + Leg dir **jetzt** ein Austauschlaufwerk an und pack die Werkzeuge hinein, + die du im Notfall brauchst — `pg_dump`, ein Packprogramm, deine Skripte. + Im Ernstfall ist keine Zeit, so etwas zusammenzusuchen, und die + wiederhergestellte Maschine hat kein Netzwerk. diff --git a/handbuch/docs/sichern/auswahl.md b/handbuch/docs/sichern/auswahl.md new file mode 100644 index 0000000..55c6e1e --- /dev/null +++ b/handbuch/docs/sichern/auswahl.md @@ -0,0 +1,210 @@ +# Welche Gäste? + +Eine Gruppe sucht sich ihre Gäste über eine oder mehrere Regeln. Dabei gilt: + +
+ +Alle Einschluss-Regeln wirken als **ODER** — wer *eine* davon erfüllt, ist dabei. + +Alle Ausschluss-Regeln **gewinnen immer** — auch gegen `all = yes`. + +
+ +```ini +all = yes # alle VMs und Container +vmids = 100,101,105-110 # nach ID, auch Bereiche +names = web-*, db-0? # nach Name, mit Platzhaltern +tags = produktion, wichtig # nach Proxmox-Tag +pools = Kunden # nach Proxmox-Pool +types = qemu # nur VMs (lxc = nur Container; leer = beides) + +exclude_vmids = 999 +exclude_names = *-test +exclude_tags = nosnap +``` + +Kontrollieren lässt sich das Ergebnis an zwei Stellen: unten in der +[Gruppenmaske](editor.md#eine-gruppe-bearbeiten) („Trifft aktuell zu auf …") und +mit `pvesnap vms`. + +--- + +## Tags + +**Der empfohlene Weg.** Die Auswahl steht dann bei der VM, nicht in der +Konfiguration — und eine neue Maschine ist mit einem Klick dabei, ohne dass +jemand `pvesnap.conf` anfassen muss. + +```ini +tags = produktion +``` + +In Proxmox setzen: + +```bash +qm set 101 --tags produktion,datenbank +pct set 110 --tags produktion +``` + +Oder in der Oberfläche unter **VM → Optionen → Tags**. + +Groß- und Kleinschreibung ist egal; pvesnap vergleicht in Kleinbuchstaben. +Mehrere Tags an einer VM trennt Proxmox mit `;` oder `,` — beides wird gelesen. + +!!! tip "Ein Tag je Zeitplan" + + Bewährt hat sich, die Tags nach dem Sicherungsrhythmus zu benennen und + nicht nach der Funktion: + + ```ini + [group:stuendlich] + tags = stuendlich + + [group:taeglich] + tags = produktion + ``` + + Dann sieht man in der Proxmox-Oberfläche an jeder VM sofort, wie oft sie + gesichert wird — ohne die Konfiguration zu öffnen. + +--- + +## VMIDs + +```ini +vmids = 100,101,105-110,200 +``` + +Einzelne IDs und Bereiche, durch Komma getrennt. Am bequemsten über die +[VM-Auswahl](editor.md#vms-aus-einer-liste-wahlen) im Editor. + +Präzise, aber pflegeintensiv: Jede neue Maschine muss von Hand nachgetragen +werden. Für kleine, feste Zusammenstellungen in Ordnung, für alles Wachsende +sind Tags besser. + +--- + +## Namensmuster + +```ini +names = web-*, db-0? +``` + +| Zeichen | | +|---|---| +| `*` | beliebig viele Zeichen | +| `?` | genau ein Zeichen | +| `[abc]` | eines dieser Zeichen | + +Praktisch bei sauberer Namenskonvention (`web-01`, `web-02`, `db-01`), +gefährlich ohne: Eine VM, die jemand `web-test-alt` nennt, fällt in `web-*` +hinein. Dagegen hilft ein `exclude_names = *-test`. + +--- + +## Pools + +```ini +pools = Kunden +``` + +Nach dem Proxmox-Pool. Sinnvoll, wenn die Pools ohnehin nach Kunde oder +Abteilung geschnitten sind — dann sichert eine Gruppe genau einen Mandanten. + +--- + +## Gasttyp + +```ini +types = qemu # nur virtuelle Maschinen +types = lxc # nur Container +types = # beides (Vorgabe) +``` + +Nützlich in Verbindung mit `vmstate = yes`, das ohnehin nur für QEMU gilt: Eine +Gruppe mit RAM-Sicherung für die VMs, eine ohne für die Container. + +--- + +## Ausschlüsse + +```ini +exclude_vmids = 999 +exclude_names = *-test, temp-* +exclude_tags = nosnap, pvesnap-recovery +``` + +Ausschlüsse schlagen alles. Auch `all = yes`. + +!!! success "Zwei Ausschlüsse, die in jede `all`-Gruppe gehören" + + ```ini + exclude_tags = nosnap, pvesnap-recovery + ``` + + **`nosnap`** ist der Notausgang: Wer eine einzelne Maschine aus der + Sicherung nehmen will, setzt ihr das Tag — ohne die Konfiguration + anzufassen. + + **`pvesnap-recovery`** trägt jede laufende + [Wiederherstellung](../wiederherstellen/index.md). Ohne den Ausschluss + bekämen diese kurzlebigen Maschinen eigene Snapshots — die dann wiederum + verhindern, dass sie sich sauber verwerfen lassen. + +--- + +## Mehrere Regeln kombiniert + +```ini +[group:taeglich] +tags = produktion +vmids = 300 +exclude_names = *-test +exclude_tags = nosnap +``` + +Gelesen: *alles mit dem Tag `produktion`* **oder** *VM 300*, **aber nichts**, +was auf `-test` endet oder das Tag `nosnap` trägt. + +--- + +## Gestoppte Gäste + +```ini +skip_stopped = yes +``` + +Übergeht Gäste, die gerade aus sind. Vorgabe ist `no` — auch von einer +gestoppten Maschine wird ein Snapshot angelegt. + +Beides ist vertretbar: + +| | | +|---|---| +| `skip_stopped = no` | Der Stand ist gesichert, auch wenn die VM länger aus ist. Kostet bei kurzen Intervallen viele identische Snapshots. | +| `skip_stopped = yes` | Nur laufende Maschinen. Passt zu stündlichen Gruppen, spart Platz — aber eine über Wochen abgeschaltete VM bekommt gar nichts mehr. | + +!!! note "Der Mischbetrieb" + + Eine stündliche Gruppe mit `skip_stopped = yes`, eine monatliche mit + `skip_stopped = no`. Dann bekommt jede Maschine mindestens den monatlichen + Stand, und die laufenden zusätzlich die feine Auflösung. + +--- + +## Nachsehen, was herauskommt + +```bash +pvesnap vms +``` + +``` +VMID Name Status Gruppen +100 web01 running stuendlich, taeglich, monatlich +101 db01 running stuendlich, taeglich, monatlich +120 build-test stopped (keine Gruppe) +``` + +Dasselbe im Editor unter Taste ++v++: + +![Übersicht: welche VM in welcher Gruppe](../bilder/config-uebersicht.svg) diff --git a/handbuch/docs/sichern/betrieb.md b/handbuch/docs/sichern/betrieb.md new file mode 100644 index 0000000..f031413 --- /dev/null +++ b/handbuch/docs/sichern/betrieb.md @@ -0,0 +1,175 @@ +# Im Betrieb + +## Der Dienst + +```bash +systemctl status pvesnap # läuft er? +systemctl reload pvesnap # Konfiguration neu einlesen (SIGHUP) +systemctl restart pvesnap # kompletter Neustart +journalctl -u pvesnap -f # Protokoll mitlesen +``` + +`reload` ist der Normalfall nach einer Änderung: Der Dienst liest die +Konfiguration neu ein, ohne laufende Vorgänge abzubrechen und ohne den +gemerkten Zustand zu verlieren. + +--- + +## Nachsehen + +```bash +pvesnap status # Gruppen, letzte und nächste Läufe +pvesnap vms # welche VM landet in welcher Gruppe +pvesnap list # vorhandene pvesnap-Snapshots (-a = auch fremde) +pvesnap check # Konfiguration prüfen +``` + +### `pvesnap check` + +Der Befehl, den man nach jeder Änderung laufen lassen sollte. Er findet unter +anderem: + +* Gruppen ohne `keep_count` **und** ohne `keep_time` — die würden endlos wachsen +* zwei Gruppen mit demselben Kurznamen — die räumten sich gegenseitig ab +* Zeitpläne, die sich widersprechen (`interval` und `schedule` zugleich) +* eine Auswahl, die auf keinen einzigen Gast passt +* [Sandbox-Optionen in der systemd-Unit](../installation.md#die-systemd-unit), + an denen später jeder Snapshot scheitern würde + +--- + +## Von Hand auslösen + +```bash +pvesnap run # jetzt fällige Gruppen ausführen +pvesnap run --force -g taeglich # diese Gruppe sofort, egal ob fällig +pvesnap run --force --dry-run # Probelauf, ändert nichts +pvesnap prune -g stuendlich # nur aufräumen +``` + +`--force` heißt „auch wenn nicht fällig". Ohne das passiert bei einem Aufruf +zwischen zwei Terminen schlicht nichts. + +!!! note "Parallel zum Dienst" + + Ein manueller `pvesnap run` ist auch möglich, während der Dienst läuft. + Beide teilen sich eine Sperre und kommen sich nicht in die Quere — der + zweite wartet, bis der erste fertig ist. + +### Eine andere Konfiguration ausprobieren + +```bash +pvesnap -c /root/test.conf check +pvesnap -c /root/test.conf run --force --dry-run +``` + +`-c` gilt für alle Unterbefehle. Zusammen mit `--dry-run` ein gefahrloser Weg, +eine neue Gruppenstruktur durchzuspielen, bevor sie scharf geschaltet wird. + +--- + +## Der Probelauf + +```ini +[global] +dry_run = yes +``` + +oder einmalig: + +```bash +pvesnap run --force --dry-run +``` + +Protokolliert, was geschähe, ohne etwas anzulegen oder zu löschen: + +``` +[TESTLAUF] wuerde Snapshot anlegen: VM 101 (db01) -> auto-taeglich-20260809-023000 +[TESTLAUF] wuerde Snapshot loeschen: VM 101 (db01) -> auto-taeglich-20260719-023000 +``` + +Besonders die zweite Zeilenart ist es wert, einmal angesehen zu werden, bevor +eine neue Vorhaltezeit produktiv geht. + +--- + +## Protokoll + +```ini +[global] +log_level = INFO +# log_file = /var/log/pvesnap.log +``` + +| Stufe | | +|---|---| +| `DEBUG` | jeder `pvesh`-Aufruf mit Argumenten — für die Fehlersuche | +| `INFO` | was angelegt und gelöscht wird (Vorgabe) | +| `WARNING` | nur Auffälligkeiten | +| `ERROR` | nur Fehler | + +Ohne `log_file` geht alles ins Journal: + +```bash +journalctl -u pvesnap -f # mitlesen +journalctl -u pvesnap --since "-1h" # letzte Stunde +journalctl -u pvesnap -p err # nur Fehler +journalctl -u pvesnap --since today | grep -i fehl +``` + +--- + +## Was im Protokoll normal ist + +Nicht jede Meldung ist ein Problem: + +| Meldung | Bedeutung | +|---|---| +| `storage does not support snapshots` | Diese VM liegt auf LVM-thick oder einem `raw`-Image. Sie wird übersprungen, die übrigen laufen weiter. | +| `got lock request timeout` | Das Storage war gerade belegt. Wird `retries`-mal wiederholt — erst danach ist es ein Fehler. | +| `VM is locked (backup)` | Ein `vzdump` läuft gerade. Ebenfalls vorübergehend. | + +Dauerhaft wiederkehrende Lock-Fehler sind dagegen ein Zeichen — siehe +[Fehlersuche](../nachschlagen/fehlersuche.md#storage-lock). + +--- + +## Wenn viele VMs auf demselben Storage liegen + +```ini +[global] +pause_between = 10s # Pause zwischen zwei Gästen +retries = 2 # zusätzliche Versuche je Snapshot +retry_delay = 60s # Wartezeit davor +task_timeout = 15m # Geduld mit einem einzelnen Proxmox-Task +``` + +`pause_between` nimmt Druck vom Storage-Lock, wenn dreißig Maschinen im selben +Pool nacheinander drankommen. Vorgabe ist `0s`; bei Problemen sind `5s` bis +`15s` ein guter erster Versuch. + +--- + +## Überwachen + +Ein einfacher Weg, im Blick zu behalten, ob die Sicherung wirklich läuft: + +```bash +# Gab es in den letzten 25 Stunden einen taeglichen Snapshot? +pvesnap list | grep auto-taeglich | tail -1 +``` + +Oder gezielt über den Dienstzustand: + +```bash +systemctl is-active pvesnap # active / failed +journalctl -u pvesnap --since "-25h" -p err -q # leer = keine Fehler +``` + +!!! warning "Der Klassiker" + + Der Dienst läuft, das Protokoll ist ruhig — und trotzdem entsteht nichts, + weil alle Gruppen auf `enabled = no` stehen. Genau so wird ausgeliefert. + + `pvesnap status` zeigt es sofort: Eine ausgeschaltete Gruppe hat keinen + nächsten Termin. diff --git a/handbuch/docs/sichern/editor.md b/handbuch/docs/sichern/editor.md new file mode 100644 index 0000000..4b665b5 --- /dev/null +++ b/handbuch/docs/sichern/editor.md @@ -0,0 +1,186 @@ +# Der Konfigurationseditor + +```bash +pvesnap config +``` + +Braucht `root` — der Editor schreibt nach `/etc/pvesnap/` und fragt Proxmox nach +dem Inventar. + +Alles, was hier geht, geht auch von Hand in der INI-Datei. Der Editor hat aber +zwei Vorteile, die man beim Tippen nicht hat: Er zeigt **live, auf wie viele +Gäste eine Auswahl gerade zutrifft**, und er rechnet den **nächsten Termin** +aus, bevor man speichert. + +--- + +## Die Gruppenliste + +![Die Gruppenliste](../bilder/config-gruppen.svg) + +Der Einstiegsbildschirm. Oben steht die Datei, an der gearbeitet wird, und +rechts daneben, ob der Dienst gerade läuft. Ein `*` vor dem Dateinamen bedeutet: +ungespeicherte Änderungen. + +| Taste | | +|---|---| +| ++enter++ | Gruppe bearbeiten | +| ++space++ | Gruppe ein- oder ausschalten | +| ++n++ | neue Gruppe | +| ++c++ | Gruppe kopieren — der schnellste Weg zu einer Variante | +| ++d++ | Gruppe löschen | +| ++g++ | [globale Einstellungen](#globale-einstellungen) | +| ++v++ | [Übersicht: welche VM in welcher Gruppe?](#die-ubersicht) | +| ++s++ | speichern | +| ++r++ | [Dienst neu laden](#dienst-neu-laden) | +| ++q++ | Ende | + +Die Spalte **Auswahl** fasst zusammen, wie die Gruppe ihre Gäste findet — +`Tags: produktion`, `alle VMs`, `VMIDs: 100-110`. Wer viele Gruppen hat, sieht +hier auf einen Blick, wo eine Maschine hineinfallen könnte. + +--- + +## Eine Gruppe bearbeiten + +![Eine Gruppe bearbeiten](../bilder/config-gruppe.svg) + +Eine lange Maske in vier Abschnitten: **Allgemein**, **Zeitplan**, +**Vorhaltezeit**, **Welche VMs?**, **Snapshot-Optionen**. Pfeiltasten bewegen +sich, ++enter++ ändert ein Feld, ++space++ schaltet Ja/Nein um. + +Zwei Zeilen darin sind keine Einstellungen, sondern Antworten: + +!!! tip "Nächster Termin (Vorschau)" + + ``` + Naechster Termin (Vorschau) Mo 10.08.2026 02:30 (taeglich um 02:30) + ``` + + Rechnet sofort nach, was der eingestellte Zeitplan bedeutet. Damit fällt + ein Denkfehler auf, bevor er drei Wochen lang unbemerkt bleibt — etwa + `day_of_month = 31` in einem 30-Tage-Monat. + +!!! tip "Trifft aktuell zu auf" + + ``` + Trifft aktuell zu auf 6 Gast/Gaeste (100, 101, 102, 105, 110, 130) + ``` + + Die Auswahlregeln, angewendet auf das echte Inventar — mitsamt VMIDs. Steht + dort `0 Gast/Gaeste`, ist meist ein Tag falsch geschrieben. + +Oben in der Kopfzeile steht außerdem, wie die Snapshots dieser Gruppe heißen +werden: + +``` +Kurzname im Snapshot: auto-taeglich-JJJJMMTT-HHMMSS +``` + +--- + +## VMs aus einer Liste wählen + +Auf dem Feld **VMs aus Liste wählen …** öffnet ++enter++ das Inventar; aus der +Gruppenmaske heraus geht auch direkt ++v++: + +![Die VM-Auswahl](../bilder/config-vms.svg) + +| Taste | | +|---|---| +| ++space++ | Gast an- oder abwählen | +| ++a++ | alle | +| ++n++ | keine | +| ++i++ | Auswahl umkehren | +| ++slash++ | filtern (Name, VMID oder Tag) | +| ++enter++ | übernehmen | + +Die Spalte **Tags** ist der eigentliche Grund, hier hereinzuschauen: Sie zeigt, +womit die Gäste in Proxmox markiert sind — und damit, ob eine Auswahl über +`tags = …` besser wäre als eine feste Liste von VMIDs. + +!!! note "Feste Listen altern schlecht" + + Eine Auswahl über VMIDs muss bei jeder neuen VM angefasst werden. Eine + Auswahl über Tags nicht. Für alles, was länger als ein paar Wochen leben + soll, sind [Tags](auswahl.md#tags) die bessere Wahl. + +--- + +## Globale Einstellungen + +Taste ++g++ in der Gruppenliste: + +![Globale Einstellungen](../bilder/config-global.svg) + +Das gilt für alle Gruppen — Präfix, Prüfintervall, Wiederholungen bei belegter +Sperre, Standard-Beschreibung. Was welcher Wert genau bedeutet, steht unter +[Alle Einstellungen](../nachschlagen/konfiguration.md#global). + +Der wichtigste ist **Testlauf (nichts wirklich tun)** — das `dry_run = yes` der +INI-Datei. Damit protokolliert der Dienst nur, was er täte. Zum Ausprobieren +einer neuen Konfiguration ohne Risiko. + +--- + +## Die Übersicht + +Taste ++v++ in der Gruppenliste: + +![Übersicht: welche VM in welcher Gruppe](../bilder/config-uebersicht.svg) + +Die Gegenprobe von der anderen Seite: nicht „welche Gäste hat diese Gruppe", +sondern **„in welchen Gruppen steckt dieser Gast"**. Wer hier steht, wird +gesichert. Wer `(keine Gruppe)` daneben stehen hat, nicht. + +Das ist der Bildschirm, den man nach jeder Änderung einmal ansehen sollte. Er +deckt beide typischen Fehler auf: eine vergessene Maschine — und eine, die +versehentlich in vier Gruppen gleichzeitig liegt. + +!!! example "Was hier auffällt" + + Im Bild trägt `build-test` das Tag `nosnap` und fällt damit überall heraus — + gewollt. Die beiden Maschinen `db01-live` und `warenwirtschaft-w` sind + laufende [Wiederherstellungen](../wiederherstellen/index.md); sie stehen + ebenfalls in keiner Gruppe, weil die monatliche Gruppe + `exclude_tags = nosnap, pvesnap-recovery` gesetzt hat. + + **Das ist ein Griff, der sich lohnt.** Ohne ihn bekämen kurzlebige + Wiederherstellungsmaschinen eigene Snapshots — die dann wiederum verhindern, + dass ihre Klone sauber verworfen werden können. + +--- + +## Dienst neu laden + +Taste ++r++ — entspricht `systemctl reload pvesnap`, ohne den Editor zu +verlassen. Der Editor nimmt einem dabei das Mitdenken ab: + +
+ +Ungespeicherte Änderungen? → bietet vorher das Speichern an + +Dienst gestoppt? → bietet das Starten an + +Neuladen scheitert? → bietet einen Neustart an + +
+ +Nach ++s++ fragt er ohnehin gleich, ob neu geladen werden soll. In der Kopfzeile +steht jederzeit, ob der Dienst läuft. + +--- + +## Beim Speichern + +!!! warning "Die INI-Datei wird neu geschrieben" + + Eigene Kommentare gehen dabei verloren. Ein `[defaults]`-Abschnitt + ebenfalls — inhaltlich bleiben die Werte erhalten, sie stehen danach nur in + jeder Gruppe einzeln. + + Eine Sicherung der alten Fassung wird als **`pvesnap.conf.bak`** abgelegt. + +Wer eine handgepflegte Konfiguration mit vielen Kommentaren hat, bearbeitet sie +besser weiter im Texteditor. Der ncurses-Editor ist für alle anderen da — und +für die beiden Vorschauzeilen, die es im Texteditor nicht gibt. diff --git a/handbuch/docs/sichern/index.md b/handbuch/docs/sichern/index.md new file mode 100644 index 0000000..9296e7e --- /dev/null +++ b/handbuch/docs/sichern/index.md @@ -0,0 +1,176 @@ +# Sichern + +Alles, was pvesnap tut, steckt in **Gruppen**. Eine Gruppe beantwortet drei +Fragen: + +
+ +**Wann?** — [Zeitplan](zeitplan.md): alle 30 Minuten, täglich um 02:30, am +Monatsersten + +**Wer?** — [Auswahl](auswahl.md): diese VMIDs, alles mit dem Tag `produktion`, +alle außer `nosnap` + +**Wie lange?** — [Vorhaltezeit](vorhaltezeit.md): die letzten 24, nichts älter +als zwei Tage + +
+ +Davon darf es beliebig viele geben, und eine VM darf in mehreren stecken. Genau +das ist der Sinn: Die Datenbank bekommt stündliche Snapshots mit kurzer +Vorhaltezeit, dieselbe Datenbank zusätzlich einen monatlichen, der ein Jahr +liegen bleibt. + +--- + +## Ein typischer Aufbau + +```ini +[group:stuendlich] +interval = 1h +align = yes # an der Uhr: 00:00, 01:00, 02:00 ... +keep_count = 24 +keep_time = 2d +tags = stuendlich +skip_stopped = yes + +[group:taeglich] +schedule = daily +at = 02:30 +keep_count = 14 +keep_time = 21d +tags = produktion +exclude_tags = nosnap + +[group:monatlich] +schedule = monthly +day_of_month = 1 +at = 04:00 +keep_count = 6 +keep_time = 400d +all = yes +exclude_tags = nosnap +``` + +Drei Gruppen, drei Zeithorizonte. Die Datenbank mit den Tags +`produktion,stuendlich` landet in allen dreien — und hat damit den Stand von +vor einer Stunde, von gestern Nacht und vom Monatsersten. + +So sieht das im Editor aus: + +![Die Gruppenliste](../bilder/config-gruppen.svg) + +--- + +## Namensschema + +Jeder Snapshot heißt nach demselben Muster: + +``` +auto-taeglich-20260809-023000 + │ │ │ └── Uhrzeit + │ │ └────────── Datum + │ └──────────────────── Kurzname der Gruppe + └─────────────────────────── prefix aus [global] +``` + +Das ist keine Kosmetik, sondern die **Sicherheitsgrenze** des ganzen Programms. + +!!! success "pvesnap löscht ausschließlich, was exakt auf dieses Muster passt" + + Und zusätzlich muss der Gruppen-Kurzname zu einer Gruppe gehören, die + gerade aufräumt. Ein Snapshot namens `vor-update-14.2` oder + `bkp_20260101` wird nie angefasst — egal wie alt er ist, egal wie voll das + Storage läuft. + +Umlaute und Sonderzeichen im Gruppennamen werden für den Kurznamen +umgeschrieben (`täglich` → `taeglich`). Ergäben zwei Gruppen denselben +Kurznamen, meldet das `pvesnap check` — sonst würden sie sich gegenseitig die +Snapshots wegräumen. + +!!! warning "Gruppe umbenennen" + + Der neue Name bekommt einen eigenen Kurznamen. Snapshots unter dem alten + Namen passen dann zu keiner Gruppe mehr und werden **nicht mehr + aufgeräumt** — sie bleiben für immer liegen. Entweder vorher aufräumen + lassen oder die alten hinterher von Hand entfernen. + +--- + +## Der Ablauf eines Laufs + +Der Dienst schaut alle `check_interval` (Vorgabe: 60 s) nach, ob eine Gruppe +fällig ist. Ist sie es: + +1. **Gäste bestimmen** — nach den Auswahlregeln der Gruppe +2. **Snapshot anlegen**, ein Gast nach dem anderen; dazwischen optional + `pause_between` +3. **Warten, bis Proxmox wirklich fertig ist** — nicht nur, bis der Aufruf + zurückkommt +4. **Aufräumen** — alles, was `keep_count` oder `keep_time` reißt + +Schritt 3 ist wichtiger, als er aussieht. Proxmox nimmt beim Anlegen eine +Sperre auf dem Storage (`cfs-lock 'storage-'`). Würde pvesnap gleich die +nächste VM anstoßen, sperrten sich die eigenen Läufe gegenseitig aus. Mehr dazu +unter [Fehlersuche](../nachschlagen/fehlersuche.md#storage-lock). + +Wird ein Termin verpasst, weil der Host aus war, wird er beim nächsten Start +**nachgeholt** — einmal, nicht für jeden ausgefallenen Termin einzeln. + +--- + +## Was in einen Snapshot gehört + +| Einstellung | Vorgabe | | +|---|---|---| +| `vmstate` | `no` | Arbeitsspeicher mitsichern — nur QEMU, nur bei laufender VM | +| `skip_stopped` | `no` | gestoppte Gäste überspringen | +| `description` | siehe unten | Beschreibung, die am Snapshot hängt | + +### `vmstate` — der Arbeitsspeicher + +Mit `vmstate = yes` wird der RAM mitgeschrieben. Die Maschine friert dafür kurz +ein, und es braucht Platz in Höhe des zugewiesenen Arbeitsspeichers — bei einer +32-GB-Datenbank also 32 GB **pro Snapshot**. + +Für regelmäßige Läufe ist das meist zu teuer. Es hat aber einen konkreten +Nutzen: Beim [Wiederherstellen](../wiederherstellen/index.md) läuft die +Maschine dann genau dort weiter, wo sie stand — statt zu booten wie nach einem +Stromausfall. Datenbanken sind dabei bereits offen und konsistent. + +!!! tip "Der Mittelweg" + + Eine eigene Gruppe mit `vmstate = yes`, die einmal täglich läuft und nur + zwei Stände vorhält. Kostet zweimal RAM-Größe und gibt im Ernstfall einen + warmen Einstiegspunkt. + +### Die Beschreibung + +Sie steht in der Proxmox-Oberfläche neben jedem Snapshot: + +```ini +description = pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time} | max: {keep_count} +``` + +Verfügbare Platzhalter: + +| | | | +|---|---|---| +| `{group}` | `{group_slug}` | `{schedule}` | +| `{vmid}` | `{name}` | `{node}` | +| `{type}` | `{pool}` | `{tags}` | +| `{date}` | `{time}` | `{datetime}` | +| `{timestamp}` | `{keep_time}` | `{keep_count}` | + +Ein unbekannter Platzhalter führt nicht zum Abbruch — es wird dann eine +brauchbare Ersatzbeschreibung eingesetzt. + +--- + +## Weiter + +* [Der Konfigurationseditor](editor.md) — alles das mit Menüs statt INI-Datei +* [Zeitpläne](zeitplan.md) +* [Welche Gäste?](auswahl.md) +* [Vorhaltezeit](vorhaltezeit.md) +* [Im Betrieb](betrieb.md) — Dienst, Protokoll, Probelauf diff --git a/handbuch/docs/sichern/vorhaltezeit.md b/handbuch/docs/sichern/vorhaltezeit.md new file mode 100644 index 0000000..8618bf1 --- /dev/null +++ b/handbuch/docs/sichern/vorhaltezeit.md @@ -0,0 +1,159 @@ +# Vorhaltezeit + +Ohne Aufräumen wächst die Zahl der Snapshots ins Unendliche — und jeder hält +alte Blöcke fest. Die Vorhaltezeit ist deshalb keine Kür. + +```ini +keep_count = 24 # höchstens 24 Snapshots je VM und Gruppe (0 = unbegrenzt) +keep_time = 7d # nichts älter als 7 Tage (0 = unbegrenzt) +keep_min = 1 # so viele bleiben in jedem Fall stehen +``` + +--- + +## Die beiden Grenzen + +Beide sind kombinierbar. Gelöscht wird, was **eine** der beiden reißt — es ist +ein ODER, kein UND. + +!!! example "Beispiel: `keep_count = 24`, `keep_time = 2d`" + + Eine stündliche Gruppe. Nach zwei Tagen sind 48 Snapshots angefallen — + `keep_count` greift und lässt nur die letzten 24 stehen. + + Läuft eine VM zwei Wochen lang nicht (mit `skip_stopped = yes`), sind ihre + Snapshots irgendwann älter als zwei Tage — `keep_time` greift und räumt sie + weg, auch wenn es weniger als 24 sind. + +**Mindestens eine der beiden muss gesetzt sein.** Sonst würde die Zahl der +Snapshots unbegrenzt wachsen; `pvesnap check` meldet das als Fehler. + +--- + +## `keep_min` — die Untergrenze + +```ini +keep_min = 1 +``` + +So viele Snapshots bleiben in jedem Fall stehen, auch wenn sie beide Grenzen +reißen. + +Der Sinn: `keep_time = 2d` würde bei einer Maschine, die drei Tage aus war, +**alles** löschen — und damit den letzten bekannten Stand. Mit `keep_min = 1` +bleibt immer einer übrig. + +!!! tip "Eine Empfehlung" + + `keep_min = 1` in jeder Gruppe mit `keep_time`. Es kostet fast nichts und + verhindert den einen Fall, in dem das Aufräumen genau das wegräumt, was man + gebraucht hätte. + +--- + +## Getrennt je VM und Gruppe + +Beide Grenzen zählen **pro Gast und pro Gruppe**, nicht insgesamt. + +`keep_count = 24` bei 30 Maschinen heißt also: bis zu 720 Snapshots — aber je +Maschine nur 24. Und eine VM, die in drei Gruppen steckt, hat 24 + 14 + 6 +Snapshots nebeneinander, die sich nicht gegenseitig verdrängen. + +--- + +## Was pvesnap anfasst — und was nicht + +Aufgeräumt wird ausschließlich, was zum +[Namensschema](index.md#namensschema) der jeweiligen Gruppe passt: + +``` +auto-taeglich-20260809-023000 ← wird von der Gruppe "taeglich" gezählt +auto-stuendlich-20260809-100000 ← zählt für "stuendlich", nicht für "taeglich" +vor-update-14.2 ← wird nie angefasst +``` + +!!! success "Von Hand angelegte Snapshots bleiben" + + Ein Snapshot, den jemand in der Proxmox-Oberfläche angelegt hat, passt nie + auf das Muster. Er zählt weder gegen `keep_count`, noch wird er jemals + gelöscht. + + Das hat eine Kehrseite: Er wird auch nicht aufgeräumt. Wer sich vor einem + Update einen Sicherheitsstand anlegt, sollte ihn hinterher selbst wieder + entfernen — sonst hält er ewig alte Blöcke fest. + +--- + +## Nur aufräumen, ohne Neues anzulegen + +```bash +pvesnap prune # alle Gruppen +pvesnap prune -g stuendlich # nur diese +pvesnap prune --dry-run # zeigt, was wegkäme +``` + +Praktisch nach dem Verkürzen einer Vorhaltezeit: Der Dienst würde erst beim +nächsten regulären Termin aufräumen, `prune` macht es sofort. + +--- + +## Platz im Blick behalten + +```bash +pvesnap list # alle pvesnap-Snapshots +pvesnap list -a # auch fremde +``` + +Und auf dem Storage selbst: + +=== "Ceph / RBD" + + ```bash + ceph df # MAX AVAIL beachten, nicht nur % RAW USED + rbd -p du # PROVISIONED gegen USED + ``` + +=== "ZFS" + + ```bash + zfs list -o space # USEDSNAP zeigt, was die Snapshots kosten + ``` + +=== "LVM-thin" + + ```bash + lvs -o lv_name,data_percent,metadata_percent + ``` + +!!! danger "Ein volles Storage blockiert alles" + + Läuft ein Ceph-Pool oder ein Thin-Pool voll, stehen **alle** Volumes darin + — nicht nur die schreibfreudige VM. Und das Aufräumen wird dann selbst + schwierig, weil Löschen ebenfalls ein Schreibvorgang ist. + + Bei Ceph ist `MAX AVAIL` die Zahl, auf die es ankommt; `% RAW USED` täuscht, + weil es die Replikation nicht berücksichtigt. + +--- + +## Wie viel kostet ein Snapshot? + +Anfangs: nichts. Ein Snapshot ist ein Zeiger, kein Abbild. + +Er wächst mit dem, was **danach** geschrieben wird — jeder geänderte Block +bedeutet, dass der alte Stand zusätzlich vorgehalten werden muss. + +| VM | Verhalten | +|---|---| +| Ein Fileserver, auf dem selten geschrieben wird | Snapshots kosten fast nichts, lange Vorhaltezeiten sind billig | +| Eine Datenbank mit ständigem Schreibverkehr | Jeder Snapshot wächst spürbar; kurze Vorhaltezeit, dafür engmaschig | +| Eine VM, die gerade ein Update bekommt | Ein einzelner Snapshot kann in Minuten mehrere GB kosten | + +Deshalb passen kurze Intervalle und lange Vorhaltezeiten schlecht zusammen. Ein +guter Ausgangspunkt: + +```ini +[group:stuendlich] keep_count = 24 keep_time = 2d +[group:taeglich] keep_count = 14 keep_time = 21d +[group:monatlich] keep_count = 6 keep_time = 400d +``` diff --git a/handbuch/docs/sichern/zeitplan.md b/handbuch/docs/sichern/zeitplan.md new file mode 100644 index 0000000..053f02f --- /dev/null +++ b/handbuch/docs/sichern/zeitplan.md @@ -0,0 +1,129 @@ +# Zeitpläne + +Jede Gruppe hat **entweder** ein Intervall **oder** einen festen Termin. Beides +gleichzeitig geht nicht — `pvesnap check` weist darauf hin. + +--- + +## Intervall + +```ini +interval = 30m # 30m, 1h, 6h, 2d12h, 1w ... +align = yes +``` + +Zeitangaben gelten überall im selben Format: `30m`, `1h`, `6h`, `2d12h`, `1w`. +`0` heißt „unbegrenzt". + +Der Unterschied steckt in `align`: + +| | | +|---|---| +| `align = yes` | **an der Uhr ausgerichtet** — 00:00, 00:30, 01:00 … Die Snapshots liegen auf runden Zeiten, unabhängig davon, wann der Dienst gestartet wurde. | +| `align = no` | **30 Minuten nach dem letzten Lauf.** Startet der Dienst um 14:07 neu, liegen die Snapshots danach bei :07 und :37. | + +Für alles, was regelmäßig aussehen soll, ist `align = yes` die richtige Wahl. +`align = no` ist nur dann sinnvoll, wenn der Abstand wichtiger ist als der +Zeitpunkt. + +--- + +## Fester Termin + +```ini +schedule = daily +at = 02:30 +``` + +| `schedule` | zusätzliche Angaben | ergibt | +|---|---|---| +| `hourly` | `minute = 15` | jede Stunde um :15 | +| `daily` | `at = 02:30` | täglich um 02:30 | +| `weekly` | `at`, `day_of_week = so` | sonntags um 03:00 | +| `monthly` | `at`, `day_of_month = 1` | am 1. jedes Monats | +| `yearly` | `at`, `day_of_month`, `month` | einmal jährlich | + +Wochentage: `mo di mi do fr sa so` (auch `mon`, `tue` … funktionieren). + +### Der 31. in kurzen Monaten + +```ini +schedule = monthly +day_of_month = 31 +``` + +Wird automatisch auf den **letzten Tag des Monats** gezogen — im Februar also +auf den 28. bzw. 29. Kein ausgefallener Lauf, keine Sonderbehandlung nötig. + +--- + +## Verpasste Termine + +Läuft der Host zum Termin nicht, wird der Lauf beim nächsten Start +**nachgeholt**. Einmal — nicht für jeden ausgefallenen Termin einzeln. + +Ein Host, der eine Woche aus war, legt beim Hochfahren also einen täglichen +Snapshot an, nicht sieben. + +Merken tut sich das die Zustandsdatei: + +```ini +[global] +state_file = /var/lib/pvesnap/state.json +``` + +Wird sie gelöscht, gilt jede Gruppe als „noch nie gelaufen" und wird beim +nächsten Prüfintervall sofort ausgeführt. + +--- + +## Beim Start sofort loslegen + +```ini +[global] +run_on_start = no +``` + +Mit `yes` macht der Dienst direkt nach dem Start einen Durchlauf, statt auf den +nächsten regulären Termin zu warten. + +Auf einem Host, der oft neu startet, führt das zu vielen zusätzlichen +Snapshots. Vorgabe ist deshalb `no`. + +--- + +## Die Zeitumstellung + +!!! warning "Gerechnet wird mit lokaler Zeit" + + Ein täglicher Termin um 02:30 kann in der Umstellungsnacht **ausfallen** + (im Frühjahr existiert 02:30 nicht) oder **doppelt anstehen** (im Herbst + gibt es sie zweimal). + + Wen das stört, legt den Termin auf eine Zeit außerhalb des Fensters — + 04:00 statt 02:30 — oder nimmt ein Intervall statt eines festen Termins. + +--- + +## Wie oft ist richtig? + +Es gibt keine allgemeingültige Antwort, aber eine brauchbare Faustregel: Der +Abstand ist die Datenmenge, die du im Ernstfall verlierst. + +| Abstand | Verlust im schlimmsten Fall | Passt zu | +|---|---|---| +| 15 min | eine Viertelstunde Arbeit | Datenbanken, Warenwirtschaft | +| 1 h | eine Stunde | Anwendungsserver | +| täglich | ein Arbeitstag | Fileserver, Infrastruktur | +| monatlich | — | der lange Rückweg, „wie war es vor dem Update?" | + +Was dagegen spricht, öfter zu sichern, ist der **Platz**: Jeder Snapshot hält +die alten Blöcke fest. Bei einer schreibfreudigen VM wächst das schnell. Deshalb +gehören kurze Intervalle immer mit kurzer [Vorhaltezeit](vorhaltezeit.md) +zusammen — 24 stündliche Snapshots über zwei Tage sind billiger als sieben +tägliche über eine Woche. + +!!! tip "Gestoppte Gäste überspringen" + + Bei kurzen Intervallen lohnt sich `skip_stopped = yes`. Sonst sammelt eine + abgeschaltete Test-VM stündlich Snapshots vom immer gleichen Zustand. diff --git a/handbuch/docs/stil/favicon.svg b/handbuch/docs/stil/favicon.svg new file mode 100644 index 0000000..2ddcb47 --- /dev/null +++ b/handbuch/docs/stil/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/handbuch/docs/stil/handbuch.css b/handbuch/docs/stil/handbuch.css new file mode 100644 index 0000000..5426a64 --- /dev/null +++ b/handbuch/docs/stil/handbuch.css @@ -0,0 +1,45 @@ +/* Bildschirmfotos aus dem Terminal. + Sie sind SVG, also beliebig skalierbar - eine feste Breite wuerde sie nur + unnoetig klein halten. Der Rahmen soll sich in beiden Farbschemata absetzen. */ +.md-typeset img[src$=".svg"] { + width: 100%; + max-width: 100%; + display: block; + border-radius: 8px; + box-shadow: 0 2px 14px rgba(0, 0, 0, .28); + margin: 1.2em 0 .6em; +} + +/* Die Bildunterschrift direkt darunter, kleiner und ruhiger. */ +.md-typeset figure { + margin: 1.4em 0; +} + +.md-typeset figcaption { + font-size: .78rem; + color: var(--md-default-fg-color--light); + margin-top: -.2em; + text-align: left; +} + +/* Tastenkuerzel in Tabellen nicht umbrechen lassen. */ +.md-typeset table kbd, +.md-typeset table code { + white-space: nowrap; +} + +/* Die Tabellen im Nachschlageteil sind breit - lieber scrollen als quetschen. */ +.md-typeset__table { + width: 100%; +} + +/* Ein ruhiger Kasten fuer die "So geht es"-Ablaeufe. */ +.md-typeset .ablauf { + border-left: 3px solid var(--md-accent-fg-color); + padding: .1em 0 .1em 1em; + margin: 1.2em 0; +} + +.md-typeset .ablauf p { + margin: .3em 0; +} diff --git a/handbuch/docs/wiederherstellen/arbeiten.md b/handbuch/docs/wiederherstellen/arbeiten.md new file mode 100644 index 0000000..a4a6ea4 --- /dev/null +++ b/handbuch/docs/wiederherstellen/arbeiten.md @@ -0,0 +1,176 @@ +# Damit arbeiten + +++enter++ auf einer Maschine in der Übersicht öffnet die Detailansicht. Das ist +der Bildschirm, auf dem man im Ernstfall die meiste Zeit verbringt: + +![Die Detailansicht](../bilder/recovery-detail.svg) + +| Taste | | +|---|---| +| ++s++ | starten | +| ++h++ | [herunterfahren](#herunterfahren) | +| ++e++ | [Austauschlaufwerk auswerfen / einklinken](transfer.md#im-laufenden-betrieb-wechseln) | +| ++v++ | [Austauschlaufwerke verwalten](transfer.md) | +| ++p++ | [SPICE und USB](dongle.md#nachtraglich-umstellen) | +| ++f++ | [vom Quell-Snapshot lösen](loesen.md) | +| ++x++ | [verwerfen](verwerfen.md) | +| ++q++ | zurück | + +--- + +## An die Konsole kommen + +In der Detailansicht steht die fertige Adresse: + +``` +https://10.20.0.11:8006/?console=kvm&novnc=1&vmid=9101&node=pve1&resize=off&cmd= +``` + +Dasselbe liefert `pvesnap-recovery console 9101`. Es ist die ganz normale +noVNC-Konsole von Proxmox — die Maschine taucht auch in der Weboberfläche auf, +markiert mit dem Tag `pvesnap-recovery`. + +Bei Containern geht zusätzlich `pct enter 9101` auf dem Host. + +!!! info "Warum eine IP-Adresse und kein Name?" + + Vorne steht bewusst die **IP** des Nodes, nicht sein Name: Auf dem + Proxmox-Host löst der Name auf, am Arbeitsplatz meist nicht. Sie kommt aus + `/etc/pve/.members`. + + Der Node hinter `node=` bleibt der Name — so erwartet Proxmox ihn. + +Für SPICE und USB-Weiterleitung siehe [Dongle und USB](dongle.md). + +--- + +## Was man drinnen tut + +Die Maschine ist voll beschreibbar. Alles, was man an einer echten Maschine +täte, geht auch hier — nur eben ohne Netz. + +Der typische Ablauf für eine Datenbank: + +
+ +**1.** Im Gast prüfen, ob der Dienst läuft — mit geladenem Arbeitsspeicher ist +er bereits offen + +**2.** Dump schreiben, auf das [Austauschlaufwerk](transfer.md): +`pg_dump -Fc kunden > /media/DUMPS/kunden.dump` + +**3.** Im Gast aushängen (`umount`, unter Windows „Auswerfen") + +**4.** In pvesnap ++e++ — Laufwerk auswerfen + +**5.** ++v++ ++enter++ — Commander auf das Laufwerk, Dump abholen + +**6.** ++x++ — Maschine verwerfen + +
+ +Schritt 3 ist der, den man vergisst. Mehr dazu unter +[Austauschlaufwerke](transfer.md#im-laufenden-betrieb-wechseln). + +--- + +## Herunterfahren + +Taste ++h++ fährt **sauber** herunter — und schaltet nicht von selbst hart ab. + +Weigert sich der Gast, kommt eine Wahl: + +| | | +|---|---| +| **Im Gast selbst herunterfahren** | pvesnap wartet und meldet, wenn sie aus ist | +| **Hart ausschalten** | wie Stecker ziehen, mit ausdrücklicher Bestätigung | +| **Abbrechen** | läuft weiter | + +!!! quote "Warum nicht einfach nach 60 Sekunden abschalten?" + + Weil ein stilles Abschalten nach Zeitablauf ein Stromausfall mit Ansage + wäre — und genau der Grund, warum ein Dateisystem hinterher unsauber ist. + + Wenn man gerade dabei ist, aus einer Maschine konsistente Daten zu holen, + ist das die falsche Voreinstellung. + +Beim Warten zeigt pvesnap, was zu tun ist, und verfolgt den Zustand: + +``` +Bitte jetzt IM GAST herunterfahren. + + Windows Start -> Ein/Aus -> Herunterfahren + Linux poweroff bzw. shutdown -h now + +Zustand: running seit 1:23 +``` + +++q++ bricht das Warten ab — die Maschine läuft dann weiter. + +--- + +## Neu starten + +Ein Neustart wird nötig, wenn sich die [Grafikkarte ändert](dongle.md) — also +beim Ein- oder Ausschalten von SPICE. Dann kommt dieselbe Wahl: + +![Wie soll die Maschine neu starten?](../bilder/recovery-neustartwahl.svg) + +| | | +|---|---| +| **Ich fahre im Gast herunter** | pvesnap wartet, startet danach selbst wieder — **der verlässliche Weg** | +| **Proxmox herunterfahren lassen (ACPI)** | klappt bei Linux, bei Windows oft nicht | +| **Gar nicht** | später selbst; die Änderung greift beim nächsten Start | + +!!! warning "ACPI und Windows" + + Bei Linux funktioniert der ACPI-Aus-Knopf zuverlässig. Windows blockt ihn + gern: Dort hält eine Anwendung den Vorgang auf, oder der Shutdown Event + Tracker fragt nach einem Grund und wartet auf eine Eingabe, die nie kommt. + + Wer im Gast selbst herunterfährt, umgeht das. Deshalb steht dieser Weg an + erster Stelle. + +!!! danger "Ein Neustart *im* Gast hilft nicht" + + Dabei setzt sich nur die Maschine zurück — der QEMU-Prozess läuft weiter, + mit der alten Grafikkarte. Es braucht wirklich den Umweg über „aus und + wieder an". + +### Der geladene Arbeitsspeicher ist danach weg + +!!! danger "Endgültig" + + Proxmox gibt den kopierten Arbeitsspeicher schon beim **ersten** Start + wieder frei. Ein zweiter Start bootet also kalt — offene Programme und + ungespeicherte Daten sind dann verloren. + + pvesnap warnt vorher ausdrücklich, wenn eine Maschine mit geladenem + Arbeitsspeicher neu starten soll: + +![Warnung vor dem Neustart](../bilder/recovery-neustart.svg) + +Wer den warmen Zustand braucht, erledigt seine Arbeit also **vor** dem ersten +Neustart — oder verzichtet gleich mit `--no-resume` darauf und bootet kalt. + +--- + +## Starten und Stoppen von außen + +```bash +pvesnap-recovery start 9101 +pvesnap-recovery stop 9101 +pvesnap-recovery list +pvesnap-recovery console 9101 +``` + +`list` zeigt dieselbe Tabelle wie die Übersicht: + +``` +Maschine Zustand Node Herkunft Modus +VM 9101 running pve1 VM 101 @ auto-stuendlich-20260809-… live +VM 9102 stopped pve2 VM 105 @ auto-taeglich-20260809-02… recover +``` + +Steht in der Spalte **Zustand** ein `weg`, wurde die Maschine außerhalb von +pvesnap entfernt — dann hilft [`cleanup`](verwerfen.md#cleanup). diff --git a/handbuch/docs/wiederherstellen/dongle.md b/handbuch/docs/wiederherstellen/dongle.md new file mode 100644 index 0000000..3daa49a --- /dev/null +++ b/handbuch/docs/wiederherstellen/dongle.md @@ -0,0 +1,176 @@ +# Dongle und USB + +Eine abgeschottete Maschine hat kein Netzwerk. Ein Software-Schutzmodul hängt +sonst an einem USB-Server im Netz — der ist damit unerreichbar. Und ohne +Lizenzprüfung startet die Warenwirtschaft nicht, aus der man gerade Daten holen +wollte. + +**SPICE löst das.** Die USB-Weiterleitung kommt vom **Rechner des Bedieners**, +nicht über das Gastnetz. Die Maschine bleibt also vollständig abgeschottet und +hat trotzdem ihren Dongle. + +``` +Dein Arbeitsplatz Proxmox-Host Gast + Dongle am USB ──SPICE──► QEMU ──virtuell──► "USB-Gerät" + remote-viewer (kein Netz nötig) +``` + +--- + +## Einrichten + +Beim Anlegen in der [Optionsmaske](einrichten.md#die-optionsmaske) die beiden +Felder setzen — oder direkt: + +```bash +pvesnap-recovery live 105 --usb 2 --no-resume +``` + +`--usb` schaltet `--spice` automatisch mit ein. + +Die Zusammenfassung sagt dann, was zu tun ist: + +![Vorhaben mit SPICE und USB](../bilder/dongle-vorhaben.svg) + +--- + +## Verbinden + +```bash +pvesnap-recovery spice 9104 -o vm.vv # Verbindungsdatei erzeugen +scp root@10.20.0.12:vm.vv . # auf den Arbeitsplatz holen +remote-viewer vm.vv # öffnen +``` + +Ohne `-o` wird die Datei auf die Standardausgabe geschrieben — praktisch für +eine Pipeline: + +```bash +ssh root@pve2 pvesnap-recovery spice 9104 > vm.vv && remote-viewer vm.vv +``` + +Im **remote-viewer** dann unter **Datei → USB-Geräteauswahl** das Gerät +anhaken. Es taucht sofort im Gast auf. + +!!! success "Guest-Tools braucht es dafür nicht" + + Der Dongle erscheint im Gast als gewöhnliches USB-Gerät; QEMU und der + SPICE-Client machen die Arbeit. + + Die Guest-Tools (`tools/get-guest-tools.sh` holt virtio-win auf einen + ISO-Storage) sind nur für Zwischenablage, automatische Auflösung und + Mauszeiger nützlich. Für den Dongle sind sie überflüssig. + +Die Verbindungsdatei ist **kurzlebig**: Das darin enthaltene Kennwort gilt nur +für wenige Sekunden. Sie muss also zeitnah nach dem Erzeugen benutzt werden — +nicht am Vortag vorbereiten. + +--- + +## Zwei Dinge, die man vorher wissen sollte + +### USB braucht SPICE + +Ohne `vga: qxl` legt QEMU die Weiterleitungen zwar an, aber es gibt keinen +Kanal, der sie transportiert — die Maschine startet dann mit `no spice port` +gar nicht erst. + +Deshalb schaltet `--usb` die Anzeige automatisch mit ein. + +Umgekehrt gilt das nicht: Wer SPICE **ausschaltet**, verliert die USB-Anschlüsse +mit — ohne Kanal wären sie ohnehin wirkungslos. pvesnap sagt das dazu, statt sie +stillschweigend stehen zu lassen. + +### SPICE und geladener Arbeitsspeicher schließen sich aus + +Das ist der unangenehme Teil, und er hängt davon ab, was im **Original** steht: + +| Original | warmer RAM-Zustand | SPICE + Dongle | +|---|---|---| +| `vga: std` (Vorgabe) | ✅ | nur mit `--no-resume` | +| `vga: qxl` | ✅ | ✅ **gleichzeitig** | + +Der Grund: `qxl` bringt 64 MB Grafikspeicher mit, die Vorgabe 16. Der +gespeicherte Zustand passt dann nicht mehr, und das Laden bricht ab: + +``` +Size mismatch: vga.vram +``` + +Die Maschine bleibt danach angehalten stehen, während Proxmox `TASK OK` meldet. +`pvesnap-recovery` lehnt die Kombination deshalb von vornherein ab und nennt die +Auswege. + +!!! tip "Der Griff, der sich lohnt — für Produktivmaschinen" + + Setz auf Maschinen, die ein Schutzmodul brauchen, **einmal** `vga: qxl`: + + ```bash + qm set 105 --vga qxl + ``` + + Dann tragen alle künftigen Snapshots es mit, und im Ernstfall gibt es + warmen RAM-Zustand **und** Dongle gleichzeitig. + + **Nachträglich lässt sich das bei einem vorhandenen Snapshot nicht mehr + reparieren** — der gespeicherte Zustand enthält die alte Grafikkarte. Es ist + also eine Entscheidung, die man vorher trifft oder gar nicht. + + Auf der Produktivmaschine kostet `qxl` nichts außer ein paar Megabyte + Grafikspeicher. + +--- + +## Nachträglich umstellen + +Taste ++p++ in der [Detailansicht](arbeiten.md) ändert SPICE und USB auch an +einer bestehenden Maschine: + +![SPICE ein- oder ausschalten](../bilder/recovery-spice.svg) + +Dann die Zahl der Anschlüsse: + +![Wie viele USB-Anschlüsse?](../bilder/recovery-usb.svg) + +| Änderung | | +|---|---| +| **SPICE ein oder aus** | **Neustart nötig** — die Grafikkarte lässt sich im Betrieb nicht wechseln | +| **USB dazu oder weg** | sofort, solange der SPICE-Kanal schon steht | + +Ist ein Neustart nötig, warnt pvesnap vorher deutlich: + +![Warnung vor dem nötigen Neustart](../bilder/recovery-neustart.svg) + +… und lässt danach die Wahl, **wie** neu gestartet wird — siehe +[Damit arbeiten](arbeiten.md#neu-starten). + +!!! danger "Bei geladenem Arbeitsspeicher" + + Der ist nach dem Neustart **endgültig weg**. Wer SPICE nachrüstet, um an + den Dongle zu kommen, verliert also genau den warmen Zustand, wegen dem er + vielleicht überhaupt mit `--resume` gestartet ist. + + Deshalb: die Entscheidung möglichst **vor** dem ersten Start treffen. + +Höchstens **14 Anschlüsse** sind möglich — mehr sieht QEMU nicht vor. Für einen +Dongle reicht einer; zwei sind bequem, wenn man zusätzlich einen USB-Stick +durchreichen will. + +--- + +## Wenn der Dongle nicht auftaucht + +| Symptom | woran es liegt | +|---|---| +| Kein Eintrag unter „USB-Geräteauswahl" | Der `remote-viewer` läuft auf dem falschen Rechner — er muss dort laufen, wo der Dongle steckt. | +| Maschine startet nicht, `no spice port` | `vga: qxl` fehlt. In der Detailansicht ++p++ → SPICE ein. | +| Gerät angehakt, im Gast nichts | Manche Dongles brauchen im Gast einen Treiber. Der gehört auf ein [Austauschlaufwerk](transfer.md). | +| `.vv`-Datei wird abgewiesen | Sie ist zu alt — neu erzeugen, das Kennwort gilt nur kurz. | +| Unter Linux fehlt `remote-viewer` | Paket `virt-viewer` installieren. | + +!!! note "Der Dongle bleibt am Arbeitsplatz" + + Er wird nicht in den Host gesteckt und nicht ins Netz gehängt. Genau das ist + der Punkt: Der Weg führt über die SPICE-Verbindung, die ohnehin schon + zwischen Arbeitsplatz und Maschine besteht — und nicht über ein Netz, das + die abgeschottete Maschine gar nicht hat. diff --git a/handbuch/docs/wiederherstellen/einrichten.md b/handbuch/docs/wiederherstellen/einrichten.md new file mode 100644 index 0000000..ba1c459 --- /dev/null +++ b/handbuch/docs/wiederherstellen/einrichten.md @@ -0,0 +1,188 @@ +# Eine Maschine einrichten + +```bash +pvesnap-recovery +``` + +Dann ++n++ in der Übersicht. Der Assistent fragt nacheinander nach Gast und +Snapshot — dieselben Auswahlfenster wie im +[Explorer](../holen/explorer.md#1-welcher-gast) — und landet dann in der +Optionsmaske. + +--- + +## Die Optionsmaske + +![Die Optionsmaske](../bilder/recovery-optionen.svg) + +Pfeiltasten bewegen sich, ++enter++ oder ++space++ ändert ein Feld. Unten steht +zu jedem Feld eine Erklärung — man muss also nichts auswendig wissen. + +| Taste | | +|---|---| +| ++enter++ ++space++ | Feld ändern | +| ++v++ | [Austauschlaufwerke verwalten](transfer.md) — ohne die Maske zu verlassen | +| ++f10++ | anlegen | +| ++q++ | abbrechen | + +### Betriebsart + +![Die Wahl der Betriebsart](../bilder/recovery-betriebsart.svg) + +| | | +|---|---| +| **live** | abgeschottet, ohne Netzwerk. Zum Hineinschauen. | +| **recovery** | mit Netzwerk und gleicher Identität. Siehe [Mit Netzwerk](netzwerk.md). | + +### Die übrigen Felder + +| Feld | Vorgabe | | +|---|---|---| +| **Netzwerk** | automatisch (nach Modus) | `keine Karte` / `Karte ohne Leitung` / `am Netz` — überschreibt die Vorgabe der Betriebsart | +| **Arbeitsspeicher** | wenn im Snapshot vorhanden | mit `nein` bootet die Maschine kalt | +| **Transfer-Laufwerke** | — | [Austauschlaufwerke](transfer.md), mehrere möglich | +| **SPICE-Anzeige** | nein | zusätzlich zu noVNC, nötig für [USB](dongle.md) | +| **USB-Weiterleitung** | keine | Anzahl der Anschlüsse; schaltet SPICE mit ein | +| **Neue VMID** | nächste freie | | +| **Node** | der des Originals | bei lokalem Storage nicht änderbar | +| **Danach starten** | ja | mit `nein` nur einrichten | + +--- + +## Austauschlaufwerke auswählen + +Auf dem Feld **Transfer-Laufwerke** öffnet ++enter++ die Mehrfachauswahl: + +![Austauschlaufwerke auswählen](../bilder/transfer-auswahl.svg) + +++space++ wählt aus, ++enter++ übernimmt. Belegte Laufwerke lassen sich nicht +anhängen — sie sind entweder am Host eingehängt oder stecken schon in einer +anderen Maschine. Warum das so streng ist, steht unter +[Austauschlaufwerke](transfer.md#host-oder-gast-nie-beides). + +Fehlt noch eins, führt ++v++ direkt in die Verwaltung und wieder zurück. + +--- + +## Die Zusammenfassung + +++f10++ zeigt, was passieren wird — **bevor** etwas passiert: + +![Das Vorhaben prüfen](../bilder/recovery-vorhaben.svg) + +Hier lohnt sich das Lesen. Vier Arten von Zeilen stehen darin: + +| | | +|---|---| +| **Klone** | welche Datenträger geklont werden, mit Größe | +| **Hinweise** (`-`) | was pvesnap von sich aus angepasst hat und warum | +| **ACHTUNG** | Dinge, die man wissen sollte, die aber in Ordnung sind | +| **GEFAHR** | Dinge, die so nicht gehen — siehe [Mit Netzwerk](netzwerk.md) | + +Im Bild sind es drei Hinweise, die je einen Sachverhalt erklären: + +> *Netzwerkkarte bleibt vorhanden, aber abgeklemmt (link_down): mit geladenem +> Arbeitsspeicher darf sich die Geräteausstattung nicht ändern.* + +Die Karte zu entfernen würde den gespeicherten RAM-Zustand unbrauchbar machen — +also bleibt sie drin, und stattdessen wird die Leitung gekappt. + +> *Proxmox gibt den kopierten Arbeitsspeicher nach dem ersten Start wieder frei +> — ein späterer Neustart bootet dann kalt.* + +Der warme Zustand ist **einmalig**. Wer ihn braucht, sollte gleich beim ersten +Start damit arbeiten. + +Mit ++j++ geht es los, mit ++n++ zurück. + +--- + +## Auf der Kommandozeile + +```bash +pvesnap-recovery live 101 # neuester Snapshot +pvesnap-recovery live 101 auto-stuendlich-20260809-100000 # ein bestimmter +pvesnap-recovery live 101 --newid 9103 --transfer werkzeuge -y +``` + +Ohne Snapshot-Namen wird der neueste brauchbare genommen. + +| Parameter | Bedeutung | +|---|---| +| `--newid ` | VMID der neuen Maschine (Vorgabe: nächste freie) | +| `--node ` | auf welchem Node sie laufen soll | +| `--net none\|down\|on` | keine Karte / Karte ohne Leitung / voll am Netz | +| `--resume` / `--no-resume` | Arbeitsspeicher laden bzw. bewusst kalt starten | +| `--transfer ` | Austauschlaufwerk anhängen (mehrfach möglich) | +| `--spice` | SPICE-Anzeige (`vga: qxl`), zusätzlich zu noVNC | +| `--usb ` | so viele USB-Weiterleitungen über SPICE (schaltet `--spice` mit ein) | +| `--iso ` | Abbild als CD einlegen, z. B. `local:iso/virtio-win.iso` | +| `--memory `, `--cores ` | abweichende Ausstattung (nicht mit `--resume`) | +| `--name ` | Name der neuen Maschine | +| `--keep-binds` | durchgereichte Host-Verzeichnisse des Originals übernehmen | +| `--no-start` | nur einrichten, nicht starten | +| `-y`, `--yes` | Routinefragen überspringen | +| `--force` | auch anlegen, wenn das Original noch läuft | + +Ohne `-y` wird die Zusammenfassung angezeigt und nachgefragt. Ein Probelauf ist +das aber nicht — wer nur sehen will, was geschähe, bricht an der Rückfrage ab. + +!!! danger "`-y` deckt `--force` nicht ab" + + `-y` überspringt **Routinefragen**. Es winkt nicht durch, dass eine + Wiederherstellung mit Netzwerk startet, während das Original läuft — das + verlangt ausdrücklich `--force`. Siehe [Mit Netzwerk](netzwerk.md). + +--- + +## Was dabei angelegt wird + +1. **Klone** aller Datenträger des Snapshots (`PVE::Storage::vdisk_clone`) +2. eine **Konfigurationsdatei** unter `/etc/pve/nodes//qemu-server/.conf` +3. der **Tag** `pvesnap-recovery` an der neuen Maschine +4. ein Eintrag in der Merkliste `/var/lib/pvesnap/recovery.json` + +Geht dabei etwas schief, wird **alles wieder abgeräumt** — es bleiben keine +halben Klone liegen. + +!!! info "Auf Ceph wird der Quell-Snapshot geschützt" + + RBD verlangt für einen Klon, dass der Quell-Snapshot geschützt ist + (`rbd snap protect`); Proxmox setzt das beim Klonen selbst. + + Solange die Wiederherstellung existiert, ist ihr Quell-Snapshot damit + **unlöschbar** — die [Vorhaltezeit](../sichern/vorhaltezeit.md) kommt an ihm + nicht vorbei. Beim [Verwerfen](verwerfen.md) wird der Schutz wieder + aufgehoben. + + Das ist der Grund, eine Wiederherstellung nicht länger stehen zu lassen als + nötig — oder sie mit [`flatten`](loesen.md) zu lösen. + +--- + +## Was übernommen wird und was nicht + +**Übernommen:** SMBIOS-UUID, `vmgenid`, MAC-Adressen, CPU- und +Speicherausstattung, Bootreihenfolge, alle Datenträger als Klone, bei Containern +der Hostname. + +**Weggelassen:** + +| | warum | +|---|---| +| `onboot`, `startup` | eine Wiederherstellung soll nicht beim nächsten Hostneustart von selbst hochkommen | +| `protection` | sie soll sich verwerfen lassen | +| `replicate` | sonst würde der Klon auf andere Nodes repliziert | +| `hookscript` | fremde Skripte laufen nicht ungefragt in der Kopie | +| `description`, `tags` | wird durch die eigenen ersetzt | +| durchgereichte Host-Verzeichnisse | nur mit `--keep-binds` | +| `parent`, `snaptime`, `snapstate` | gehören zum Snapshot, nicht zur Maschine | + +!!! warning "`--keep-binds` mit Bedacht" + + Reicht das Original ein Host-Verzeichnis in den Container durch, sieht die + Wiederherstellung mit `--keep-binds` **dieselben, echten Daten** — nicht + deren Stand vom Snapshot. Und sie kann hineinschreiben. + + Ohne die Option bleibt das Verzeichnis weg; in der Zusammenfassung steht + dann, was weggelassen wurde. diff --git a/handbuch/docs/wiederherstellen/index.md b/handbuch/docs/wiederherstellen/index.md new file mode 100644 index 0000000..232c73e --- /dev/null +++ b/handbuch/docs/wiederherstellen/index.md @@ -0,0 +1,179 @@ +# Wiederherstellen + +Manchmal reicht es nicht, einzelne Dateien herauszukopieren. + +Eine Datenbank liegt im Snapshot als Haufen halbfertiger Dateien — brauchbar +wird sie erst, wenn der Datenbankserver läuft und selbst einen Dump schreibt. +Eine Warenwirtschaft prüft beim Start ihre Lizenz. Ein Verzeichnisdienst ist +ohne laufende Maschine überhaupt nicht zu befragen. + +`pvesnap-recovery` startet den Snapshot deshalb als **eigenständige Maschine** — +ohne das Original anzufassen. + +![Übersicht der Wiederherstellungen](../bilder/recovery-uebersicht.svg) + +--- + +## Zwei Betriebsarten + +| | **live** | **recovery** | +|---|---|---| +| Netzwerk | keins | volles Netz | +| Identität | bleibt (UUID, MACs, Hostname) | bleibt (UUID, MACs, Hostname) | +| Wofür | hineinschauen, Dump ziehen, Daten holen | die Maschine wirklich wieder in Betrieb nehmen | +| Risiko | keins — sie kann nichts erreichen | hoch, solange das Original läuft | + +**Der Unterschied ist ausschließlich das Netzwerk** — und die Zahl der +Sicherheitsabfragen. An den Datenträgern ändert der Modus nichts; beide klonen +gleich. + +`live` ist der Normalfall. Die Maschine ist vollständig abgeschottet: Sie kann +nichts erreichen und niemanden stören, auch wenn sie dieselbe IP-Adresse +konfiguriert hat wie das noch laufende Original. Daten kommen über ein +[Austauschlaufwerk](transfer.md) heraus. + +`recovery` ist der Ernstfall — und gefährlich, solange das Original läuft. +Details unter [Mit Netzwerk](netzwerk.md). + +--- + +## Warum das schnell geht + +Die Datenträger werden nicht kopiert, sondern **geklont**. Auf Ceph/RBD, ZFS und +LVM-thin ist das ein Copy-on-Write-Klon: fertig in Sekunden, egal wie groß die +Platte ist, und anfangs ohne zusätzlichen Platzbedarf. + +``` +Klon ~1 s egal ob 32 GB oder 2 TB +Start Sekunden mit RAM-Zustand sofort im laufenden Zustand +Arbeiten ab jetzt voll beschreibbar +``` + +Eine 32-GB-Platte war im Test nach **0,8 Sekunden** geklont. + +Dafür wird `PVE::Storage::vdisk_clone` benutzt — dieselbe Funktion, die auch +Proxmox selbst für Klone verwendet, samt Cluster-Sperre auf dem Storage. Es +wird nichts nachgebaut, was Proxmox schon kann. + +!!! info "Kein „Live-Restore"— besser" + + Beim Live-Restore eines Backup-Servers liegt die Sicherung auf einem anderen + Medium: Die VM startet zwar sofort, holt die Blöcke aber im Hintergrund + übers Netz nach und läuft bis dahin gebremst. + + Hier wird **gar nichts nachgeladen**. Der Klon liegt im selben Pool und ist + aus Sicht des Gastes von Sekunde eins an vollständig — bei voller + Geschwindigkeit. Bei einer 2-TB-VM wird also nicht stundenlang kopiert und + dann gestartet; gearbeitet wird ab der ersten Sekunde. + +!!! warning "Nicht auf jedem Storage" + + Auf Datei-Storages (`dir`, `nfs`, `cifs`) geht das nicht — aus einem + qcow2-internen Snapshot lässt sich kein Klon ziehen. Für diese Storages + bleibt der Weg über den [Explorer](../holen/explorer.md). + + Was welches Storage kann, steht unter + [Speicherarten](../nachschlagen/speicher.md). + +--- + +## Der Arbeitsspeicher kommt mit + +Enthält der Snapshot den Arbeitsspeicher (`vmstate = yes` in der Gruppe), wird +er mitgenommen. Die Maschine **bootet dann nicht**, sondern läuft genau dort +weiter, wo sie beim Snapshot stand: + +* kein Crash-Recovery, kein `fsck`, kein Journal-Rollback +* Datenbanken sind bereits offen und konsistent +* die Uhr im Gast steht auf dem Snapshot-Zeitpunkt + +Im Test war das gut zu sehen: Die Konsole zeigte 10:38 — die Uhrzeit des +Snapshots — während auf dem Host längst 11:24 war. + +Damit das klappt, muss die Geräteausstattung **exakt** zum gespeicherten Zustand +passen. `pvesnap-recovery` sorgt selbst dafür: + +| | | +|---|---| +| `vmgenid`, `smbios1` | bleiben erhalten — fehlen sie, bricht das Laden mit `Unknown savevm section or instance 'vmgenid'` ab | +| `runningmachine`, `runningcpu` | werden aus dem Snapshot übernommen | +| Netzwerkkarte | bleibt **vorhanden**, wird aber abgeklemmt (`link_down=1`) statt entfernt | +| Austauschlaufwerke | werden erst **nach** dem Fortsetzen angesteckt (Hotplug) — der Gast wacht in einem Zustand auf, in dem es die Platte noch nicht gab | + +!!! note "Ein Detail, das Proxmox nicht selbst meldet" + + Schlägt das Laden des Arbeitsspeichers fehl, quittiert Proxmox den Start + trotzdem mit `TASK OK` und lässt die Maschine angehalten stehen. + + `pvesnap-recovery` liest das Task-Protokoll mit, setzt die Maschine fort und + sagt deutlich, wenn statt des RAM-Standes kalt gebootet wurde. + +Mit `--no-resume` lässt sich der Arbeitsspeicher bewusst weglassen — etwa, wenn +[SPICE](dongle.md) gebraucht wird, das sich damit ausschließt. + +--- + +## Der übliche Ablauf + +
+ +**1.** [Austauschlaufwerk vorbereiten](transfer.md) — *vorher*, in Ruhe + +**2.** [Maschine einrichten](einrichten.md) — Snapshot wählen, Optionen prüfen, anlegen + +**3.** [Damit arbeiten](arbeiten.md) — noVNC-Konsole, Dump ziehen, Laufwerk auswerfen + +**4.** [Verwerfen](verwerfen.md) — oder [dauerhaft übernehmen](loesen.md) + +
+ +Bedient wird alles entweder über die ncurses-Oberfläche: + +```bash +pvesnap-recovery +``` + +… oder direkt über Parameter: + +```bash +pvesnap-recovery live 101 # neuester Snapshot, abgeschottet +pvesnap-recovery live 101 auto-stuendlich-20260809-100000 +pvesnap-recovery recover 101 --newid 9101 # mit Netzwerk +pvesnap-recovery list +pvesnap-recovery destroy 9101 +``` + +Vollständig unter [Alle Befehle](../nachschlagen/befehle.md#pvesnap-recovery). + +--- + +## Was in der Übersicht steht + +| Spalte | | +|---|---| +| **Maschine** | die neue VMID | +| **Zustand** | `running`, `stopped` — oder `weg`, wenn sie außerhalb von pvesnap entfernt wurde | +| **Node** | auf welchem Cluster-Knoten sie läuft | +| **Herkunft** | Original-VMID und Snapshot, aus dem sie stammt | +| **Modus** | `live` oder `recover` | + +| Taste | | +|---|---| +| ++enter++ | [Detailansicht](arbeiten.md) | +| ++n++ | [neue Wiederherstellung](einrichten.md) | +| ++s++ / ++h++ | starten / herunterfahren | +| ++x++ | [verwerfen](verwerfen.md) | +| ++v++ | [Austauschlaufwerke](transfer.md) | +| ++r++ | Liste neu einlesen | +| ++q++ | Ende | + +!!! tip "Wiederhergestellte Maschinen tragen einen Tag" + + Jede von `pvesnap-recovery` angelegte Maschine bekommt in Proxmox den Tag + **`pvesnap-recovery`**. Daran erkennt man sie in der Weboberfläche — und + daran erkennt `destroy`, dass es sie anfassen darf. Eine von Hand angelegte + VM lässt sich damit nicht versehentlich löschen. + + Der Tag ist auch der Grund, warum + `exclude_tags = nosnap, pvesnap-recovery` in jede `all`-Gruppe gehört, siehe + [Auswahl](../sichern/auswahl.md#ausschlusse). diff --git a/handbuch/docs/wiederherstellen/loesen.md b/handbuch/docs/wiederherstellen/loesen.md new file mode 100644 index 0000000..3acbff7 --- /dev/null +++ b/handbuch/docs/wiederherstellen/loesen.md @@ -0,0 +1,194 @@ +# Vom Snapshot lösen + +Ein Linked Clone hängt für immer am Quell-Snapshot. Fürs Hineinschauen ist das +ideal — es kostet nichts und geht in Sekunden. Für eine **dauerhaft übernommene** +Maschine ist es ein Problem: + +* der Quell-Snapshot lässt sich nicht mehr löschen — die + [Vorhaltezeit](../sichern/vorhaltezeit.md) scheitert mit + `snapshot is protected` +* die Original-VM muss mitsamt ihrer Platte bestehen bleiben +* der Klon wächst ohnehin mit jedem Schreibvorgang + +Die Detailansicht sagt, woran man ist: + +![Linked Clone in der Detailansicht](../bilder/recovery-detail.svg) + +``` +Datentraeger: Linked Clone - haengt am Quell-Snapshot + Belegt nur, was seither geschrieben wurde. Der Quell-Snapshot ist dafuer unloeschbar. + Fuer den Dauerbetrieb mit f loesen (kopiert die Daten wirklich). +``` + +--- + +## Lösen + +Taste ++f++ in der Detailansicht — oder: + +```bash +pvesnap-recovery flatten 9101 +``` + +Zuerst wird gemessen, wie viel kopiert werden muss: + +![Der Lösen-Dialog mit der Größe](../bilder/recovery-loesen.svg) + +Dahinter steckt `rbd flatten`: Die Daten aus dem Elternteil werden jetzt +wirklich in den Klon kopiert. Danach hängt die Maschine an nichts mehr, und der +Schutz der Quell-Snapshots wird **automatisch aufgehoben** — die Vorhaltezeit +kommt wieder durch. + +Schnell ist es, solange Platz da ist: 13 GB waren im Test in rund +**70 Sekunden** kopiert. + +--- + +## Die Maschine läuft dabei weiter + +!!! success "`rbd flatten` ist eine Online-Operation" + + Im Test blieb die VM über den gesamten Vorgang auf `qmpstatus: running`, der + QEMU-Monitor antwortete durchgehend, und die Uhr auf der Konsole lief weiter + — inklusive geladenem RAM-Zustand. + +Auch Daten, die **während** des Abkoppelns geschrieben werden, überleben es. +Gemessen an einem Container, der durchgehend schrieb: + +| | | +|---|---| +| vor dem Flatten geschrieben | 64 MB Zufallsdaten, SHA-256 danach **OK** | +| während des Flatten geschrieben | 192 MB Zufallsdaten, SHA-256 danach **OK** | +| Schreibvorgänge im Zeitfenster | 12, von 19:35:48 bis 19:36:10 (Flatten: 19:35:48–19:36:11) | +| Zustand des Gastes | durchgehend `running` | + +Der Grund: `rbd flatten` füllt nur die Blöcke auf, die der Klon noch **nicht** +selbst besitzt. Was der Gast bereits geschrieben hat, gehört ihm — und wird +nicht überschrieben. + +`flatten` betrifft also ausschließlich die *Abhängigkeit* vom Quell-Snapshot, +nie die Verfügbarkeit. + +--- + +## Der Platzbedarf — und warum `rbd du` täuscht + +!!! warning "Nicht von der Snapshot-Zeile täuschen lassen" + + Die Zeile eines Snapshots in `rbd du` zeigt nur dessen **Zuwachs**, nicht + seinen Inhalt. + + Im Test stand `@handtest01` mit `USED 56 MiB` da — kopiert wurden beim + Flatten trotzdem über 8 GB. Denn sichtbar ist an dieser Stelle der gesamte + Inhalt der Kette (` 14 GiB`). + + Deshalb misst `pvesnap-recovery flatten` das vorher richtig und nennt die + Größe, bevor es losgeht. + +### Es wird geprüft, ob der Platz reicht + +!!! danger "Ein volllaufender Ceph-Pool blockiert alles" + + Läuft der Pool während des Kopierens voll, blockiert er **jeden** + Schreibvorgang. Dann steht nicht nur das Flatten, sondern jede VM auf diesem + Storage — und selbst das Aufräumen wird schwierig, weil Löschen ebenfalls + ein Schreibvorgang ist. + +`flatten` bricht deshalb ab, wenn weniger als das **1,15-fache** des Bedarfs +frei ist. `--force` setzt sich darüber hinweg. + +Vorher selbst nachsehen: + +```bash +ceph df # MAX AVAIL beachten, nicht % RAW USED +``` + +`% RAW USED` täuscht, weil es die Replikation nicht berücksichtigt: Bei +dreifacher Replikation kosten 13 GB Nutzdaten 39 GB im Pool. + +--- + +## Nachsehen, was los ist + +```bash +rbd -p ls -l | grep -E 'NAME|vm-9101' # Spalte PARENT +rbd -p du | grep -E 'NAME|vm-9101' # PROVISIONED gegen USED +``` + +Eine frisch geklonte 32-GB-Platte steht dort mit `PROVISIONED 32 GiB` und +`USED 80 MiB` — die 80 MiB sind alles, was der laufende Gast seither geschrieben +hat. + +Nach dem Flatten ist die Spalte `PARENT` leer, und `USED` entspricht dem +tatsächlichen Inhalt. + +--- + +## Auf anderen Speicherarten + +Das ganze Thema ist eine **Ceph-Eigenheit**. Andere Storages verhalten sich +anders: + +| Storage | Hängt am Quell-Snapshot? | `flatten` | +|---|---|---| +| `rbd` (Ceph) | **ja** — Snapshot wird geschützt und unlöschbar | nötig für den Dauerbetrieb | +| `lvmthin` | **nein** | nicht nötig | +| `zfspool` | ja | nicht möglich | + +### LVM-thin ist hier angenehmer als Ceph + +Ein Thin-Snapshot ist ein eigenständiges Volume, das sich mit anderen nur die +Blöcke im Pool teilt. Nachgemessen: + +``` +Belegung im Thin-Pool nach dem Klonen (256 MB Nutzdaten): + data <19.84g 1.26 % ← Pool hält die Daten nur EINMAL + snap_..._wdhtest 1.00g + vm-9998-disk-0 1.00g 25.00 % Klon + vm-9999-disk-0 1.00g 25.00 % Original + +Quell-Snapshot löschen, während der Klon existiert → erlaubt +Original löschen → erlaubt +Prüfsumme des Klons danach → unverändert +``` + +Also: platzsparend geteilt **und** trotzdem frei löschbar. `flatten` meldet +solche Datenträger entsprechend als „nicht nötig". + +Der Preis liegt woanders: `lvmthin` ist **lokaler** Speicher. Die +Wiederherstellung muss auf demselben Node laufen wie das Original; +`pvesnap-recovery` prüft das und lehnt einen anderen Node ab. Mit Ceph ist sie +dagegen auf jedem Node des Clusters startbar. + +!!! warning "Auch Thin-Pools laufen voll" + + Und dann stehen alle Volumes darin. Überprovisionierung im Blick behalten: + + ```bash + lvs -o lv_name,data_percent,metadata_percent + ``` + +### Bei ZFS geht es nicht + +Der Klon hängt wie bei Ceph am Snapshot. `zfs promote` würde die Abhängigkeit +nur **umdrehen** statt auflösen — danach hinge das Original am Klon, was das +Problem nicht löst, sondern verschiebt. + +Echte Unabhängigkeit ginge nur über eine Vollkopie per `zfs send | zfs recv`. +Das macht `flatten` nicht, und es sagt das auch so: + +``` +nicht moeglich - ein ZFS-Klon haengt am Snapshot. Loesen ginge nur +ueber 'zfs send | zfs recv' in einen neuen Datentraeger +``` + +--- + +## Wann lösen, wann nicht? + +| Lage | | +|---|---| +| Kurz hineinschauen, Dump ziehen, verwerfen | **nicht lösen.** Kostet nur Platz und Zeit. | +| Die Maschine bleibt ein paar Tage stehen | **lösen**, sonst blockiert sie die Vorhaltezeit des Originals | +| Die Maschine übernimmt dauerhaft | **lösen** — und zwar vor dem Löschen des Originals | +| Auf LVM-thin | nichts zu tun | diff --git a/handbuch/docs/wiederherstellen/netzwerk.md b/handbuch/docs/wiederherstellen/netzwerk.md new file mode 100644 index 0000000..e48677e --- /dev/null +++ b/handbuch/docs/wiederherstellen/netzwerk.md @@ -0,0 +1,167 @@ +# Mit Netzwerk + +```bash +pvesnap-recovery recover 101 auto-stuendlich-20260809-100000 --newid 9101 +``` + +Der Ernstfall: Die Maschine soll nicht nur untersucht, sondern **wieder in +Betrieb genommen** werden. + +Dabei bleibt alles erhalten, was die Maschine ausmacht: + +* SMBIOS-UUID und `vmgenid` +* MAC-Adressen aller Netzwerkkarten +* bei Containern der Hostname + +Für alles im Netz ist sie damit **dieselbe Maschine** — inklusive +Lizenzbindungen, AD-Mitgliedschaft und DHCP-Reservierungen. Genau das will man +im Ernstfall. + +--- + +## Genau deshalb ist sie gefährlich + +!!! danger "Zwei Maschinen, eine Identität" + + Dieselbe MAC und dieselbe IP zweimal im selben Netz geben Chaos: ARP-Tabellen + schlagen um, Verbindungen brechen sporadisch ab, ein Verzeichnisdienst + bekommt widersprüchliche Anmeldungen, und die Datenbank wird von zwei Seiten + beschrieben. + + Und das Unangenehme daran: Es fällt nicht sofort auf. Es fällt eine halbe + Stunde später auf, wenn niemand mehr weiß, was zuletzt geändert wurde. + +`pvesnap-recovery` prüft deshalb vorher, ob das Original noch läuft — und lässt +sich in dem Fall **nicht beiläufig durchwinken**: + +| Situation | Verhalten | +|---|---| +| ncurses-Oberfläche | Zusammenfassung nur mit ++j++, danach eine **zweite** Rückfrage, die den Grund beim Namen nennt | +| `recover …` im Terminal | das Wort **`ja`** muss ausgeschrieben werden — ++j++ reicht nicht | +| `recover … -y` | **wird abgewiesen** (Exit-Code 2) | +| `recover … --force` | läuft durch — die bewusste Entscheidung | + +!!! quote "Warum `-y` nicht reicht" + + `-y` heißt „keine Routinefragen", nicht „frag mich auch dann nicht, wenn es + weh tut". Ein Skript, das mit `-y` läuft, soll nicht versehentlich eine + zweite Domänencontroller-Identität ins Netz stellen. + + Wer es wirklich will, schreibt `--force` — und hat es dann bewusst getan. + +--- + +## Der sichere Weg + +
+ +**Erste Wahl:** Das Original **vorher stoppen**. + +```bash +qm stop 101 +pvesnap-recovery recover 101 --newid 9101 +``` + +**Zweite Wahl:** Mit abgeklemmter Leitung starten und die Karte erst zuschalten, +wenn klar ist, dass die Luft rein ist. + +```bash +pvesnap-recovery recover 101 --newid 9101 --net down +# ... in der Konsole prüfen, ob es die richtige Maschine ist ... +qm set 9101 --net0 virtio=BC:24:11:00:65:40,bridge=vmbr0 +``` + +
+ +Der zweite Weg hat einen praktischen Vorteil: Man sieht der Maschine über die +noVNC-Konsole an, ob es wirklich der richtige Stand ist — **bevor** sie +irgendjemand im Netz erreicht. + +--- + +## Die drei Netzwerkzustände + +| `--net` | | +|---|---| +| `none` | **keine Netzwerkkarte.** Die Maschine hat gar kein Netz. Vorgabe bei `live`. | +| `down` | **Karte vorhanden, Leitung abgeklemmt** (`link_down=1`). Der Gast sieht seine Karte, aber nichts kommt durch. | +| `on` | **voll am Netz**, mit den MAC-Adressen des Originals. Vorgabe bei `recover`. | + +Der Unterschied zwischen `none` und `down` ist wichtiger, als er aussieht: + +!!! info "Mit geladenem Arbeitsspeicher wird aus `none` automatisch `down`" + + Ein gespeicherter RAM-Zustand lässt sich nur in eine Maschine mit **exakt + derselben Geräteausstattung** laden. Eine entfernte Netzwerkkarte wäre eine + Änderung — das Laden würde scheitern. + + Deshalb bleibt die Karte in diesem Fall vorhanden und wird stattdessen + abgeklemmt. In der Zusammenfassung steht dazu ein Hinweis; man muss also + nicht selbst daran denken. + +--- + +## Was danach zu tun ist + +Eine Wiederherstellung, die dauerhaft bleiben soll, ist noch nicht fertig: + +
+ +**1.** [Vom Quell-Snapshot lösen](loesen.md) — sonst hängt sie für immer daran, +und die Vorhaltezeit kommt an den Snapshots des Originals nicht mehr vorbei + +**2.** Den Tag `pvesnap-recovery` entfernen, wenn sie zur regulären Maschine +werden soll: + +```bash +qm set 9101 --tags produktion,datenbank +``` + +**3.** Aus der Merkliste nehmen — nach dem Entfernen des Tags taucht sie in +`pvesnap-recovery list` ohnehin nicht mehr als lebend auf + +**4.** `onboot` wieder setzen, falls sie beim Hostneustart hochkommen soll: + +```bash +qm set 9101 --onboot 1 +``` + +
+ +!!! warning "Schritt 1 ist keine Kür" + + Solange die Maschine ein Linked Clone ist, kann der Quell-Snapshot nicht + gelöscht werden — und das Original muss mitsamt seiner Platte bestehen + bleiben. Im Protokoll des Dienstes stünde dauerhaft + `snapshot is protected`. + + Siehe [Vom Snapshot lösen](loesen.md). + +!!! note "`onboot` und `startup` werden bewusst weggelassen" + + Eine frisch angelegte Wiederherstellung soll nicht beim nächsten + Hostneustart von selbst hochkommen — schon gar nicht eine mit den + MAC-Adressen einer noch laufenden Maschine. Wer sie behält, setzt das selbst. + +--- + +## Und das Original? + +Wenn die Wiederherstellung übernimmt, will das Original meistens weg. Aber +**nicht sofort**: + +!!! danger "Erst lösen, dann löschen" + + Solange die Wiederherstellung ein Linked Clone ist, hängt sie an den + Datenträgern des Originals. Das Original zu löschen, würde die + Wiederherstellung mitnehmen. + + Reihenfolge: + + 1. `pvesnap-recovery flatten 9101` — abwarten, bis es durch ist + 2. prüfen: Die Detailansicht muss *„eigenständig — hängt an keinem Snapshot"* + zeigen + 3. erst dann das Original entfernen + +Auf LVM-thin entfällt das — dort sind die Klone von sich aus unabhängig. Siehe +[Speicherarten](../nachschlagen/speicher.md). diff --git a/handbuch/docs/wiederherstellen/transfer.md b/handbuch/docs/wiederherstellen/transfer.md new file mode 100644 index 0000000..f34fa85 --- /dev/null +++ b/handbuch/docs/wiederherstellen/transfer.md @@ -0,0 +1,228 @@ +# Austauschlaufwerke + +Eine abgeschottete Wiederherstellung hat **kein Netzwerk**. Kein SCP, kein +Netzlaufwerk, kein Cloud-Speicher. Wie kommt der Datenbank-Dump dann heraus? + +Über ein **Austauschlaufwerk**: eine benannte Abbilddatei unter +`/var/lib/pvesnap/transfer/`, die sich am Host ganz normal einhängen lässt und +einem Gast als zusätzliche Platte angehängt wird. + +``` +/var/lib/pvesnap/transfer/dumps.img 20G "Datenbank-Dumps und Exporte" +/var/lib/pvesnap/transfer/werkzeuge.img 4G "Skripte, Treiber, Installer" +``` + +!!! success "Der eigentliche Sinn: vorher, nicht mittendrin" + + Ein Austauschlaufwerk gehört zu keiner Maschine und überlebt jede + Wiederherstellung. Man legt es **einmal in Ruhe an**, packt hinein, was man + im Notfall braucht — und wählt es im Ernstfall nur noch aus einer Liste aus. + + Im Notfall ist keine Zeit, sich Werkzeuge zusammenzusuchen. Und die + Maschine, in der man sie bräuchte, kommt nicht ins Netz. + +--- + +## Die Übersicht + +Taste ++v++ — erreichbar aus der Übersicht, aus der +[Optionsmaske](einrichten.md#die-optionsmaske) und aus der +[Detailansicht](arbeiten.md): + +![Übersicht der Austauschlaufwerke](../bilder/transfer-uebersicht.svg) + +| Taste | | +|---|---| +| ++enter++ | am Host einhängen und im Commander öffnen | +| ++n++ | neues Laufwerk anlegen | +| ++u++ | am Host aushängen | +| ++l++ | löschen | +| ++r++ | neu einlesen | +| ++q++ | zurück | + +Die Spalte **Zustand** ist die wichtigste. Sie kennt drei Werte: + +| Zustand | Farbe | bedeutet | +|---|---|---| +| `frei` | — | gehört niemandem, kann angehängt oder eingehängt werden | +| `am Host eingehängt` | grün | liegt gerade unter `/run/pvesnap/transfer/` | +| `in Benutzung von 9101` | gelb | steckt in dieser Maschine | + +--- + +## Ein Laufwerk anlegen + +Taste ++n++, dann drei Fragen: + +![Ein neues Laufwerk anlegen](../bilder/transfer-neu.svg) + +| Frage | | +|---|---| +| **Name** | kurz, klein geschrieben, z. B. `dumps`. Wird zur Bezeichnung im Gast (`DUMPS`). | +| **Größe** | `20G`, `500M`, `2T` | +| **Wofür** | freier Text, taucht in der Übersicht auf | + +Dabei passiert: + +1. Abbilddatei anlegen (dünn besetzt — sie belegt anfangs nichts) +2. GPT-Partitionstabelle mit einer Partition, Typ `msftdata` +3. exFAT-Dateisystem, Bezeichnung = Name in Großbuchstaben +4. Rechte `0777` und eine `LIESMICH.txt` hineinlegen + +!!! note "Die Größe ist eine Obergrenze, keine Reservierung" + + Die Datei ist dünn besetzt: Ein 20-GB-Laufwerk mit 3 GB Inhalt belegt 3 GB + auf dem Host. In der Übersicht stehen beide Zahlen nebeneinander — + **Größe** und **belegt**. + +--- + +## Befüllen und auslesen + +++enter++ hängt das Laufwerk am Host ein und öffnet den +[Commander](../holen/explorer.md) darauf: + +![Der Commander auf einem Austauschlaufwerk](../bilder/transfer-commander.svg) + +Links das Laufwerk, rechts der Host. **Beide Seiten sind beschreibbar** — anders +als beim Snapshot-Explorer, wo links Schreibschutz herrscht. Kopiert wird mit +++f5++ in beide Richtungen. + +Beim Verlassen mit ++q++ wird das Laufwerk automatisch wieder ausgehängt. + +!!! tip "Was hineingehört" + + Alles, was man im Gast bräuchte und dort nicht herunterladen kann: + + * ein passendes `pg_dump` / `mysqldump` in der richtigen Version + * ein Packprogramm (`7z.exe` für Windows-Gäste) + * eigene Auswerteskripte + * bei Windows die virtio-Treiber, falls die Maschine sie braucht + * ein leeres Verzeichnis `ausgang/` für das, was herauskommen soll + +--- + +## Host oder Gast, nie beides + +Zwei unabhängige Einhängungen desselben Blockgeräts zerlegen das Dateisystem. +Nicht „vielleicht" — zuverlässig, weil beide Seiten Puffer halten, von denen +die andere nichts weiß. + +Die Verwaltung lässt das deshalb nicht zu: + +| Versuch | | +|---|---| +| Anhängen, während der Host es hält | abgelehnt | +| Aushängen, während ein Gast darauf arbeitet | abgelehnt | +| Löschen, solange es überhaupt in Benutzung ist | abgelehnt | + +Woher sie das weiß: Sie liest die **Gast-Konfigurationen** unter +`/etc/pve/nodes/*/`. Dort steht der Eintrag auch dann, wenn die Maschine gerade +nicht läuft — was die Wahrheit besser trifft als jede Merkliste. + +!!! info "Bei Containern ist es anders" + + Dort ist es kein Blockgerät, sondern ein durchgereichtes Verzeichnis + (Bind-Mount). Host und Container sehen dieselben Dateien **gleichzeitig** — + es ist dasselbe Dateisystem, nicht zweimal eingehängt. + + Dort gibt es also nichts auszuwerfen und nichts abzuwarten. + +--- + +## Im laufenden Betrieb wechseln + +Taste ++e++ in der [Detailansicht](arbeiten.md) zieht das Laufwerk bei laufender +Maschine ab und gibt es wieder hinein — beliebig oft: + +
+ +**auswerfen** → gehört wieder dem Host: einhängen, auslesen, neu befüllen + +**einklinken** → zurück in denselben Steckplatz, die VM läuft durchgehend + +
+ +Das ist der Weg, ohne die Maschine anzufassen an Zwischenergebnisse zu kommen — +Dump ziehen, abholen, weitermachen. + +!!! warning "Im Gast vorher aushängen" + + Unter Linux `umount`, unter Windows „Auswerfen" im Explorer. + + Proxmox meldet das Gerät zwar ordentlich ab, aber ein Dateisystem, auf das + gerade geschrieben wird, nimmt das übel. Hält der Gast es fest, schlägt das + Auswerfen mit einer entsprechenden Meldung fehl — dann erst im Gast + aushängen und noch einmal. + +Die Detailansicht zeigt jederzeit, was steckt: + +``` +Transfer-Laufwerke: + dumps eingesteckt als scsi1 + e = auswerfen / einklinken, im laufenden Betrieb +``` + +--- + +## Warum exFAT + +| | | +|---|---| +| **Windows und Linux** lesen und schreiben es von Haus aus | kein Treiber, keine Nachinstallation im Gast | +| **Keine 4-GB-Grenze** je Datei | ein 30-GB-Dump passt | +| **Keine Besitzrechte auf der Platte** | keine Rechteprobleme zwischen Host und Gast, keine `chown`-Runden | + +Die Bezeichnung darf höchstens **11 Zeichen** haben — daher der Rat, den Namen +kurz zu halten. Eingehängt wird mit `umask=0000`, damit auch ein +unprivilegierter Container hineinschreiben kann (dessen `root` ist auf dem Host +die UID 100000). + +### Warum eine Datei und kein Proxmox-Volume? + +!!! danger "Ein Volume würde mitgelöscht" + + Beim Entfernen der Maschine — auch aus der Proxmox-Weboberfläche heraus. + Die mühsam vorbereitete Werkzeugsammlung wäre weg, und zwar genau in dem + Moment, in dem jemand aufräumt. + + Pfade überspringt PVE beim Zerstören ausdrücklich: + `return if $volid =~ m|^/|`. Deshalb eine Datei. + +Angehängt wird über ein **Loop-Gerät**, weil Proxmox als Pfad nur `/dev/…` +akzeptiert — eine Abbilddatei direkt einzutragen lehnt es ab +(`unable to associate path to any storage`). + +Die GPT-Partition trägt bewusst den Typ `msftdata`. Ohne diese Angabe vergibt +`parted` den Typ „Linux filesystem", und Windows gibt dem Laufwerk dann keinen +Buchstaben. + +--- + +## Auf der Kommandozeile + +```bash +pvesnap-recovery live 101 --transfer werkzeuge --transfer dumps +``` + +Mehrfach möglich. Die Laufwerke müssen frei sein, sonst bricht das Anlegen mit +einer Meldung ab. + +Angelegt und verwaltet werden sie nur über die Oberfläche — dafür gibt es +bewusst keine Parameter. Das ist eine Vorbereitungsaufgabe, keine, die man +unter Zeitdruck tippt. + +--- + +## Wo sie liegen + +``` +/var/lib/pvesnap/transfer/index.json Verzeichnis der Laufwerke +/var/lib/pvesnap/transfer/dumps.img die Abbilddatei +/run/pvesnap/transfer/dumps Einhängepunkt am Host +``` + +!!! warning "`uninstall.sh --purge` nimmt sie mit" + + Ohne `--purge` bleibt alles liegen. Mit `--purge` ist auch der Inhalt der + Austauschlaufwerke weg. Siehe [Installation](../installation.md#deinstallation). diff --git a/handbuch/docs/wiederherstellen/verwerfen.md b/handbuch/docs/wiederherstellen/verwerfen.md new file mode 100644 index 0000000..a42c3c1 --- /dev/null +++ b/handbuch/docs/wiederherstellen/verwerfen.md @@ -0,0 +1,146 @@ +# Verwerfen und aufräumen + +## Verwerfen + +Taste ++x++ in der Übersicht oder in der Detailansicht: + +![Sicherheitsabfrage vor dem Verwerfen](../bilder/recovery-verwerfen.svg) + +Oder auf der Kommandozeile: + +```bash +pvesnap-recovery destroy 9101 +pvesnap-recovery destroy 9101 -y # ohne Rückfrage +``` + +`destroy` räumt vollständig auf: + +
+ +**1.** Maschine stoppen + +**2.** Gast samt aller Klone entfernen + +**3.** Austauschlaufwerke freigeben (der **Inhalt bleibt** — sie gehören zu +keiner Maschine) + +**4.** Den **Schutz der Quell-Snapshots wieder aufheben** + +**5.** Eintrag aus der Merkliste nehmen + +
+ +--- + +### Schritt 4 ist der wichtige + +!!! info "Warum der Snapshot-Schutz wieder weg muss" + + Ceph verlangt für einen Klon, dass der Quell-Snapshot geschützt ist + (`rbd snap protect`), und Proxmox setzt das beim Klonen selbst. + + Bliebe der Schutz stehen, könnte die [Vorhaltezeit](../sichern/vorhaltezeit.md) + diesen Snapshot später nicht mehr löschen — im Protokoll stünde dann immer + wieder: + + ``` + snapshot is protected + ``` + + Solange eine Wiederherstellung existiert, ist ihr Quell-Snapshot also + **bewusst** unlöschbar. Danach nicht mehr. + +--- + +### Was `destroy` nicht anfasst + +!!! success "Nur Maschinen mit dem Tag `pvesnap-recovery`" + + Eine von Hand angelegte VM lässt sich damit nicht versehentlich löschen — + auch dann nicht, wenn sie zufällig eine VMID hat, die einmal einer + Wiederherstellung gehörte. + +**Austauschlaufwerke bleiben.** Sie gehören zu keiner Maschine; ihr Inhalt +überlebt jede Wiederherstellung. Sie werden nur wieder als `frei` markiert. + +**Das Original bleibt.** Selbstverständlich — `destroy` fasst ausschließlich die +Wiederherstellung an. + +--- + +## `cleanup` + +Wird eine Wiederherstellung in der **Proxmox-Oberfläche** entfernt statt mit +`destroy`, bleibt etwas liegen: + +* der Klon selbst kann übrigbleiben +* der Quell-Snapshot bleibt **geschützt** — die Vorhaltezeit scheitert dann + dauerhaft + +```bash +pvesnap-recovery cleanup # zeigt Gefundenes, fragt, räumt auf +pvesnap-recovery cleanup --all # auch Datenträger ohne Elternteil +pvesnap-recovery cleanup -y # ohne Rückfrage +``` + +### Wonach gesucht wird + +Nach Datenträgern der Form `vm--disk-N`, zu deren VMID es **keine +Konfigurationsdatei** unter `/etc/pve/nodes/*/` mehr gibt. + +!!! info "Bewusst nicht über `/cluster/resources`" + + Ein Gast auf einem abgemeldeten Node taucht dort unter Umständen nicht auf + — und dann würden die Platten einer **lebenden** Maschine als verwaist + gelten. + + Die Konfigurationsdateien im Cluster-Dateisystem sind die verlässlichere + Quelle. + +Zwei weitere Vorsichtsmaßnahmen: + +| | | +|---|---| +| **Ohne Elternteil** wird nichts von selbst entfernt | dafür braucht es `--all` | +| **Der Schutz eines Snapshots wird nur gelöst**, wenn wirklich kein Klon mehr daran hängt | sonst würde ein noch lebender Klon seine Grundlage verlieren | + +### Wann man es braucht + +* Jemand hat eine Wiederherstellung in der Weboberfläche gelöscht +* Ein `destroy` ist mittendrin abgebrochen (Host neu gestartet, Netz weg) +* Im Protokoll des Dienstes taucht wiederholt `snapshot is protected` auf + +Der letzte Fall ist der häufigste — und der, den man sonst lange sucht. + +--- + +## Aufräumen prüfen + +```bash +pvesnap-recovery list # sind noch welche eingetragen? +qm list | grep -i recovery # laufen noch welche? +rbd -p ls | grep -E 'vm-9[0-9]{3}-' # liegen noch Klone herum? +rbd -p snap ls vm-101-disk-0 # steht noch ein Schutz? +``` + +Bei der letzten Ausgabe steht in der Spalte `PROTECTED` ein `yes`, solange ein +Snapshot geschützt ist. Ohne zugehörigen Klon ist das ein Fall für `cleanup`. + +--- + +## Vor der Deinstallation + +`uninstall.sh` warnt, wenn noch Wiederherstellungen offen sind. Der Grund ist +derselbe: + +!!! warning "Erst verwerfen, dann deinstallieren" + + Nach dem Entfernen von pvesnap gibt es kein `destroy` und kein `cleanup` + mehr. Die Klone müsste man dann von Hand aufspüren, und den Snapshot-Schutz + von Hand lösen: + + ```bash + rbd -p snap unprotect vm-101-disk-0@auto-stuendlich-20260809-100000 + ``` + + Mit Werkzeug ist das ein Tastendruck. Ohne eine Stunde Sucherei. diff --git a/handbuch/mkdocs.yml b/handbuch/mkdocs.yml new file mode 100644 index 0000000..ab92f55 --- /dev/null +++ b/handbuch/mkdocs.yml @@ -0,0 +1,121 @@ +site_name: pvesnap — Handbuch +site_description: >- + Automatische Snapshots für Proxmox VE: einrichten, Dateien zurückholen und + einen Snapshot als laufende Maschine starten. +site_author: pvesnap +copyright: Handbuch zu pvesnap + +docs_dir: docs +site_dir: site + +# Das Handbuch muss offline funktionieren - im Notfall steht vielleicht das +# halbe Netz. Deshalb: keine absolute site_url, keine Schriften vom CDN, +# Suchindex liegt neben den Seiten. +use_directory_urls: true + +theme: + name: material + language: de + font: false # keine Google-Fonts, sonst braucht es Netz + # Bewusst nicht unter bilder/ - dort raeumt werkstatt/aufnehmen.sh auf. + favicon: stil/favicon.svg + icon: + logo: material/camera-timer + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue grey + accent: teal + toggle: + icon: material/weather-night + name: Zu dunkler Darstellung wechseln + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: blue grey + accent: teal + toggle: + icon: material/weather-sunny + name: Zu heller Darstellung wechseln + features: + - navigation.instant + - navigation.tracking + - navigation.sections + - navigation.indexes + - navigation.top + - toc.follow + - search.highlight + - search.suggest + - content.code.copy + - content.tooltips + +plugins: + - search: + lang: de + separator: '[\s\-\.\_/]+' + +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - tables + - toc: + permalink: true + permalink_title: Verweis auf diesen Abschnitt + toc_depth: 3 + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + +extra_css: + - stil/handbuch.css + +extra: + generator: false + +nav: + - Start: index.md + - Schnellstart: schnellstart.md + - Installation: installation.md + + - Sichern: + - sichern/index.md + - Der Konfigurationseditor: sichern/editor.md + - Zeitpläne: sichern/zeitplan.md + - Welche Gäste?: sichern/auswahl.md + - Vorhaltezeit: sichern/vorhaltezeit.md + - Im Betrieb: sichern/betrieb.md + + - Daten zurückholen: + - holen/index.md + - Der Explorer: holen/explorer.md + - Die Web-Oberfläche: holen/web.md + + - Wiederherstellen: + - wiederherstellen/index.md + - Eine Maschine einrichten: wiederherstellen/einrichten.md + - Damit arbeiten: wiederherstellen/arbeiten.md + - Austauschlaufwerke: wiederherstellen/transfer.md + - Dongle und USB: wiederherstellen/dongle.md + - Mit Netzwerk: wiederherstellen/netzwerk.md + - Vom Snapshot lösen: wiederherstellen/loesen.md + - Verwerfen und aufräumen: wiederherstellen/verwerfen.md + + - Nachschlagen: + - nachschlagen/index.md + - Alle Befehle: nachschlagen/befehle.md + - Alle Einstellungen: nachschlagen/konfiguration.md + - Tastenkürzel: nachschlagen/tasten.md + - Speicherarten: nachschlagen/speicher.md + - Fehlersuche: nachschlagen/fehlersuche.md diff --git a/handbuch/werkstatt/LIESMICH.md b/handbuch/werkstatt/LIESMICH.md new file mode 100644 index 0000000..f405610 --- /dev/null +++ b/handbuch/werkstatt/LIESMICH.md @@ -0,0 +1,139 @@ +# Die Bildschirmfoto-Werkstatt + +Die Bilder im Handbuch sind keine abfotografierten Terminals, sondern werden +erzeugt — aus dem **echten** Programmcode, gegen einen erfundenen Proxmox-Host. + +```bash +./aufnehmen.sh # alle Bilder neu +./aufnehmen.sh explorer # nur die, deren Name "explorer" enthält +``` + +Ergebnis: `handbuch/docs/bilder/*.svg` + +Das hat drei Gründe: + +* **Sie stimmen.** Es läuft wirklich `pvesnap/recovery_ui.py`, nicht eine + Nachbildung. Ändert sich die Tastenleiste, ändert sich das Bild. +* **Sie sind reproduzierbar.** Derselbe Aufruf ergibt dasselbe Bild — gleiche + Daten, gleiche Uhrzeiten, gleiche Snapshot-Namen. +* **Kein echter Host wird angefasst.** Weder Daten noch Namen aus einer echten + Umgebung landen im Handbuch. + +--- + +## Wie es funktioniert + +``` +scenes.py Liste aller Aufnahmen: Name, Szene, Tastenfolge, Titel + └─ probe.py startet eine Szene in einem Pseudo-Terminal + └─ stage.sh hängt die Kulisse an die richtigen Pfade (Namensraum) + └─ run.py biegt die Pfade im Programmcode um und ruft die Oberfläche auf + └─ pvesnap/… der echte Programmcode + └─ shot.py baut den Bildschirm nach und schreibt ihn als SVG +``` + +### `demo.py` — die Kulisse + +Baut unter `werkstatt/demo/` ein erfundenes Rechenzentrum auf: zwei Nodes, zehn +Gäste, ein paar hundert Snapshots, zwei laufende Wiederherstellungen, drei +Austauschlaufwerke und einen Dateibaum für den Explorer. + +Ein **fester Zeitpunkt** (`NOW`) sorgt dafür, dass zwei Aufnahmen derselben +Liste gleich aussehen — sonst würde die Beschreibung im Handbuch nicht mehr +zum Bild passen. + +### `bin/pvesh`, `bin/perl`, `bin/rbd` — die Attrappen + +pvesnap spricht mit Proxmox über genau drei Programme. Alle drei liegen hier als +kleine Python-Skripte, die aus `demo/state.json` antworten. Sie stehen im `PATH` +vor den echten. + +Nichts davon verändert etwas — die schreibenden Aufrufe geben brav eine UPID +zurück und tun sonst nichts. + +### `run.py` — die Pfade umbiegen + +Setzt vor dem Start die Konstanten um, die sonst auf einen echten Host zeigen: + +```python +recovery.REGISTRY = demo/lib/recovery.json +transfer.INDEX = demo/lib/transfer/index.json +transfer.CONF_ROOT = demo/pve/nodes +``` + +Der Programmcode selbst wird nicht angefasst. Die Bilder zeigen also wirklich +das, was auf dem Host auch zu sehen ist. + +### `stage.sh` — die Pfade, die im Bild stehen + +Der Explorer zeigt Pfade an. Im Handbuch sollen das die Pfade eines echten +Hosts sein (`/run/pvesnap/mnt/…`, `/root`, `/etc/pvesnap.conf`) und nicht die +Arbeitsverzeichnisse dieser Werkstatt. + +Deshalb läuft jede Aufnahme in einem eigenen **Mount-Namensraum** +(`unshare -r -m`): Dort dürfen wir uns die Pfade hinbiegen, ohne am System +etwas zu verändern. `/etc` bekommt eine Overlay-Schicht, damit die +Konfiguration dort liegt, wo sie hingehört. + +### `shot.py` — der Bildschirmnachbau + +Startet das Programm in einem Pseudo-Terminal fester Größe (118 × 32), spielt +Tastendrücke ein, lässt [pyte](https://pypi.org/project/pyte/) den Bildschirm +nachbilden und schreibt das Ergebnis als SVG. + +Zwei Feinheiten, die Zeit gekostet haben: + +**`textLength` auf jedem Textstück.** Das SVG bettet keine Schrift ein — es +soll ja klein bleiben und offline funktionieren. Damit das Zeichenraster +trotzdem in jedem Browser sitzt, wird jede Zeichenkette fest vermessen. + +**`NCURSES_NO_UTF8_ACS=1`.** ncurses zeichnet Rahmen sonst über den +Ersatzzeichensatz (`ESC ( 0`). pyte ignoriert das im UTF-8-Betrieb, und aus den +Linien würden Buchstaben (`lqqqk` statt `┌───┐`). Mit der Variablen liefert +ncurses echte Unicode-Rahmenzeichen. + +--- + +## Eine Szene nachsehen + +Zum Ausprobieren, ohne ein Bild zu schreiben — gibt den Bildschirm als Text aus: + +```bash +../.venv/bin/python probe.py recovery +../.venv/bin/python probe.py recovery enter p +COLS=140 ROWS=40 ../.venv/bin/python probe.py explorer ab ab enter +``` + +Tastennamen: `ab auf links rechts enter tab esc space f2 f3 f5 f7 f9`, +alles andere wird als Zeichen geschickt. + +Verfügbare Szenen stehen in `run.py` unter `SZENEN`. + +--- + +## Eine Aufnahme hinzufügen + +In `scenes.py` eine Zeile ergänzen: + +```python +("recovery-neuerkram", "recovery", ["enter", "p"], + "root@pve1 — pvesnap-recovery"), +``` + +Fünftes Feld optional: Wartezeit in Sekunden, für Bildschirme, hinter denen +noch etwas arbeitet (sonst hält der Nachbau den letzten Stand fest, während im +Hintergrund noch ein Proxmox-Task läuft). + +Braucht es dafür eine neue Szene, kommt sie in `run.py` dazu. + +--- + +## Wenn etwas nicht klappt + +| Symptom | Ursache | +|---|---| +| Bild ist leer oder zeigt einen Traceback | `scenes.py` meldet das selbst — mit `probe.py` nachsehen | +| Rahmen bestehen aus `lqqqk` | `NCURSES_NO_UTF8_ACS=1` fehlt in `probe.umgebung()` | +| Pfade zeigen ins Scratchpad | `unshare -r` ging nicht; `aufnehmen.sh` prüft das vorher | +| Pfeiltasten bewegen nichts | Es muss `\x1bOB` sein, nicht `\x1b[B` — curses schaltet auf den Anwendungsmodus | +| Ein Bildschirm hängt bei „bitte warten" | Eine Antwort der Attrappe fehlt; mit `log_level=DEBUG` sieht man den Aufruf | diff --git a/handbuch/werkstatt/aufnehmen.sh b/handbuch/werkstatt/aufnehmen.sh new file mode 100755 index 0000000..6d3a4e4 --- /dev/null +++ b/handbuch/werkstatt/aufnehmen.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# --------------------------------------------------------------------- +# Alle Bildschirmfotos fuers Handbuch neu aufnehmen. +# +# ./aufnehmen.sh alle +# ./aufnehmen.sh explorer nur die, deren Name das enthaelt +# +# Ergebnis: handbuch/docs/bilder/*.svg +# Wie das funktioniert, steht in LIESMICH.md. +# --------------------------------------------------------------------- +set -euo pipefail + +HIER="$(cd "$(dirname "$0")" && pwd)" +HANDBUCH="$(dirname "${HIER}")" +VENV="${HANDBUCH}/.venv" + +info() { printf '\033[36m%s\033[0m\n' "$*"; } +rot() { printf '\033[31m%s\033[0m\n' "$*" >&2; } + +# unshare -r braucht Benutzer-Namensraeume; ohne sie stehen im Explorer die +# Arbeitspfade dieser Werkstatt statt der Pfade eines echten Hosts. +if ! unshare -r -m --propagation private true 2>/dev/null; then + rot "Benutzer-Namensraeume sind nicht verfuegbar (unshare -r schlaegt fehl)." + rot "Ohne sie stimmen die Pfade in den Explorer-Bildern nicht." + exit 1 +fi + +if [ ! -x "${VENV}/bin/python" ]; then + info "Richte Bau-Umgebung in ${VENV} ein ..." + python3 -m venv "${VENV}" + "${VENV}/bin/pip" install --quiet --upgrade pip +fi +if ! "${VENV}/bin/python" -c "import pyte" 2>/dev/null; then + info "Installiere pyte (Bildschirm-Nachbau) ..." + "${VENV}/bin/pip" install --quiet pyte +fi + +info "Baue die Demo-Kulisse ..." +"${VENV}/bin/python" "${HIER}/demo.py" >/dev/null + +info "Nehme auf ..." +"${VENV}/bin/python" "${HIER}/scenes.py" "$@" + +echo +info "Fertig: ${HANDBUCH}/docs/bilder/" diff --git a/handbuch/werkstatt/bin/perl b/handbuch/werkstatt/bin/perl new file mode 100755 index 0000000..8fa6a48 --- /dev/null +++ b/handbuch/werkstatt/bin/perl @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Attrappe der Storage-Schicht. + +pvesnap ruft `perl -e "" ` auf, um an PVE::Storage zu +kommen. Hier wird nur der Teil beantwortet, den die Bildschirmfotos brauchen - +Groessen und Pfade. Nichts wird angelegt oder geloescht. +""" + +import sys + +# Groessen der Datentraeger in der Kulisse, in Byte. +GROESSEN = { + "vm-100-disk-0": 60, "vm-101-disk-0": 400, "vm-102-disk-0": 2048, + "vm-105-disk-0": 250, "vm-130-disk-0": 80, "vm-120-disk-0": 120, + "subvol-110-disk-0": 16, "subvol-111-disk-0": 64, + "vm-9101-disk-0": 400, "vm-9102-disk-0": 250, +} +RAM = 32 * 1024 ** 3 # der gespeicherte Arbeitsspeicher von db01 + + +def main(argv): + # argv: -e + rest = argv[2:] if len(argv) > 2 and argv[0] == "-e" else argv + if not rest: + return 1 + op, args = rest[0], rest[1:] + + if op == "size": + volid = args[0] if args else "" + name = volid.split(":", 1)[-1] + if "-state-" in name: + print("RESULT %d raw" % RAM) + else: + gigabyte = GROESSEN.get(name, 32) + print("RESULT %d raw" % (gigabyte * 1024 ** 3)) + return 0 + + if op == "path": + volid = args[0] if args else "" + storage, _, name = volid.partition(":") + print("RESULT /dev/rbd/%s/%s" % (storage, name)) + return 0 + + if op in ("activate", "deactivate", "free"): + print("RESULT ok") + return 0 + + if op == "clone": + volid = args[0] if args else "" + storage = volid.split(":", 1)[0] + print("RESULT %s:vm-%s-disk-0" % (storage, args[1] if len(args) > 1 else "0")) + return 0 + + if op == "alloc": + print("RESULT %s:vm-%s-disk-9" % (args[0], args[1])) + return 0 + + print("unbekannte Operation: %s" % op, file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/handbuch/werkstatt/bin/pvesh b/handbuch/werkstatt/bin/pvesh new file mode 100755 index 0000000..b7be346 --- /dev/null +++ b/handbuch/werkstatt/bin/pvesh @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Attrappe von `pvesh` - beantwortet Abfragen aus demo/state.json. + +Nur so viel, wie die Oberflaechen fuer die Bildschirmfotos brauchen. Alles, +was etwas veraendern wuerde, gibt brav eine UPID zurueck und tut nichts. +""" + +import json +import os +import sys + +STATE = os.environ.get("PVESNAP_DEMO_STATE", "") + + +def load(): + with open(STATE, "r", encoding="utf-8") as handle: + return json.load(handle) + + +def main(argv): + if len(argv) < 2: + return 1 + verb, path = argv[0], argv[1] + args = {} + rest = argv[2:] + index = 0 + while index < len(rest): + if rest[index].startswith("--"): + key = rest[index][2:] + value = rest[index + 1] if index + 1 < len(rest) else "" + args[key] = value + index += 2 + else: + index += 1 + + data = load() + parts = [p for p in path.strip("/").split("/") if p] + + if verb == "get": + result = handle_get(parts, args, data) + if result is _MISS: + print("no such resource '%s'" % path, file=sys.stderr) + return 2 + if args.get("output-format") == "json": + print(json.dumps(result)) + else: + print(result) + return 0 + + # Alles Veraendernde: Proxmox liefert eine UPID, der Aufrufer wartet darauf. + node = parts[1] if len(parts) > 1 and parts[0] == "nodes" else "pve1" + if parts[-1] == "spiceproxy": + print(json.dumps({ + "type": "spice", "host": "pvespiceproxy:63f4a2b1:100:pve1::abc", + "proxy": "http://10.20.0.11", "tls-port": 61000, + "password": "aG9jaGdlaGVpbQ==", "ca": "-----BEGIN CERTIFICATE-----\\n" + "MIIFxTCCA62gAwIBAgIB...\\n-----END CERTIFICATE-----\\n", + "host-subject": "OU=PVE Cluster Node,O=Proxmox Virtual Environment," + "CN=pve1.hausnetz.lan", + "delete-this-file": 1, "secure-attention": "Ctrl+Alt+Ins", + "toggle-fullscreen": "Shift+F11", "release-cursor": "Ctrl+Alt+R", + "title": "VM 9102 - warenwirtschaft-w", + })) + return 0 + print("UPID:%s:00001A2B:0BC3D4E5:68970000:qmsnapshot:100:root@pam:" % node) + return 0 + + +_MISS = object() + + +def handle_get(parts, args, data): + if parts == ["cluster", "resources"]: + return data["resources"] + if len(parts) == 2 and parts[0] == "storage": + return {"ceph-vm": {"type": "rbd", "pool": "vmdaten", "shared": 1, + "content": "images", "storage": "ceph-vm", + "krbd": 0, "monhost": "10.20.0.11 10.20.0.12"}, + "ceph-ct": {"type": "rbd", "pool": "ctdaten", "shared": 1, + "content": "rootdir", "storage": "ceph-ct"}, + "local": {"type": "dir", "shared": 0, "storage": "local", + "content": "iso,vztmpl,backup"}, + }.get(parts[1], _MISS) + if parts == ["storage"]: + return [{"storage": "ceph-vm", "type": "rbd", "shared": 1}, + {"storage": "ceph-ct", "type": "rbd", "shared": 1}, + {"storage": "local", "type": "dir", "shared": 0}] + if parts == ["cluster", "nextid"]: + wanted = args.get("vmid") + if wanted: + taken = {str(r["vmid"]) for r in data["resources"]} + if str(wanted) in taken: + print("VM %s already exists" % wanted, file=sys.stderr) + sys.exit(2) + return int(wanted) + return data["nextid"] + + # Muss vor dem allgemeinen /nodes-Zweig stehen: sonst wird "tasks" fuer + # eine Gastart gehalten und /status liefert einen leeren Datensatz - die + # Warteschleife auf den Task laeuft dann bis zum Zeitlimit. + if len(parts) >= 3 and parts[0] == "nodes" and parts[2] == "tasks": + if parts[-1] == "log": + return [{"n": 1, "t": "TASK OK"}] + return {"status": "stopped", "exitstatus": "OK", "type": "qmconfig", + "upid": parts[3] if len(parts) > 3 else "", "node": parts[1]} + + if len(parts) >= 4 and parts[0] == "nodes": + node, kind, vmid = parts[1], parts[2], parts[3] + key = "%s/%s/%s" % (node, kind, vmid) + tail = parts[4:] + + if tail == ["snapshot"]: + entries = list(data["snapshots"].get(key, [])) + entries.append({"name": "current", "digest": "0" * 40, + "description": "You are here!", + "parent": entries[-1]["name"] if entries else ""}) + return entries + if tail == ["config"]: + return _conf(data["configs"].get(key, "")) + if len(tail) == 3 and tail[0] == "snapshot" and tail[2] == "config": + base = _conf(data["configs"].get(key, "")) + base["parent"] = tail[1] + base["snaptime"] = next( + (s["snaptime"] for s in data["snapshots"].get(key, []) + if s["name"] == tail[1]), 0) + # Ein Snapshot "mit RAM" traegt den Arbeitsspeicher als eigenes + # Volume; genau daran erkennt pvesnap, dass es warm gehen kann. + if tail[1].startswith("auto-stuendlich"): + base["vmstate"] = "ceph-vm:vm-%s-state-%s" % (vmid, tail[1]) + base["runningmachine"] = "pc-i440fx-9.0+pve0" + base["runningcpu"] = "x86-64-v2-AES,enforce" + return base + if tail == ["status", "current"]: + return data["status"].get(key, {}) + if tail and tail[0] == "status": + return data["status"].get(key, {}) + + return _MISS + + +def _conf(text): + result = {} + for line in text.splitlines(): + if ":" in line and not line.startswith("#"): + key, _, value = line.partition(":") + result[key.strip()] = value.strip() + for key in ("cores", "memory", "sockets", "numa", "agent"): + if key in result: + try: + result[key] = int(result[key]) + except ValueError: + pass + return result + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/handbuch/werkstatt/bin/rbd b/handbuch/werkstatt/bin/rbd new file mode 100755 index 0000000..f67187d --- /dev/null +++ b/handbuch/werkstatt/bin/rbd @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Attrappe von `rbd` - nur lesende Auskuenfte fuer die Bildschirmfotos. + +VM 9101 ist ein Linked Clone (haengt am Quell-Snapshot), VM 9102 ist bereits +geloest. Genau diese beiden Zustaende soll das Handbuch zeigen. +""" + +import sys + +VERBUNDEN = "vm-9101-disk-0" # haengt noch am Snapshot +QUELLE = "vmdaten/vm-101-disk-0@auto-stuendlich-20260809-100000" + + +# Optionen, hinter denen noch ein Wert steht - sonst haelt man "-m " +# fuer den Befehl. +MIT_WERT = {"-m", "--id", "--keyring", "-c", "--conf", "-p", "--pool", + "--namespace", "-n", "--name"} + + +def zerlege(argv): + """(Befehl, Abbild) aus der Kommandozeile.""" + rest = [] + index = 0 + while index < len(argv): + wort = argv[index] + if wort in MIT_WERT: + index += 2 + continue + if wort.startswith("-"): + index += 1 + continue + rest.append(wort) + index += 1 + return (rest[0] if rest else ""), (rest[1] if len(rest) > 1 else "") + + +def main(argv): + if not argv: + return 1 + befehl, ziel = zerlege(argv) + name = ziel.split("/")[-1].split("@")[0] + + if befehl == "info": + print("rbd image '%s':" % name) + print("\tsize 400 GiB in 102400 objects") + print("\torder 22 (4 MiB objects)") + print("\tsnapshot_count: 0") + print("\tid: 1f5c3d9a7b2e") + print("\tblock_name_prefix: rbd_data.1f5c3d9a7b2e") + print("\tformat: 2") + print("\tfeatures: layering, exclusive-lock") + print("\top_features:") + print("\tflags:") + if name == VERBUNDEN: + print("\tparent: %s" % QUELLE) + print("\toverlap: 400 GiB") + return 0 + + if befehl == "du": + print("NAME PROVISIONED USED") + print("%-19s 400 GiB 2.4 GiB" % name) + return 0 + + if befehl in ("flatten", "snap"): + return 0 + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/handbuch/werkstatt/demo.py b/handbuch/werkstatt/demo.py new file mode 100755 index 0000000..3d78627 --- /dev/null +++ b/handbuch/werkstatt/demo.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +"""Ein erfundenes Rechenzentrum fuer die Bildschirmfotos im Handbuch. + +Baut unter DEMO/ eine vollstaendige Kulisse auf: Antworten fuer ein +Attrappen-pvesh, Gast-Konfigurationen, die Merkliste der Wiederherstellungen, +die Transfer-Laufwerke und einen Dateibaum fuer den Explorer. + +Nichts davon fasst einen echten Proxmox-Host an. +""" + +from __future__ import annotations + +import json +import os +import shutil +from datetime import datetime, timedelta + +HERE = os.path.dirname(os.path.abspath(__file__)) +DEMO = os.path.join(HERE, "demo") + +# Ein fester Zeitpunkt - sonst sehen zwei Bildschirmfotos derselben Liste +# unterschiedlich aus, und im Handbuch passt die Beschreibung nicht mehr. +NOW = datetime(2026, 8, 9, 11, 42, 0) + +NODES = {"pve1": "10.20.0.11", "pve2": "10.20.0.12"} + +GUESTS = [ + # vmid, name, typ, node, zustand, tags, pool + (100, "web01", "qemu", "pve1", "running", "produktion;stuendlich", "Hausnetz"), + (101, "db01", "qemu", "pve1", "running", "produktion;datenbank;stuendlich", "Hausnetz"), + (102, "fileserver", "qemu", "pve2", "running", "produktion", "Hausnetz"), + (105, "warenwirtschaft", "qemu", "pve2", "running", "produktion;dongle", "Hausnetz"), + (110, "mailgw", "lxc", "pve1", "running", "produktion", "Hausnetz"), + (111, "gitlab", "lxc", "pve2", "running", "entwicklung", ""), + (120, "build-test", "qemu", "pve2", "stopped", "nosnap", ""), + (130, "dc01", "qemu", "pve1", "running", "produktion", "Hausnetz"), + (9101, "db01-live", "qemu", "pve1", "running", "pvesnap-recovery", ""), + (9102, "warenwirtschaft-w", "qemu", "pve2", "stopped", "pvesnap-recovery", ""), +] + +# Wie viele Snapshots welcher Gruppe je Gast - so, wie es nach ein paar Wochen +# Betrieb tatsaechlich aussieht. +PLAN = { + 100: [("stuendlich", 24, 3600), ("taeglich", 8, 86400)], + 101: [("stuendlich", 24, 3600), ("taeglich", 14, 86400), ("monatlich", 3, 2592000)], + 102: [("taeglich", 9, 86400)], + 105: [("taeglich", 12, 86400), ("monatlich", 2, 2592000)], + 110: [("taeglich", 7, 86400)], + 111: [("taeglich", 4, 86400)], + 130: [("taeglich", 11, 86400)], +} + +# Von Hand angelegte Snapshots - die pvesnap niemals anfasst. +HANDMADE = { + 101: [("vor-update-14.2", "Vor dem Update auf PostgreSQL 14.2", 6 * 86400)], + 105: [("golden", "Frisch eingerichtet, Lizenz aktiviert", 40 * 86400)], +} + +DESCRIPTION = ("pvesnap | Gruppe: %s | erstellt: %s | Vorhaltezeit: %s | max: %d") + +KEEP = {"stuendlich": ("2 Tage", 24), "taeglich": ("21 Tage", 14), + "monatlich": ("400 Tage", 6)} + +QEMU_CONF = """\ +agent: 1 +boot: order=scsi0 +cores: %(cores)d +cpu: x86-64-v2-AES +memory: %(memory)d +meta: creation-qemu=9.0.2 +name: %(name)s +net0: virtio=%(mac)s,bridge=vmbr0,firewall=1 +numa: 0 +ostype: %(ostype)s +scsi0: ceph-vm:vm-%(vmid)d-disk-0,discard=on,iothread=1,size=%(size)dG +scsihw: virtio-scsi-single +smbios1: uuid=%(uuid)s +sockets: 1 +vmgenid: %(genid)s +""" + +LXC_CONF = """\ +arch: amd64 +cores: 2 +features: nesting=1 +hostname: %(name)s +memory: 2048 +net0: name=eth0,bridge=vmbr0,firewall=1,hwaddr=%(mac)s,ip=dhcp,type=veth +ostype: debian +rootfs: ceph-ct:subvol-%(vmid)d-disk-0,size=%(size)dG +swap: 512 +unprivileged: 1 +""" + +HARDWARE = { + 100: dict(cores=4, memory=8192, size=60, ostype="l26"), + 101: dict(cores=8, memory=32768, size=400, ostype="l26"), + 102: dict(cores=4, memory=8192, size=2048, ostype="l26"), + 105: dict(cores=6, memory=16384, size=250, ostype="win11"), + 120: dict(cores=8, memory=16384, size=120, ostype="l26"), + 130: dict(cores=4, memory=8192, size=80, ostype="win11"), + 110: dict(size=16), + 111: dict(size=64), + 9101: dict(cores=8, memory=32768, size=400, ostype="l26"), + 9102: dict(cores=6, memory=16384, size=250, ostype="win11"), +} + + +def _mac(vmid, index=0): + return "BC:24:11:%02X:%02X:%02X" % (vmid // 256, vmid % 256, 0x40 + index) + + +def _uuid(vmid): + return "8f3c%04d-11ee-4a7b-9c2d-%012d" % (vmid, vmid * 7919) + + +def _genid(vmid): + return "b2e1%04d-7f45-4c18-a3d9-%012d" % (vmid, vmid * 104729) + + +# --------------------------------------------------------------------------- +# Snapshots +# --------------------------------------------------------------------------- + +def _snapshots_for(vmid): + """Alle Snapshots eines Gastes, aeltester zuerst, mit Elternkette.""" + entries = [] + for slug, count, step in PLAN.get(vmid, []): + keep_time, keep_count = KEEP[slug] + for index in range(count): + moment = NOW - timedelta(seconds=step * (index + 1)) + if slug == "taeglich": + moment = moment.replace(hour=2, minute=30, second=0) + elif slug == "monatlich": + moment = moment.replace(day=1, hour=4, minute=0, second=0) + else: + moment = moment.replace(minute=0, second=0) + entries.append({ + "name": "auto-%s-%s" % (slug, moment.strftime("%Y%m%d-%H%M%S")), + "snaptime": int(moment.timestamp()), + "description": DESCRIPTION % (slug, moment.strftime("%Y-%m-%d %H:%M:%S"), + keep_time, keep_count), + }) + for name, note, age in HANDMADE.get(vmid, []): + moment = NOW - timedelta(seconds=age) + entries.append({"name": name, "snaptime": int(moment.timestamp()), + "description": note}) + + entries.sort(key=lambda e: e["snaptime"]) + parent = "" + for entry in entries: + entry["parent"] = parent + parent = entry["name"] + return entries + + +# --------------------------------------------------------------------------- +# Kulisse aufbauen +# --------------------------------------------------------------------------- + +def _write(path, text): + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", encoding="utf-8") as handle: + handle.write(text) + + +def _guest_config(vmid, name, kind): + values = dict(HARDWARE.get(vmid, {})) + values.update(vmid=vmid, name=name, mac=_mac(vmid), + uuid=_uuid(vmid), genid=_genid(vmid)) + values.setdefault("cores", 2) + values.setdefault("memory", 2048) + values.setdefault("size", 32) + values.setdefault("ostype", "l26") + return (LXC_CONF if kind == "lxc" else QEMU_CONF) % values + + +def build(): + if os.path.isdir(DEMO): + shutil.rmtree(DEMO) + + resources, snapshots, configs, status = [], {}, {}, {} + + for vmid, name, kind, node, state, tags, pool in GUESTS: + resources.append({ + "vmid": vmid, "name": name, "type": kind, "node": node, + "status": state, "tags": tags, "pool": pool, + "maxmem": HARDWARE.get(vmid, {}).get("memory", 2048) * 1024 * 1024, + "maxdisk": HARDWARE.get(vmid, {}).get("size", 32) * 1024 ** 3, + "uptime": 486231 if state == "running" else 0, + }) + key = "%s/%s/%d" % (node, kind, vmid) + snapshots[key] = _snapshots_for(vmid) + configs[key] = _guest_config(vmid, name, kind) + status[key] = { + "status": state, "vmid": vmid, "name": name, + "qmpstatus": "running" if state == "running" else "stopped", + "uptime": 486231 if state == "running" else 0, + "maxmem": HARDWARE.get(vmid, {}).get("memory", 2048) * 1024 * 1024, + "cpus": HARDWARE.get(vmid, {}).get("cores", 2), + "spice": 1 if vmid == 9102 else 0, + } + + # Die beiden Wiederherstellungen haben eine eigene Konfiguration: kein Netz + # bzw. Netz mit den MAC-Adressen des Originals, dazu das Transfer-Laufwerk. + live = _guest_config(9101, "db01-live", "qemu") + live = live.replace("net0: virtio=%s,bridge=vmbr0,firewall=1\n" % _mac(9101), "") + live = live.replace("scsi0: ceph-vm:vm-9101-disk-0", + "scsi0: ceph-vm:vm-9101-disk-0") + live += "scsi1: /dev/loop3,backup=0,replicate=0\n" + live += "tags: pvesnap-recovery\n" + configs["pve1/qemu/9101"] = live + + warm = _guest_config(9102, "warenwirtschaft-w", "qemu") + warm = warm.replace("net0: virtio=%s" % _mac(9102), "net0: virtio=%s" % _mac(105)) + warm = warm.replace("smbios1: uuid=%s" % _uuid(9102), + "smbios1: uuid=%s" % _uuid(105)) + warm += "tags: pvesnap-recovery\nusb0: spice\nusb1: spice\nvga: qxl\n" + configs["pve2/qemu/9102"] = warm + + _write(os.path.join(DEMO, "state.json"), json.dumps({ + "resources": resources, "snapshots": snapshots, + "configs": configs, "status": status, "nextid": 9103, + "nodes": NODES, + }, indent=1)) + + # /etc/pve nachbilden - daraus liest pvesnap die Belegung der Laufwerke. + for vmid, name, kind, node, _s, _t, _p in GUESTS: + sub = "lxc" if kind == "lxc" else "qemu-server" + _write(os.path.join(DEMO, "pve", "nodes", node, sub, "%d.conf" % vmid), + configs["%s/%s/%d" % (node, kind, vmid)]) + _write(os.path.join(DEMO, "pve", ".members"), + json.dumps({"nodename": "pve1", "version": 8, + "nodelist": {n: {"id": i + 1, "online": 1, "ip": ip} + for i, (n, ip) in enumerate(NODES.items())}}, + indent=1)) + + _build_registry() + _build_transfers() + _build_config() + _build_tree() + return DEMO + + +def _build_registry(): + created_live = int((NOW - timedelta(minutes=38)).timestamp()) + created_warm = int((NOW - timedelta(hours=5, minutes=12)).timestamp()) + entries = [ + {"vmid": 9101, "type": "qemu", "node": "pve1", "name": "db01-live", + "source": 101, "source_node": "pve1", + "snapshot": "auto-stuendlich-20260809-100000", "mode": "live", + "created": created_live, + "volumes": ["ceph-vm:vm-9101-disk-0"], + "protected": [["ceph-vm:vm-101-disk-0", "auto-stuendlich-20260809-100000"]], + "transfers": [{"name": "dumps", "key": "scsi1", + "drive": "/dev/loop3,backup=0,replicate=0", + "kind": "disk", "pending": False}], + "resumed": True}, + {"vmid": 9102, "type": "qemu", "node": "pve2", "name": "warenwirtschaft-w", + "source": 105, "source_node": "pve2", + "snapshot": "auto-taeglich-20260809-023000", "mode": "recover", + "created": created_warm, + "volumes": ["ceph-vm:vm-9102-disk-0"], + "protected": [], + "transfers": [], "resumed": False}, + ] + _write(os.path.join(DEMO, "lib", "recovery.json"), json.dumps(entries, indent=1)) + + +def _build_transfers(): + volumes = [ + {"name": "dumps", "image": os.path.join(DEMO, "lib", "transfer", "dumps.img"), + "size": 20 * 1024 ** 3, "fs": "exfat", "label": "DUMPS", + "note": "Datenbank-Dumps und Exporte", + "created": int((NOW - timedelta(days=41)).timestamp()), "partitioned": True}, + {"name": "werkzeuge", + "image": os.path.join(DEMO, "lib", "transfer", "werkzeuge.img"), + "size": 4 * 1024 ** 3, "fs": "exfat", "label": "WERKZEUGE", + "note": "Skripte, Treiber, Installer", + "created": int((NOW - timedelta(days=41)).timestamp()), "partitioned": True}, + {"name": "austausch", + "image": os.path.join(DEMO, "lib", "transfer", "austausch.img"), + "size": 8 * 1024 ** 3, "fs": "exfat", "label": "AUSTAUSCH", + "note": "Alles andere", + "created": int((NOW - timedelta(days=9)).timestamp()), "partitioned": True}, + ] + _write(os.path.join(DEMO, "lib", "transfer", "index.json"), + json.dumps(volumes, indent=1)) + # Duenn besetzte Abbilddateien: sie kosten keinen Plattenplatz, aber + # Volume.used_bytes findet echte Werte vor. + for volume, used in zip(volumes, (3_400_000_000, 780_000_000, 0)): + with open(volume["image"], "wb") as handle: + handle.truncate(volume["size"]) + if used: + handle.seek(0) + handle.write(b"\0" * min(used, 4 * 1024 ** 2)) + os.truncate(volume["image"], volume["size"]) + + +def _build_config(): + _write(os.path.join(DEMO, "pvesnap.conf"), """\ +[global] +prefix = auto +check_interval = 60s +state_file = /var/lib/pvesnap/state.json +log_level = INFO +task_timeout = 15m +retries = 2 +retry_delay = 60s +pause_between = 0s +run_on_start = no +dry_run = no +description = pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time} | max: {keep_count} + +[defaults] +enabled = yes +skip_stopped = no +vmstate = no + +[group:stuendlich] +interval = 1h +align = yes +keep_count = 24 +keep_time = 2d +tags = stuendlich +skip_stopped = yes + +[group:taeglich] +schedule = daily +at = 02:30 +keep_count = 14 +keep_time = 21d +tags = produktion +exclude_tags = nosnap + +[group:monatlich] +schedule = monthly +day_of_month = 1 +at = 04:00 +keep_count = 6 +keep_time = 400d +all = yes +exclude_tags = nosnap, pvesnap-recovery +description = Monatssicherung {name} ({vmid}) vom {date} +""") + + +FSTAB = """\ +# /etc/fstab: static file system information. +# +# Use 'blkid' to print the universally unique identifier for a device; this may +# be used with UUID= as a more robust way to name devices that works even if +# disks are added and removed. See fstab(5). +# +# +UUID=4c1a9f3e-2b77-4d18-9a5c-7e3f0d1b8a26 / ext4 errors=remount-ro 0 1 +UUID=9f2c-31AD /boot/efi vfat umask=0077 0 1 +/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1 /srv/dumps xfs defaults 0 2 +/swap.img none swap sw 0 0 +""" + +SICHERUNG = """\ +#!/bin/bash +# Naechtlicher Dump - laeuft aus der Crontab um 01:50, also vor dem Snapshot. +set -euo pipefail + +ZIEL=/srv/dumps +STAMPE=$(date +%Y%m%d-%H%M) + +for DB in kunden auftraege artikel; do + pg_dump -Fc "$DB" | gzip -1 > "$ZIEL/${DB}-${STAMPE}.sql.gz" +done + +find "$ZIEL" -name '*.sql.gz' -mtime +14 -delete +""" + +LIESMICH = """\ +Transfer-Laufwerk dumps +======================= + +Angelegt mit pvesnap-recovery (Taste v in der Uebersicht). + +Dieses Laufwerk gehoert zu keiner Maschine. Es wird beim Start einer +Wiederherstellung angehaengt und danach wieder freigegeben - der Inhalt +bleibt erhalten. + +Immer nur an einer Stelle benutzen: entweder am Host eingehaengt oder in +einem Gast. Beides gleichzeitig zerlegt das Dateisystem. +""" + +TREE = { + "etc": { + "postgresql": {"14": {"main": { + "postgresql.conf": 28_412, "pg_hba.conf": 5_137, "pg_ident.conf": 1_636}}}, + "nginx": {"nginx.conf": 1_482, "sites-enabled": {"default": 2_416}}, + "fstab": FSTAB, "hostname": "db01\n", "hosts": 274, "passwd": 2_143, + "shadow": 1_309, "ssh": {"sshd_config": 3_290}, + }, + "home": {"stefan": {"notizen.txt": 1_204, ".bashrc": 3_771}}, + "root": {".bash_history": 8_214, "sicherung.sh": SICHERUNG}, + "srv": {"dumps": { + "db01-20260809-0200.sql.gz": 4_183_244_800, + "db01-20260808-0200.sql.gz": 4_106_112_512, + "kunden-export.csv": 88_412_160}}, + "var": {"log": {"syslog": 12_884_901, "auth.log": 940_233, + "postgresql": {"postgresql-14-main.log": 3_402_118}}, + "lib": {"postgresql": {"14": {"main": {"PG_VERSION": 3, + "postgresql.auto.conf": 88}}}}}, +} + + +# Alles, was frisch angelegt wird, traegt sonst das Datum von heute - im +# Bildschirmfoto sieht ein Serverdateisystem dann aus wie eben ausgepackt. +MTIMES = { + "srv/dumps/db01-20260809-0200.sql.gz": NOW - timedelta(hours=9, minutes=12), + "srv/dumps/db01-20260808-0200.sql.gz": NOW - timedelta(days=1, hours=9), + "srv/dumps/kunden-export.csv": NOW - timedelta(days=2, hours=4), + "srv/dumps": NOW - timedelta(hours=9), + "var/log/syslog": NOW - timedelta(minutes=3), + "var/log/auth.log": NOW - timedelta(minutes=41), + "var/log": NOW - timedelta(minutes=3), + "root/.bash_history": NOW - timedelta(days=1, hours=2), + "root/sicherung.sh": NOW - timedelta(days=96), + "home/stefan/notizen.txt": NOW - timedelta(days=12), + "etc/postgresql/14/main/postgresql.conf": NOW - timedelta(days=214), + "etc/nginx/nginx.conf": NOW - timedelta(days=402), + "etc/fstab": NOW - timedelta(days=611), + "etc/hostname": NOW - timedelta(days=611), +} +GRUNDALTER = timedelta(days=611) # der Tag, an dem der Server aufgesetzt wurde + + +def _build_tree(): + """Ein glaubhaftes Linux-Wurzelverzeichnis fuer den Explorer.""" + def make(base, spec, prefix=""): + for name, value in spec.items(): + path = os.path.join(base, name) + relativ = "%s/%s" % (prefix, name) if prefix else name + if isinstance(value, dict): + os.makedirs(path, exist_ok=True) + make(path, value, relativ) + elif isinstance(value, str): + # Echter Inhalt - damit die Dateivorschau (F3) etwas zu zeigen hat. + with open(path, "w", encoding="utf-8") as handle: + handle.write(value) + else: + with open(path, "wb") as handle: + handle.truncate(value) + moment = MTIMES.get(relativ, NOW - GRUNDALTER) + stamp = moment.timestamp() + os.utime(path, (stamp, stamp)) + + root = os.path.join(DEMO, "snapshot") + os.makedirs(root, exist_ok=True) + make(root, TREE) + + lokal = os.path.join(DEMO, "lokal") + os.makedirs(lokal, exist_ok=True) + make(lokal, {"werkzeuge": {"pg_repack.deb": 412_160, "pruefen.sh": 2_048}, + "holen": {}, "notizen.md": 3_190}) + + # Was auf einem Transfer-Laufwerk liegt, wenn man es vorbereitet hat. + transfer = os.path.join(DEMO, "transfer-dumps") + os.makedirs(transfer, exist_ok=True) + make(transfer, {"LIESMICH.txt": LIESMICH, + "werkzeuge": {"pg_dump-14": 1_284_096, "7z.exe": 1_140_224}, + "eingang": {}, "ausgang": {}}) + + for basis, alter in ((lokal, timedelta(days=3)), (transfer, timedelta(days=41))): + stamp = (NOW - alter).timestamp() + for wurzel, ordner, dateien in os.walk(basis): + for name in list(ordner) + list(dateien): + os.utime(os.path.join(wurzel, name), (stamp, stamp)) + os.utime(wurzel, (stamp, stamp)) + + +if __name__ == "__main__": + print(build()) diff --git a/handbuch/werkstatt/probe.py b/handbuch/werkstatt/probe.py new file mode 100755 index 0000000..618cba4 --- /dev/null +++ b/handbuch/werkstatt/probe.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Eine Szene starten und den Bildschirm als Text zeigen - zum Nachsehen.""" +import os +import sys +import time + +HERE = os.path.dirname(os.path.abspath(__file__)) # handbuch/werkstatt +HANDBUCH = os.path.dirname(HERE) # handbuch +REPO = os.path.dirname(HANDBUCH) # Projektwurzel +sys.path.insert(0, HERE) +from shot import Terminal # noqa: E402 + +TASTEN = {"ab": "\x1bOB", "auf": "\x1bOA", "rechts": "\x1bOC", "links": "\x1bOD", + "enter": "\r", "tab": "\t", "esc": "\x1b", "f2": "\x1bOQ", "f3": "\x1bOR", + "f5": "\x1b[15~", "f7": "\x1b[18~", "f9": "\x1b[20~", "space": " "} + + +def umgebung(): + env = dict(os.environ) + env["PATH"] = os.path.join(HERE, "bin") + ":" + env["PATH"] + env["PVESNAP_DEMO_STATE"] = os.path.join(HERE, "demo", "state.json") + # ncurses zeichnet Rahmen sonst ueber den Ersatzzeichensatz (ESC ( 0). Der + # Bildschirmnachbau ignoriert das im UTF-8-Betrieb, und aus den Linien + # wuerden Buchstaben. So kommen echte Unicode-Rahmenzeichen heraus. + env["NCURSES_NO_UTF8_ACS"] = "1" + env["PVESNAP_SRC"] = os.environ.get("PVESNAP_SRC", REPO) + env["PYTHONPATH"] = env["PVESNAP_SRC"] + return env + + +# Dieselbe Umgebung, die auch bauen.sh benutzt - dort wird pyte nachinstalliert. +VENV = os.path.join(HANDBUCH, ".venv", "bin", "python") + + +def befehl(szene): + """Die Aufnahme laeuft in einem eigenen Mount-Namensraum - siehe stage.sh.""" + return ["unshare", "-r", "-m", "--propagation", "private", + os.path.join(HERE, "stage.sh"), szene] + + +def starte(szene, cols=100, rows=30): + env = umgebung() + env["DOKU"] = HERE + env["PY"] = VENV + return Terminal(befehl(szene), cols=cols, rows=rows, env=env, cwd=HERE) + + +if __name__ == "__main__": + szene = sys.argv[1] + tasten = [TASTEN.get(t, t) for t in sys.argv[2:]] + term = starte(szene, cols=int(os.environ.get("COLS", 100)), + rows=int(os.environ.get("ROWS", 30))) + try: + time.sleep(1.2) + term.settle() + term.send(tasten, pause=0.3) + time.sleep(0.3) + term.settle() + for line in term.screen.display: + print("|" + line.rstrip() + "|") + finally: + term.close() diff --git a/handbuch/werkstatt/run.py b/handbuch/werkstatt/run.py new file mode 100755 index 0000000..84c5feb --- /dev/null +++ b/handbuch/werkstatt/run.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""Startet eine Oberflaeche von pvesnap gegen die Demo-Kulisse. + +Aufruf: run.py [argumente] + +Der eigentliche Trick steckt in _patch(): alle Pfade, die sonst auf einen +echten Proxmox-Host zeigen, werden auf demo/ umgebogen. Der Programmcode +selbst wird nicht angefasst - die Bildschirmfotos zeigen also wirklich das, +was auf dem Host auch zu sehen ist. +""" + +from __future__ import annotations + +import curses +import json +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +DEMO = os.path.join(HERE, "demo") +sys.path.insert(0, os.environ.get("PVESNAP_SRC", "")) + +# Wo die Kulisse haengt, wenn stage.sh sie eingehaengt hat. Ohne Namensraum +# (beim schnellen Nachsehen) wird auf die Verzeichnisse selbst zurueckgefallen. +SNAP_MNT = "/run/pvesnap/mnt/101-auto-taeglich-20260809-023000" +TRANSFER_MNT = "/run/pvesnap/transfer/dumps" +LOKAL = "/root" + + +def _oder(pfad, ersatz): + return pfad if os.path.isdir(pfad) and os.listdir(pfad) else ersatz + + +def _patch(): + from pvesnap import preflight, recovery, transfer, tui + + recovery.STATE_DIR = os.path.join(DEMO, "lib") + recovery.REGISTRY = os.path.join(DEMO, "lib", "recovery.json") + recovery.CONF_ROOT = os.path.join(DEMO, "pve", "nodes") + + transfer.TRANSFER_DIR = os.path.join(DEMO, "lib", "transfer") + transfer.INDEX = os.path.join(DEMO, "lib", "transfer", "index.json") + transfer.MOUNT_ROOT = "/run/pvesnap/transfer" + transfer.CONF_ROOT = recovery.CONF_ROOT + + # Loop-Geraete und Einhaengepunkte gibt es hier nicht - also erzaehlen wir + # sie. "dumps" haengt in der Maschine, "austausch" gerade am Host. + abbild = lambda name: os.path.join(transfer.TRANSFER_DIR, name + ".img") + transfer._loop_devices = lambda: {abbild("dumps"): "/dev/loop3", + abbild("austausch"): "/dev/loop5"} + transfer._mounts = lambda: {"/dev/loop5p1": os.path.join(transfer.MOUNT_ROOT, + "austausch")} + + with open(os.path.join(DEMO, "pve", ".members"), encoding="utf-8") as handle: + mitglieder = json.load(handle) + recovery._ADDRESSES.update({n: e["ip"] for n, e + in mitglieder["nodelist"].items()}) + + preflight.check_environment = lambda require_pve=True: [] + tui.service_state = lambda: (True, "active") + tui.service_text = lambda: "Dienst: laeuft" + + +def _explorer_panes(links_pfad, links_titel, untertitel, links_schreibbar=False): + from pvesnap.explorer import Explorer, Pane + links = Pane(path=links_pfad, root=links_pfad, + readonly=not links_schreibbar, title=links_titel) + rechts = Pane(path=_oder(LOKAL, os.path.join(DEMO, "lokal")), root="/", + readonly=False, title="Lokaler Rechner") + return Explorer(links, rechts, subtitle=untertitel) + + +# --------------------------------------------------------------------------- +# Szenen +# --------------------------------------------------------------------------- + +def szene_config(stdscr): + from pvesnap.tui import Editor + pfad = "/etc/pvesnap.conf" + editor = Editor(pfad if os.path.exists(pfad) + else os.path.join(DEMO, "pvesnap.conf")) + editor.load() + editor.run(stdscr) + + +def szene_recovery(stdscr): + from pvesnap.proxmox import Proxmox + from pvesnap.recovery_ui import _overview + _overview(stdscr, Proxmox()) + + +def szene_transfer(stdscr): + from pvesnap import transfer_ui + from pvesnap.recovery_ui import _browse_transfer + transfer_ui.screen(stdscr, browse=_browse_transfer) + + +def szene_explorer(stdscr): + _explorer_panes(_oder(SNAP_MNT, os.path.join(DEMO, "snapshot")), + "VM 101 (db01) @ auto-taeglich-20260809-023000", + "VM 101 (db01) - Snapshot auto-taeglich-20260809-023000" + ).run(stdscr) + + +def szene_explorer_transfer(stdscr): + _explorer_panes(_oder(TRANSFER_MNT, os.path.join(DEMO, "transfer-dumps")), + "Transfer: dumps (DUMPS)", + "Transfer-Laufwerk dumps - beide Seiten beschreibbar", + links_schreibbar=True).run(stdscr) + + +def szene_gastauswahl(stdscr): + from pvesnap.explorer import _pick_guest + from pvesnap.proxmox import Proxmox + _pick_guest(stdscr, Proxmox().inventory()) + + +def szene_snapshotauswahl(stdscr): + from pvesnap.explorer import _pick_snapshot + from pvesnap.proxmox import Proxmox + from pvesnap.snapfs import list_snapshots + proxmox = Proxmox() + gast = next(g for g in proxmox.inventory() if g.vmid == 101) + _pick_snapshot(stdscr, [s for s in list_snapshots(proxmox, gast) if s.complete]) + + +def szene_neu(stdscr): + """Die Optionsmaske des Assistenten - ohne etwas anzulegen.""" + from pvesnap.proxmox import Proxmox + from pvesnap.recovery_ui import _options + from pvesnap.recovery import Spec + proxmox = Proxmox() + gast = next(g for g in proxmox.inventory() if g.vmid == 101) + spec = Spec(mode="live") + spec.snapshot = "auto-stuendlich-20260809-100000" + spec.newid = 9103 + spec.node = "pve1" + spec.transfers = ["werkzeuge"] + _options(stdscr, gast, spec) + + +def _vorhaben(stdscr, vmid, snapshot, **werte): + from pvesnap.curses_util import init_colors + from pvesnap.proxmox import Proxmox + from pvesnap.recovery import Spec, plan + from pvesnap.recovery_ui import _pages, summary_lines + init_colors() + proxmox = Proxmox() + gast = next(g for g in proxmox.inventory() if g.vmid == vmid) + spec = Spec(mode=werte.pop("mode", "live")) + for schluessel, wert in werte.items(): + setattr(spec, schluessel, wert) + _pages(stdscr, summary_lines(plan(proxmox, gast, snapshot, spec)), + "Vorhaben pruefen", "j = anlegen | n = abbrechen") + + +def szene_vorhaben(stdscr): + """Der Normalfall: abgeschottet hineinschauen, mit Arbeitsspeicher.""" + _vorhaben(stdscr, 101, "auto-stuendlich-20260809-100000", + newid=9103, transfers=["werkzeuge"]) + + +def szene_vorhaben_dongle(stdscr): + """Die Windows-Maschine mit SPICE und USB - fuer das Schutzmodul.""" + _vorhaben(stdscr, 105, "auto-taeglich-20260809-023000", + mode="live", newid=9104, transfers=["werkzeuge"], spice=True, usb=2) + + +def szene_laufwerkswahl(stdscr): + from pvesnap import transfer_ui + transfer_ui.choose(stdscr, preselected=["werkzeuge"]) + + +SZENEN = { + "config": szene_config, + "recovery": szene_recovery, + "transfer": szene_transfer, + "explorer": szene_explorer, + "explorer-transfer": szene_explorer_transfer, + "gastauswahl": szene_gastauswahl, + "snapshotauswahl": szene_snapshotauswahl, + "neu": szene_neu, + "vorhaben": szene_vorhaben, + "vorhaben-dongle": szene_vorhaben_dongle, + "laufwerkswahl": szene_laufwerkswahl, +} + + +def main(): + if len(sys.argv) < 2 or sys.argv[1] not in SZENEN: + print("Szenen: %s" % ", ".join(sorted(SZENEN)), file=sys.stderr) + return 1 + _patch() + from pvesnap.curses_util import init_colors + + szene = SZENEN[sys.argv[1]] + + def start(stdscr): + curses.curs_set(0) + if hasattr(curses, "set_escdelay"): + curses.set_escdelay(25) + init_colors() + stdscr.keypad(True) + return szene(stdscr) + + try: + curses.wrapper(start) + except Exception: # beim Bauen will man den Grund sehen + import traceback + traceback.print_exc() + return 2 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/handbuch/werkstatt/scenes.py b/handbuch/werkstatt/scenes.py new file mode 100755 index 0000000..0d23007 --- /dev/null +++ b/handbuch/werkstatt/scenes.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Alle Bildschirmfotos fuers Handbuch aufnehmen. + + ./scenes.py alle + ./scenes.py explorer nur die, deren Name das enthaelt +""" + +from __future__ import annotations + +import os +import sys +import time + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) + +from probe import TASTEN, VENV, befehl, umgebung # noqa: E402 +from shot import Terminal # noqa: E402 + +ZIEL = os.path.join(os.path.dirname(HERE), "docs", "bilder") + +BREIT, HOCH = 118, 32 + +# (Dateiname, Szene, Tasten, Fenstertitel) +AUFNAHMEN = [ + # -- Sicherung einrichten: der Konfigurationseditor -------------------- + ("config-gruppen", "config", [], + "root@pve1 — pvesnap config"), + ("config-gruppe", "config", ["ab", "enter"], + "root@pve1 — pvesnap config → Gruppe taeglich"), + ("config-vms", "config", ["ab", "enter", "v"], + "root@pve1 — pvesnap config → VMs waehlen"), + ("config-global", "config", ["g"], + "root@pve1 — pvesnap config → Globales"), + ("config-uebersicht", "config", ["v"], + "root@pve1 — pvesnap config → Uebersicht"), + + # -- Dateien holen: der Explorer --------------------------------------- + ("explorer-gastauswahl", "gastauswahl", [], + "root@pve1 — pvesnap-explorer"), + ("explorer-snapshotauswahl", "snapshotauswahl", [], + "root@pve1 — pvesnap-explorer"), + ("explorer-fenster", "explorer", ["ab", "ab", "ab", "enter", "ab", "enter"], + "root@pve1 — pvesnap-explorer 101"), + ("explorer-markiert", "explorer", + ["ab", "ab", "ab", "enter", "ab", "enter", "ab", "space", "space"], + "root@pve1 — pvesnap-explorer 101"), + ("explorer-kopieren", "explorer", + ["ab", "ab", "ab", "enter", "ab", "enter", "ab", "space", "f5"], + "root@pve1 — pvesnap-explorer 101"), + ("explorer-ansehen", "explorer", + ["enter", "ab", "ab", "ab", "ab", "f3"], + "root@pve1 — pvesnap-explorer 101"), + ("explorer-hilfe", "explorer", ["?"], + "root@pve1 — pvesnap-explorer 101"), + + # -- Wiederherstellen --------------------------------------------------- + ("recovery-uebersicht", "recovery", [], + "root@pve1 — pvesnap-recovery"), + ("recovery-optionen", "neu", [], + "root@pve1 — pvesnap-recovery → einrichten"), + ("recovery-betriebsart", "neu", ["enter"], + "root@pve1 — pvesnap-recovery → einrichten"), + ("recovery-vorhaben", "vorhaben", [], + "root@pve1 — pvesnap-recovery → einrichten"), + ("recovery-detail", "recovery", ["enter"], + "root@pve1 — pvesnap-recovery"), + ("recovery-loesen", "recovery", ["enter", "f"], + "root@pve1 — pvesnap-recovery"), + ("recovery-spice", "recovery", ["enter", "p"], + "root@pve1 — pvesnap-recovery"), + ("recovery-usb", "recovery", ["enter", "p", "enter"], + "root@pve1 — pvesnap-recovery"), + ("recovery-neustart", "recovery", + ["enter", "p", "enter", "ab", "ab", "enter"], + "root@pve1 — pvesnap-recovery"), + ("recovery-neustartwahl", "recovery", + ["enter", "p", "enter", "ab", "ab", "enter", "j"], + "root@pve1 — pvesnap-recovery", 4.0), + ("recovery-verwerfen", "recovery", ["x"], + "root@pve1 — pvesnap-recovery"), + + # -- Austauschlaufwerke ------------------------------------------------- + ("transfer-uebersicht", "transfer", [], + "root@pve1 — pvesnap-recovery → Laufwerke"), + ("transfer-neu", "transfer", ["n"], + "root@pve1 — pvesnap-recovery → Laufwerke"), + ("transfer-commander", "explorer-transfer", [], + "root@pve1 — pvesnap-recovery → dumps"), + ("transfer-auswahl", "laufwerkswahl", [], + "root@pve1 — pvesnap-recovery → einrichten"), + + # -- Schutzmodul am SPICE-Anschluss ------------------------------------- + ("dongle-vorhaben", "vorhaben-dongle", [], + "root@pve2 — pvesnap-recovery → einrichten"), +] + + +def aufnehmen(name, szene, tasten, titel, nachlauf=0.3): + """`nachlauf` fuer Bildschirme, hinter denen noch etwas arbeitet: der + Nachbau haelt sonst den letzten Stand fest, waehrend im Hintergrund noch + ein Proxmox-Task laeuft und danach der naechste Dialog aufgeht.""" + env = umgebung() + env["DOKU"] = HERE + env["PY"] = VENV + term = Terminal(befehl(szene), cols=BREIT, rows=HOCH, env=env, cwd=HERE) + try: + time.sleep(1.3) + term.settle() + term.send([TASTEN.get(t, t) for t in tasten], pause=0.3) + time.sleep(nachlauf) + term.settle() + svg = term.to_svg(titel) + text = "\n".join(l.rstrip() for l in term.screen.display) + finally: + term.close() + + os.makedirs(ZIEL, exist_ok=True) + with open(os.path.join(ZIEL, name + ".svg"), "w", encoding="utf-8") as handle: + handle.write(svg) + return text + + +def main(): + filter_ = sys.argv[1] if len(sys.argv) > 1 else "" + fehler = [] + for eintrag in AUFNAHMEN: + name = eintrag[0] + if filter_ and filter_ not in name: + continue + text = aufnehmen(*eintrag) + leer = not text.strip() + panne = "Traceback" in text + kennzeichen = "LEER" if leer else ("ABSTURZ" if panne else "ok") + print("%-28s %s" % (name, kennzeichen)) + if leer or panne: + fehler.append(name) + print("\n".join(" " + l for l in text.splitlines() if l.strip())[:1500]) + if fehler: + print("\nNachsehen: %s" % ", ".join(fehler)) + return 1 if fehler else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/handbuch/werkstatt/shot.py b/handbuch/werkstatt/shot.py new file mode 100755 index 0000000..930dbb9 --- /dev/null +++ b/handbuch/werkstatt/shot.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python3 +"""Bildschirmfotos aus ncurses-Oberflaechen. + +Startet ein Programm in einem echten Pseudo-Terminal fester Groesse, spielt +Tastendruecke ein, laesst pyte den Bildschirm nachbilden und schreibt das +Ergebnis als SVG. Das SVG ist reiner Text - keine Schriftart eingebettet, +dafuer jede Zeichenkette mit `textLength` fest vermessen. Damit sitzt das +Raster in jedem Browser, egal welche Monospace-Schrift er waehlt. +""" + +from __future__ import annotations + +import fcntl +import os +import pty +import select +import signal +import struct +import subprocess +import sys +import termios +import time +from xml.sax.saxutils import escape + +import pyte + +CW = 8.6 # Zeichenbreite in px +LH = 18.0 # Zeilenhoehe in px +FS = 14.5 # Schriftgroesse +PAD = 14.0 # Rand um den Textbereich +CHROME = 30.0 # Hoehe der Titelleiste + +# Tango - die Palette, die auch Debian im Terminal benutzt. Fett macht aus der +# Grundfarbe die helle Variante; genau das tut ein Terminal mit A_BOLD, und +# genau darauf beruht der Kommentar in curses_util.init_colors(). +PALETTE = { + "black": "#2e3436", "red": "#cc0000", "green": "#4e9a06", + "brown": "#c4a000", "yellow": "#c4a000", "blue": "#3465a4", + "magenta": "#75507b", "cyan": "#06989a", "white": "#d3d7cf", + "brightblack": "#555753", "brightred": "#ef2929", "brightgreen": "#8ae234", + "brightbrown": "#fce94f", "brightyellow": "#fce94f", "brightblue": "#729fcf", + "brightmagenta": "#ad7fa8", "brightcyan": "#34e2e2", "brightwhite": "#eeeeec", +} +BG_DEFAULT = "#1b1e24" +FG_DEFAULT = "#d3d7cf" + +BRIGHT = {"black": "brightblack", "red": "brightred", "green": "brightgreen", + "brown": "brightbrown", "yellow": "brightyellow", "blue": "brightblue", + "magenta": "brightmagenta", "cyan": "brightcyan", "white": "brightwhite"} + + +def _color(name, bold, default): + if name == "default": + # Fett faerbt den Vordergrund nicht um, wenn er gar keine Farbe hat. + return default + if bold: + name = BRIGHT.get(name, name) + if name in PALETTE: + return PALETTE[name] + if len(name) == 6: + try: + int(name, 16) + return "#" + name + except ValueError: + pass + return default + + +class Terminal: + """Ein Programm in einem Pseudo-Terminal, dessen Bildschirm mitgelesen wird.""" + + def __init__(self, command, cols=100, rows=30, env=None, cwd=None): + self.cols, self.rows = cols, rows + self.screen = pyte.Screen(cols, rows) + self.stream = pyte.ByteStream(self.screen) + + self.master, slave = pty.openpty() + fcntl.ioctl(slave, termios.TIOCSWINSZ, + struct.pack("HHHH", rows, cols, cols * 8, rows * 16)) + + environment = dict(os.environ) + environment.update({"TERM": "xterm-256color", "LINES": str(rows), + "COLUMNS": str(cols), "LANG": "de_DE.UTF-8", + "LC_ALL": "de_DE.UTF-8"}) + environment.update(env or {}) + + self.process = subprocess.Popen( + command, stdin=slave, stdout=slave, stderr=slave, cwd=cwd, + env=environment, close_fds=True, start_new_session=True) + os.close(slave) + + # -- lesen ------------------------------------------------------------- + + def _drain(self, timeout): + """Alles lesen, was bis `timeout` kommt. True, wenn etwas kam.""" + got = False + deadline = time.monotonic() + timeout + while True: + rest = deadline - time.monotonic() + if rest <= 0: + return got + ready, _, _ = select.select([self.master], [], [], rest) + if not ready: + return got + try: + data = os.read(self.master, 65536) + except OSError: + return got + if not data: + return got + self.stream.feed(data) + got = True + + def settle(self, quiet=0.35, limit=8.0): + """Warten, bis eine Weile nichts mehr nachkommt.""" + deadline = time.monotonic() + limit + while time.monotonic() < deadline: + if not self._drain(quiet): + return + self._drain(0.1) + + # -- schreiben --------------------------------------------------------- + + def send(self, keys, pause=0.25): + for key in keys if isinstance(keys, (list, tuple)) else [keys]: + os.write(self.master, key.encode("utf-8")) + time.sleep(pause) + self.settle() + + def close(self): + try: + self.process.send_signal(signal.SIGKILL) + self.process.wait(timeout=5) + except (ProcessLookupError, subprocess.TimeoutExpired, OSError): + pass + try: + os.close(self.master) + except OSError: + pass + + # -- ausgeben ---------------------------------------------------------- + + def to_svg(self, title=""): + return render(self.screen, self.cols, self.rows, title) + + +def _runs(screen, y, cols): + """Eine Zeile in Abschnitte gleicher Darstellung zerlegen.""" + line = screen.buffer[y] + out, current = [], None + for x in range(cols): + char = line[x] + style = (char.fg, char.bg, char.bold, char.reverse) + if char.reverse: + style = (char.bg, char.fg, char.bold, False) + if current and current[0] == style: + current[1].append(char.data or " ") + else: + current = (style, [char.data or " "], x) + out.append(current) + return out + + +def render(screen, cols, rows, title=""): + width = cols * CW + 2 * PAD + height = rows * LH + 2 * PAD + (CHROME if title else 0) + top = PAD + (CHROME if title else 0) + + parts = [ + '' + % (width, height, width, height, FS), + '' % (width, height, BG_DEFAULT), + ] + + if title: + parts.append('' % (width - 14, CHROME - 7)) + for index, colour in enumerate(("#ed6a5e", "#f4bf4f", "#61c554")): + parts.append('' + % (16 + index * 17, colour)) + parts.append('%s' + % (width / 2, escape(title))) + + for y in range(rows): + for style, chars, x in _runs(screen, y, cols): + fg_name, bg_name, bold, _ = style + text = "".join(chars) + run_width = len(chars) * CW + bg = _color(bg_name, False, None) + if bg and bg != BG_DEFAULT: + parts.append('' % (PAD + x * CW, top + y * LH, + run_width + 0.4, LH + 0.4, bg)) + if not text.strip(): + continue + fg = _color(fg_name, bold, FG_DEFAULT) + weight = ' font-weight="bold"' if bold else "" + parts.append('%s' + % (PAD + x * CW, top + y * LH + FS * 0.92, fg, weight, + run_width, escape(text))) + + parts.append("") + return "\n".join(parts) + + +def capture(target, command, keys=(), cols=100, rows=30, title="", + env=None, cwd=None, pause=0.3, warmup=1.2): + """Ein Programm starten, Tasten schicken, Bildschirm als SVG ablegen.""" + term = Terminal(command, cols=cols, rows=rows, env=env, cwd=cwd) + try: + time.sleep(warmup) + term.settle() + term.send(list(keys), pause=pause) + time.sleep(0.2) + term.settle() + svg = term.to_svg(title) + finally: + term.close() + + os.makedirs(os.path.dirname(target), exist_ok=True) + with open(target, "w", encoding="utf-8") as handle: + handle.write(svg) + return svg + + +def text_of(svg_screen): + """Nur zum Nachsehen beim Bauen: der Bildschirm als reiner Text.""" + return "\n".join(svg_screen.display) + + +if __name__ == "__main__": + print("Wird von scenes.py benutzt.", file=sys.stderr) diff --git a/handbuch/werkstatt/stage.sh b/handbuch/werkstatt/stage.sh new file mode 100755 index 0000000..3a2a446 --- /dev/null +++ b/handbuch/werkstatt/stage.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Die Kulisse an die richtigen Stellen haengen. +# +# Der Explorer zeigt Pfade an. Im Handbuch sollen das die Pfade sein, die auf +# einem echten Host stehen - /run/pvesnap/mnt/... und /root -, nicht die +# Arbeitsverzeichnisse dieser Werkstatt. Also laeuft die Aufnahme in einem +# eigenen Mount-Namensraum (unshare -r -m), in dem wir uns die Pfade +# hinbiegen duerfen, ohne irgendetwas am System zu veraendern. +set -e + +D="$DOKU/demo" + +mount -t tmpfs none /run +mkdir -p /run/pvesnap/mnt/101-auto-taeglich-20260809-023000 +mkdir -p /run/pvesnap/transfer/dumps +mount --bind "$D/snapshot" /run/pvesnap/mnt/101-auto-taeglich-20260809-023000 +mount --bind "$D/transfer-dumps" /run/pvesnap/transfer/dumps +mount --bind "$D/lokal" /root + +# /etc bleibt vollstaendig, bekommt aber eine Schreibschicht obendrauf - damit +# die Konfiguration dort liegt, wo sie auf dem Host liegt. +mkdir -p /run/overlay/oben /run/overlay/arbeit +mount -t overlay overlay -o lowerdir=/etc,upperdir=/run/overlay/oben,workdir=/run/overlay/arbeit /etc +cp "$D/pvesnap.conf" /etc/pvesnap.conf + +cd /root +exec "$PY" "$DOKU/run.py" "$@" diff --git a/install.sh b/install.sh index 3d60a00..8327016 100755 --- a/install.sh +++ b/install.sh @@ -211,6 +211,22 @@ if [ -f "${SRC}/README.md" ]; then install -m 0644 "${SRC}/README.md" "${DOC_DIR}/README.md" fi +# --- Handbuch --------------------------------------------------------- +# Nur kopieren, wenn es gebaut vorliegt (handbuch/bauen.sh). Auf dem Host +# selbst wird nichts gebaut - mkdocs gehoert nicht auf einen Hypervisor. +# Das gebaute Handbuch ist reines HTML samt Suche und braucht kein Netz. +if [ -d "${SRC}/handbuch/site" ]; then + info "Installiere Handbuch nach ${DOC_DIR}/handbuch" + rm -rf "${DOC_DIR}/handbuch" + install -d -m 0755 "${DOC_DIR}/handbuch" + cp -r "${SRC}/handbuch/site/." "${DOC_DIR}/handbuch/" + find "${DOC_DIR}/handbuch" -type d -exec chmod 0755 {} + + find "${DOC_DIR}/handbuch" -type f -exec chmod 0644 {} + + HANDBUCH=1 +elif [ -d "${SRC}/handbuch" ]; then + info "Handbuch liegt nur als Quelle vor - bauen mit: handbuch/bauen.sh" +fi + # --- systemd ---------------------------------------------------------- info "Installiere systemd-Unit ${UNIT}" install -m 0644 "${SRC}/systemd/pvesnap.service" "${UNIT}" @@ -288,10 +304,22 @@ Fertig. Einen Snapshot als Maschine starten: pvesnap-recovery Uebersicht und Assistent - pvesnap-recovery live 100 --exchange ja - abgeschottet ohne Netz, mit Austauschverzeichnis + pvesnap-recovery live 100 --transfer dumps + abgeschottet ohne Netz, mit Austauschlaufwerk EOF +if [ "${HANDBUCH:-0}" -eq 1 ]; then + cat <