# pvesnap — automatische Snapshots für Proxmox VE Ein systemd-Dienst, der nach frei definierbaren Zeitplänen Snapshots von VMs und Containern anlegt und alte Snapshots nach einer einstellbaren Vorhaltezeit wieder löscht. Alles wird über **eine INI-Datei** gesteuert, die sich auch bequem in einem **ncurses-Editor** bearbeiten lässt. * Beliebig viele **Gruppen** — z. B. „diese VMs stündlich, jene täglich, der Rest monatlich“ * **VM-Auswahl** nach VMID, Name (mit `*`-Mustern), Tag, Pool oder einfach „alle“ * **Vorhaltezeit** je Gruppe nach Anzahl *und/oder* Alter * Jeder Snapshot bekommt eine **Beschreibung**, an der er sich wiedererkennen lässt * Läuft cluster-weit (alle Nodes) über `pvesh` * Nur Python-Standardbibliothek — keine zusätzlichen Pakete nötig --- ## Installation Auf dem Proxmox-Host als root: ```bash git clone pvesnap && cd pvesnap ./install.sh ``` Das Skript legt an: | Pfad | Inhalt | |---|---| | `/usr/lib/pvesnap/` | Programmcode | | `/usr/local/bin/pvesnap` | Startbefehl | | `/etc/pvesnap/pvesnap.conf` | Konfiguration (wird bei Updates **nicht** überschrieben) | | `/var/lib/pvesnap/state.json` | merkt sich die letzten Läufe | | `/etc/systemd/system/pvesnap.service` | systemd-Unit | In der mitgelieferten Beispielkonfiguration sind alle Gruppen auf `enabled = no` gesetzt — es passiert also erst etwas, wenn du eine Gruppe aktivierst. **Deinstallation:** ```bash ./uninstall.sh # Programm und Dienst entfernen, Konfiguration bleibt ./uninstall.sh --purge # zusätzlich /etc/pvesnap und /var/lib/pvesnap löschen ``` Bereits angelegte Snapshots werden dabei **nie** angerührt. --- ## Konfiguration im ncurses-Editor ```bash pvesnap config ``` * **Gruppenliste** — `Enter` bearbeiten, `n` neu, `c` kopieren, `d` löschen, `Leertaste` an/aus, `g` globale Einstellungen, `v` Übersicht welche VM in welcher Gruppe landet, `s` speichern, `r` Dienst neu laden, `q` Ende * **`r` — Dienst neu laden**, ohne den Editor zu verlassen: entspricht `systemctl reload pvesnap`. Ungespeicherte Änderungen werden vorher zum Speichern angeboten, ein gestoppter Dienst zum Starten, und wenn das Neuladen scheitert, wird ein Neustart angeboten. Nach `s` fragt der Editor ohnehin gleich, ob neu geladen werden soll. Oben in der Kopfzeile steht jederzeit, ob der Dienst läuft. (Braucht root — also `sudo pvesnap config`.) * **Gruppe bearbeiten** — Feld auswählen, `Enter` ändern, `Leertaste` umschalten. Ganz unten steht live, auf wie viele Gäste die Auswahl gerade zutrifft. * **VM-Auswahl** (`v`) — Liste aller VMs und Container mit Node, Status und Tags; `Leertaste` auswählen, `a` alle, `n` keine, `i` umkehren, `/` filtern, `Enter` übernehmen. > Beim Speichern wird die INI-Datei neu geschrieben. Eigene Kommentare und ein > `[defaults]`-Abschnitt gehen dabei verloren (die Werte bleiben inhaltlich > erhalten, sie stehen danach in jeder Gruppe einzeln). Eine Sicherung wird als > `pvesnap.conf.bak` abgelegt. --- ## Konfiguration von Hand ```ini [global] prefix = auto # Namenspräfix aller pvesnap-Snapshots check_interval = 60s # wie oft der Dienst nach Fälligem schaut state_file = /var/lib/pvesnap/state.json log_level = INFO task_timeout = 15m retries = 2 # Wiederholungen, wenn eine Sperre belegt ist retry_delay = 60s pause_between = 0s # Pause zwischen zwei Gästen run_on_start = no # beim Start sofort einen Durchlauf machen? dry_run = no # yes = nichts wirklich tun, nur protokollieren description = pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time} [defaults] # Vorgaben für alle Gruppen (optional) skip_stopped = no [group:stündlich] interval = 1h align = yes # an der Uhr ausrichten: 00:00, 01:00, ... keep_count = 24 # höchstens 24 Snapshots je VM keep_time = 2d # nichts älter als 2 Tage tags = stuendlich # alle VMs mit diesem Tag skip_stopped = yes [group:täglich] schedule = daily at = 02:30 keep_count = 14 keep_time = 21d names = web-*, db-* [group:monatlich] schedule = monthly day_of_month = 1 at = 04:00 keep_count = 6 all = yes exclude_tags = nosnap ``` Nach jeder Änderung: ```bash pvesnap check # Konfiguration prüfen systemctl reload pvesnap # Dienst übernimmt die Änderung ohne Neustart ``` ### Zeitplan Entweder ein Intervall … ```ini interval = 30m # 30m, 1h, 6h, 2d12h, 1w ... align = yes # yes: an der Uhr ausgerichtet (00:00, 00:30, 01:00, ...) # no: 30 Minuten nach dem letzten Lauf ``` … oder ein fester Termin: | `schedule` | zusätzliche Angaben | Beispiel | |---|---|---| | `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 | `day_of_month = 31` wird in kürzeren Monaten automatisch auf den letzten Tag gezogen. Läuft der Host zum Termin nicht, wird der Lauf beim nächsten Start nachgeholt. ### Vorhaltezeit ```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 ``` Beides ist kombinierbar — gelöscht wird, was *eine* der beiden Grenzen reißt. Mindestens eine der beiden Angaben muss gesetzt sein, sonst würde die Zahl der Snapshots unbegrenzt wachsen (`pvesnap check` weist darauf hin). ### Welche VMs? Alle Angaben wirken als **ODER**, Ausschlüsse gewinnen immer: ```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 = prod, wichtig # nach Proxmox-Tag pools = Kunden # nach Proxmox-Pool types = qemu # nur VMs (bzw. lxc = nur Container; leer = beides) exclude_vmids = 999 exclude_names = *-test exclude_tags = nosnap ``` ### Snapshot-Optionen ```ini vmstate = yes # RAM mitsichern (nur QEMU und nur bei laufender VM) skip_stopped = yes # gestoppte Gäste überspringen description = Sicherung von {name} ({vmid}) vom {date} ``` Platzhalter der Beschreibung: `{group}` `{group_slug}` `{vmid}` `{name}` `{node}` `{type}` `{pool}` `{tags}` `{date}` `{time}` `{datetime}` `{timestamp}` `{keep_time}` `{keep_count}` `{schedule}` --- ## Namensschema und Sicherheit ``` auto-taeglich-20260730-023000 │ │ │ └── Uhrzeit │ │ └────────── Datum │ └──────────────────── Kurzname der Gruppe └─────────────────────────── prefix aus [global] ``` pvesnap löscht **ausschließlich** Snapshots, deren Name exakt auf dieses Muster passt und deren Gruppen-Kurzname zur jeweiligen Gruppe gehört. Von Hand oder von anderen Werkzeugen angelegte Snapshots bleiben garantiert unangetastet. Umlaute und Sonderzeichen im Gruppennamen werden für den Kurznamen umgeschrieben (`täglich` → `taeglich`). Wenn zwei Gruppen denselben Kurznamen ergäben, meldet das `pvesnap check`. --- ## Befehle ```bash pvesnap status # Übersicht: Gruppen, letzte und nächste Läufe pvesnap vms # alle VMs und die Gruppen, in denen sie stecken pvesnap list # vorhandene pvesnap-Snapshots (-a = auch fremde) pvesnap check # Konfiguration prüfen pvesnap config # ncurses-Editor pvesnap run # jetzt fällige Gruppen ausführen pvesnap run --force -g täglich # eine Gruppe sofort ausführen pvesnap run --force --dry-run # Probelauf, ändert nichts pvesnap prune -g stündlich # nur aufräumen, keine neuen Snapshots systemctl status pvesnap systemctl reload pvesnap # Konfiguration neu einlesen (SIGHUP) journalctl -u pvesnap -f # Protokoll mitlesen ``` 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. Mit `-c /pfad/zur.conf` lässt sich jederzeit eine andere Konfiguration verwenden, z. B. zum Ausprobieren. --- ## Hinweise aus der Praxis * **Snapshot ist kein Backup.** Snapshots liegen auf demselben Storage wie die VM. Für echte Sicherungen zusätzlich `vzdump` / Proxmox Backup Server verwenden. * **Speicherplatz:** Jeder Snapshot hält alte Blöcke fest. Bei schreibfreudigen VMs lieber kurze Vorhaltezeiten wählen und den Storage im Auge behalten. * **Nicht jedes Storage kann Snapshots** — LVM-thick und Verzeichnis-Storage mit `raw`-Images können es nicht. Solche VMs melden einen Fehler im Protokoll; die übrigen laufen normal weiter. * **`vmstate = yes`** friert die VM kurz ein und braucht Platz in Höhe des zugewiesenen RAM. Für regelmäßige Läufe meist unnötig. * **Gruppe umbenennen:** Der neue Name bekommt einen eigenen Kurznamen. Snapshots unter dem alten Namen werden dann nicht mehr automatisch aufgeräumt — vorher aufräumen lassen oder die alten Snapshots von Hand entfernen. * **Zeitumstellung:** Es wird mit lokaler Zeit gerechnet. Ein täglicher Termin um 02:30 kann in der Umstellungsnacht ausfallen oder doppelt anstehen. --- ## Wenn Snapshots am Storage-Lock scheitern ``` trying to acquire cfs lock 'storage-data' ... TASK ERROR: cfs-lock 'storage-data' error: got lock request timeout ``` `storage-data` ist dabei 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 s auf, wenn sie jemand anderes hält. pvesnap geht damit so um: * Der Aufruf wartet, bis der Snapshot-Task in Proxmox **wirklich fertig** ist, bevor die nächste VM drankommt — sonst würden sich die Läufe gegenseitig 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. * Mit `pause_between = 10s` lässt sich zusätzlich Druck vom Storage nehmen, wenn viele VMs auf demselben Storage liegen. ### Der Dienst scheitert, von Hand geht es Scheitert **jeder** Lauf des Dienstes am Storage-Lock, während derselbe Befehl in der Shell funktioniert, liegt es an der Umgebung des Dienstes — nicht am Storage. `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 systemd-Unit eingeschränkt wurde. Der Klassiker ist `ProtectSystem=` in der Unit: das 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 drei müssen leer bzw. `no` sein. Die mitgelieferte Unit enthält deshalb bewusst **keine** Sandbox-Optionen. ### Wenn die Sperre wirklich belegt ist Diese Kommandos helfen beim Eingrenzen: ```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. --- ## Aufbau ``` 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 tui.py ncurses-Editor cli.py Kommandozeile config/pvesnap.conf.example systemd/pvesnap.service install.sh uninstall.sh ```