Files
proxmox-snapshot-service/install.sh
T
duffyduckandClaude Opus 5.5 74be5aee59 Platz vor jedem Snapshot pruefen, bei Mangel alte loeschen, per Mail warnen
Volle Ceph-Pools legen alle VMs darauf lahm. Vor jedem Snapshot gilt jetzt
je Storage des Gastes: frei >= sofortiger Verbrauch + space_min_free
(Vorgabe 20 %). Die Reserve bleibt also auch nach dem Snapshot frei.

Reicht es nicht, wird der aelteste pvesnap-Snapshot des Gastes geloescht und
neu geprueft, bis es reicht. Sind nur noch die neuesten space_keep (Vorgabe
7) uebrig, entfaellt der Snapshot und der naechste Gast kommt dran.

Gezaehlt wird je Gast ueber alle Gruppen, anders als bei der Vorhaltezeit.
Auf dem Testhost haette eine Grenze je Gruppe nie gegriffen: keep_count = 5
liegt unter 7, und 13 GiB steckten in einer Gruppe, die es nicht mehr gibt.

Sofortiger Verbrauch: Dicke ZFS-Zvols muessen ihre Reservierung auch nach
dem Snapshot garantieren, das kostet alles seit dem letzten Snapshot
Geschriebene. Exakt ist das refreservation - usedbyrefreservation; die Werte
kommen per zfs get, bei anderen Nodes per ssh ueber die IP aus dem
Clusterstatus, weil Node-Namen nicht ueberall aufloesen. Dazu kommt der RAM
bei vmstate. --dry-run zeigt die Rechnung je Storage.

Ceph und ZFS geben Platz erst im Hintergrund frei. Deshalb wird nach jedem
Loeschen gewartet, solange der Platz waechst, und erst nach space_settle
(Vorgabe 30s) ohne Zuwachs weitergemacht. Ohne das wurde auf Ceph ein Snapshot
zu viel geloescht. Laesst sich der Platz nicht feststellen, wird nichts
geloescht und der Snapshot trotzdem versucht.

Neu ist [mail]: eine Alarm-Mail je Gruppe und Lauf, wenn ein Snapshot wegen
Platzmangels ausfaellt. STARTTLS oder SSL/TLS mit Zertifikatspruefung, ohne
Rueckfall auf Klartext. Einzurichten im Editor (Taste m) oder von Hand, Test
mit pvesnap mail-test. Liegt ein Passwort in der Datei, setzt der Editor die
Rechte auf 600, auch fuer die .bak.

Das Handbuch beschreibt, wie man Ceph (Ratios) und ZFS (Notreserve) frueher
"voll" sagen laesst und was im Notfall zu tun ist. Ausserdem war
"pvesnap run --dry-run" ueberall falsch: Die Option ist global und muss vor
dem Unterbefehl stehen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:27:21 +01:00

346 lines
13 KiB
Bash
Executable File

#!/bin/bash
# ---------------------------------------------------------------------
# pvesnap - Installation
#
# Aufruf: ./install.sh [Optionen]
#
# --with-webexplorer Web-Oberflaeche zusaetzlich als Dienst
# einrichten (pvesnap-web.service).
# Erfordert --port.
# --port <nummer> Port fuer die Web-Oberflaeche, z.B. 8823
# --bind <adresse> Adresse, auf der sie lauscht
# (Vorgabe: 0.0.0.0 = alle)
# --no-start installieren, aber Dienste nicht starten
# --no-install-deps fehlende Werkzeuge (parted, exfatprogs) nicht
# nachinstallieren
# --force auch ohne erkanntes Proxmox VE installieren
# -h, --help diese Hilfe
#
# Beispiele:
# ./install.sh
# ./install.sh --with-webexplorer --port 8823
# ./install.sh --with-webexplorer --port 8443 --bind 10.0.0.5
#
# Die Web-Oberflaeche meldet Benutzer gegen Proxmox VE an - dieselben
# Zugangsdaten und Realms wie im Proxmox-Webinterface.
# ---------------------------------------------------------------------
set -euo pipefail
LIB_DIR="/usr/lib/pvesnap"
BIN="/usr/local/bin/pvesnap"
CONF_DIR="/etc/pvesnap"
CONF="${CONF_DIR}/pvesnap.conf"
STATE_DIR="/var/lib/pvesnap"
DOC_DIR="/usr/share/doc/pvesnap"
UNIT="/etc/systemd/system/pvesnap.service"
WEB_UNIT="/etc/systemd/system/pvesnap-web.service"
WEB_CONF="${CONF_DIR}/web.conf"
SRC="$(cd "$(dirname "$0")" && pwd)"
START=1
FORCE=0
DEPS=1
WEB_RUNNING=0
WEB=0
WEB_PORT=""
WEB_BIND="0.0.0.0"
while [ $# -gt 0 ]; do
case "$1" in
--with-webexplorer) WEB=1 ;;
--port) WEB_PORT="${2:-}"; shift ;;
--port=*) WEB_PORT="${1#*=}" ;;
--bind) WEB_BIND="${2:-}"; shift ;;
--bind=*) WEB_BIND="${1#*=}" ;;
--no-start) START=0 ;;
--no-install-deps) DEPS=0 ;;
--force) FORCE=1 ;;
-h|--help) sed -n '2,27p' "$0" | sed 's/^#[[:space:]]\{0,1\}//'; exit 0 ;;
*) echo "Unbekannte Option: $1" >&2
echo "Hilfe mit: $0 --help" >&2; exit 1 ;;
esac
shift
done
info() { printf '\033[1;32m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m==>\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31m==>\033[0m %s\n' "$*" >&2; exit 1; }
# --- Web-Oberflaeche: Angaben pruefen ---------------------------------
if [ "$WEB" -eq 1 ]; then
if [ -z "$WEB_PORT" ]; then
fail "--with-webexplorer benoetigt einen Port.
Beispiel: ./install.sh --with-webexplorer --port 8823
Hilfe mit: $0 --help"
fi
case "$WEB_PORT" in
''|*[!0-9]*) fail "--port erwartet eine Zahl, bekommen: '$WEB_PORT'" ;;
esac
if [ "$WEB_PORT" -lt 1 ] || [ "$WEB_PORT" -gt 65535 ]; then
fail "--port muss zwischen 1 und 65535 liegen (bekommen: $WEB_PORT)"
fi
if [ "$WEB_PORT" -eq 8006 ]; then
fail "Port 8006 gehoert dem Proxmox-Webinterface - bitte einen anderen waehlen."
fi
# Laeuft unsere eigene Web-Oberflaeche schon, gehoert ihr der Port auch -
# das ist kein Konflikt, sondern der Normalfall bei einer Aktualisierung.
# Sie wird weiter unten gestoppt, bevor der Code ausgetauscht wird.
if systemctl is-active --quiet pvesnap-web.service 2>/dev/null; then
WEB_RUNNING=1
elif ss -ltn 2>/dev/null | grep -qE "[:.]${WEB_PORT}[[:space:]]"; then
warn "Auf Port ${WEB_PORT} lauscht bereits etwas anderes - der Dienst"
warn "wird sich moeglicherweise nicht starten lassen."
ss -ltnp 2>/dev/null | grep -E "[:.]${WEB_PORT}[[:space:]]" | sed 's/^/ /' >&2 || true
fi
[ -n "$WEB_BIND" ] || fail "--bind erwartet eine Adresse"
elif [ -n "$WEB_PORT" ]; then
fail "--port wirkt nur zusammen mit --with-webexplorer."
fi
[ "$(id -u)" -eq 0 ] || fail "Bitte als root ausfuehren (sudo ./install.sh)."
command -v python3 >/dev/null 2>&1 || fail "python3 wird benoetigt."
python3 - <<'PY' || fail "Python 3.7 oder neuer wird benoetigt."
import sys
sys.exit(0 if sys.version_info >= (3, 7) else 1)
PY
if ! command -v pvesh >/dev/null 2>&1; then
if [ "$FORCE" -eq 1 ]; then
warn "pvesh nicht gefunden - installiere trotzdem (--force)."
START=0
else
fail "pvesh nicht gefunden. pvesnap gehoert auf einen Proxmox-VE-Host.
Mit --force laesst sich die Installation trotzdem erzwingen."
fi
fi
command -v systemctl >/dev/null 2>&1 || fail "systemd wird benoetigt."
[ -d "${SRC}/pvesnap" ] || fail "Verzeichnis 'pvesnap' nicht gefunden (Aufruf aus dem Projektordner?)."
# --- Werkzeuge fuer Transfer-Laufwerke -------------------------------------
# Transfer-Laufwerke sind Abbilddateien, die auf dem Host formatiert und
# eingehaengt werden. Dafuer braucht es eine Partitionstabelle (parted) und
# einen Formatierer je Dateisystem.
have_tool() {
command -v "$1" >/dev/null 2>&1 && return 0
for d in /usr/local/sbin /usr/sbin /sbin; do
[ -x "$d/$1" ] && return 0
done
return 1
}
if [ "$DEPS" -eq 1 ]; then
MISSING=""
have_tool parted || MISSING="${MISSING} parted"
have_tool mkfs.exfat || MISSING="${MISSING} exfatprogs"
if [ -n "$MISSING" ]; then
info "Fehlende Werkzeuge fuer Transfer-Laufwerke:${MISSING}"
if command -v apt-get >/dev/null 2>&1; then
info "Installiere:${MISSING}"
# shellcheck disable=SC2086
if DEBIAN_FRONTEND=noninteractive apt-get install -y ${MISSING}; then
info "Werkzeuge installiert"
else
warn "Installation fehlgeschlagen - Transfer-Laufwerke lassen sich"
warn "dann nur mit den vorhandenen Dateisystemen anlegen."
fi
else
warn "apt-get nicht gefunden - bitte von Hand:${MISSING}"
fi
fi
fi
# --- Laufendes anhalten -----------------------------------------------
# Der Code wird gleich ersetzt. Die Web-Oberflaeche kann dabei noch Snapshots
# eingebunden haben - sie bekommt beim Stoppen ihr SIGTERM und haengt sauber
# aus. Wuerde sie einfach weiterlaufen, blieben rbd-Zuordnungen und
# Mountpunkte einer Fassung stehen, die es nicht mehr gibt.
if [ "$WEB_RUNNING" -eq 1 ]; then
info "Web-Oberflaeche laeuft - halte sie fuer die Aktualisierung an"
if systemctl stop pvesnap-web.service; then
info " angehalten, Port ${WEB_PORT} ist frei"
else
warn " liess sich nicht anhalten - bitte pruefen:"
warn " systemctl status pvesnap-web"
fi
fi
# Bewusst kein "pvesnap-explorer --cleanup" an dieser Stelle: das wuerde auch
# die Einbindungen einer Sitzung loesen, die jemand gerade in einem anderen
# Terminal offen hat. Wer nach einem Absturz aufraeumen will, ruft es selbst.
# --- Programm ---------------------------------------------------------
info "Installiere Programm nach ${LIB_DIR}"
rm -rf "${LIB_DIR}/pvesnap"
install -d -m 0755 "${LIB_DIR}"
cp -r "${SRC}/pvesnap" "${LIB_DIR}/pvesnap"
find "${LIB_DIR}/pvesnap" -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null || true
chmod -R go-w "${LIB_DIR}/pvesnap"
info "Installiere Startbefehle"
install_command() { # $1 = Befehlsname, $2 = Python-Modul
cat > "/usr/local/bin/$1" <<EOF
#!/bin/sh
# von install.sh erzeugt
PYTHONPATH="${LIB_DIR}\${PYTHONPATH:+:\$PYTHONPATH}" exec /usr/bin/python3 -m $2 "\$@"
EOF
chmod 0755 "/usr/local/bin/$1"
info " /usr/local/bin/$1"
}
install_command pvesnap pvesnap
install_command pvesnap-explorer pvesnap.explorer
install_command pvesnap-web pvesnap.web
install_command pvesnap-recovery pvesnap.recovery_ui
# --- Konfiguration ----------------------------------------------------
install -d -m 0755 "${CONF_DIR}" "${STATE_DIR}" "${DOC_DIR}"
if [ -f "${CONF}" ]; then
info "Vorhandene Konfiguration bleibt unveraendert: ${CONF}"
install -m 0644 "${SRC}/config/pvesnap.conf.example" "${CONF_DIR}/pvesnap.conf.example"
else
info "Lege Beispielkonfiguration an: ${CONF}"
install -m 0640 "${SRC}/config/pvesnap.conf.example" "${CONF}"
install -m 0644 "${SRC}/config/pvesnap.conf.example" "${CONF_DIR}/pvesnap.conf.example"
NEW_CONFIG=1
fi
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}"
systemctl daemon-reload
info "Pruefe Konfiguration"
if ! "${BIN}" --config "${CONF}" check; then
warn "Die Konfiguration ist noch nicht vollstaendig - Dienst wird nicht gestartet."
START=0
fi
if systemctl enable pvesnap.service >/dev/null 2>&1; then
info "Dienst beim Systemstart aktiviert"
else
warn "Dienst konnte nicht aktiviert werden - bitte 'systemctl enable pvesnap' pruefen."
fi
# --- Web-Oberflaeche als Dienst ---------------------------------------
if [ "$WEB" -eq 1 ]; then
info "Richte Web-Oberflaeche als Dienst ein (Port ${WEB_PORT})"
cat > "${WEB_CONF}" <<EOF
# Aufrufparameter von pvesnap-web.service
# Nach Aenderungen: systemctl restart pvesnap-web
#
# --auth pve Anmeldung mit den Benutzern von Proxmox VE (empfohlen)
# --auth token Zugangsschluessel in der Adresse
# --auth none ohne jede Anmeldung - nur in vertrauenswuerdigen Netzen
#
# Sichtbar ist ein Gast nur fuer Benutzer, die darauf das Recht aus
# --require-privilege besitzen (Vorgabe: VM.Snapshot). Mit --allow-user
# lassen sich einzelne Benutzer generell zulassen.
PVESNAP_WEB_ARGS=--bind ${WEB_BIND} --port ${WEB_PORT} --auth pve
EOF
chmod 0640 "${WEB_CONF}"
install -m 0644 "${SRC}/systemd/pvesnap-web.service" "${WEB_UNIT}"
systemctl daemon-reload
if systemctl enable pvesnap-web.service >/dev/null 2>&1; then
info "Web-Oberflaeche beim Systemstart aktiviert"
else
warn "pvesnap-web konnte nicht aktiviert werden."
fi
fi
if [ "$START" -eq 1 ]; then
info "Starte Dienst"
systemctl restart pvesnap.service
sleep 1
systemctl --no-pager --lines=5 status pvesnap.service || true
if [ "$WEB" -eq 1 ]; then
info "Starte Web-Oberflaeche"
systemctl restart pvesnap-web.service
sleep 1
systemctl --no-pager --lines=3 status pvesnap-web.service || true
fi
else
warn "Dienste wurden nicht gestartet."
fi
cat <<EOF
Fertig.
Konfiguration bearbeiten : pvesnap config (ncurses-Editor)
nano ${CONF}
Konfiguration pruefen : pvesnap check
Uebersicht : pvesnap status
VMs und Gruppen : pvesnap vms
Testlauf ohne Aenderung : pvesnap --dry-run run --force
Nach Aenderungen : systemctl reload pvesnap
Protokoll : journalctl -u pvesnap -f
Dateien aus einem Snapshot holen:
pvesnap-explorer zwei Fenster wie im Midnight Commander
pvesnap-web im Browser durchsehen und herunterladen
Einen Snapshot als Maschine starten:
pvesnap-recovery Uebersicht und Assistent
pvesnap-recovery live 100 --transfer dumps
abgeschottet ohne Netz, mit Austauschlaufwerk
EOF
if [ "${HANDBUCH:-0}" -eq 1 ]; then
cat <<EOF
Handbuch (funktioniert ohne Netz, mit Volltextsuche):
${DOC_DIR}/handbuch/index.html
Im Browser oeffnen, oder vom Arbeitsplatz aus holen:
scp -r root@\$(hostname -f):${DOC_DIR}/handbuch .
EOF
fi
if [ "$WEB" -eq 1 ]; then
cat <<EOF
Web-Oberflaeche laeuft als Dienst:
Adresse : http://<IP-dieses-Hosts>:${WEB_PORT}/
Anmeldung : Benutzer von Proxmox VE (gleiche Zugangsdaten wie dort)
Einstellungen : ${WEB_CONF}
Status : systemctl status pvesnap-web
Protokoll : journalctl -u pvesnap-web -f
Sichtbar ist ein Gast nur fuer Benutzer mit dem Recht VM.Snapshot darauf;
root@pam sieht alles. Anzupassen in ${WEB_CONF}.
EOF
fi
if [ "${NEW_CONFIG:-0}" -eq 1 ]; then
cat <<EOF
Hinweis: In der Beispielkonfiguration sind alle Gruppen mit "enabled = no"
angelegt. Erst nach dem Aktivieren einer Gruppe werden Snapshots erstellt.
EOF
fi