#!/bin/bash # ============================================================================= # plugin/sbin/podman-backup.sh # # Rollback support — see docs/ARCHITECTURE.md section 14 (Rollback). Two # independent kinds of backup, matching the two kinds of thing an update can # break: # # PACKAGES plugin/podman.plg downloads each release's .txz files # straight into $PODMAN_BOOT_DIR/backup/packages// # — all 7 component packages of one plugin release share a # single directory named after the PLUGIN version (not each # component's own version), since a rollback means "go back to # plugin release X" as one unit — rather than into a scratch # dir that gets discarded, so the previously installed # version's packages are automatically still on disk after an # update, with no separate copy step. `restore-packages # ` reinstalls every package from one of those # directories. # # CONFIG Before a config schema migration (see podman.cfg's # CONFIG_SCHEMA_VERSION), the plg's postinstall calls # `snapshot-config` to copy the current *.conf + podman.cfg # into $PODMAN_BOOT_DIR/backup/config//. # `restore-config ` copies them back. # # `prune` bounds how many old snapshots of each kind are kept, since flash # space is limited (see ARCHITECTURE.md section 14, "Rollback-Historie # begrenzen"). # # Usage: # podman-backup.sh snapshot-config # podman-backup.sh restore-config # podman-backup.sh restore-packages # podman-backup.sh list # podman-backup.sh prune [--keep N] # ============================================================================= set -eu SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=./podman-common.sh . "$SCRIPT_DIR/podman-common.sh" DEFAULT_KEEP=3 cmd_snapshot_config() { local ts ts=$(date -u +%Y%m%dT%H%M%SZ) local dest="$PODMAN_BACKUP_DIR/config/$ts" mkdir -p "$dest" local f for f in podman.cfg containers.conf storage.conf registries.conf policy.json; do [ -f "$PODMAN_BOOT_DIR/$f" ] && cp "$PODMAN_BOOT_DIR/$f" "$dest/" done podman_log "backup: config snapshot saved to $dest" echo "$ts" } cmd_restore_config() { local ts="${1:?usage: podman-backup.sh restore-config }" local src="$PODMAN_BACKUP_DIR/config/$ts" if [ ! -d "$src" ]; then podman_log_error "backup: no config snapshot found at $src" exit 1 fi local f for f in "$src"/*; do [ -f "$f" ] || continue cp "$f" "$PODMAN_BOOT_DIR/$(basename "$f")" done podman_log "backup: config restored from snapshot $ts — restart podman (rc.podman restart) to apply" } cmd_restore_packages() { local version="${1:?usage: podman-backup.sh restore-packages }" local src="$PODMAN_BACKUP_DIR/packages/$version" if [ ! -d "$src" ]; then podman_log_error "backup: no package backup found for version $version at $src" exit 1 fi podman_require_command upgradepkg local txz local restored=0 for txz in "$src"/*.txz; do [ -f "$txz" ] || continue podman_log "backup: reinstalling $(basename "$txz") from backup (version $version)" upgradepkg --reinstall --install-new "$txz" restored=$((restored + 1)) done if [ "$restored" -eq 0 ]; then podman_log_error "backup: no .txz files found in $src" exit 1 fi podman_notify "Podman packages rolled back" \ "Reinstalled $restored package(s) from the version $version backup. Run 'rc.podman restart' to apply." \ "warning" podman_log "backup: package rollback to version $version complete ($restored package(s))" } cmd_list() { echo "Config snapshots ($PODMAN_BACKUP_DIR/config/):" if [ -d "$PODMAN_BACKUP_DIR/config" ]; then find "$PODMAN_BACKUP_DIR/config" -mindepth 1 -maxdepth 1 -type d -printf ' %f\n' 2> /dev/null | sort -r fi echo echo "Package backups ($PODMAN_BACKUP_DIR/packages/):" if [ -d "$PODMAN_BACKUP_DIR/packages" ]; then find "$PODMAN_BACKUP_DIR/packages" -mindepth 1 -maxdepth 1 -type d -printf ' %f\n' 2> /dev/null | sort -r fi } cmd_prune() { local keep="$DEFAULT_KEEP" if [ "${1:-}" = "--keep" ] && [ -n "${2:-}" ]; then keep="$2" fi local kind for kind in config packages; do local dir="$PODMAN_BACKUP_DIR/$kind" [ -d "$dir" ] || continue # Sort newest-first by directory name (timestamps and semver both sort # correctly lexicographically here — timestamps are zero-padded ISO8601, # versions are compared via `sort -V`), then remove everything beyond # $keep. local sorted if [ "$kind" = "config" ]; then sorted=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -r) else sorted=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -rV) fi local i=0 local name while IFS= read -r name; do [ -z "$name" ] && continue i=$((i + 1)) if [ "$i" -gt "$keep" ]; then podman_log "backup: pruning old $kind backup: $name" rm -rf "${dir:?}/$name" fi done <<< "$sorted" done } case "${1:-}" in snapshot-config) cmd_snapshot_config ;; restore-config) shift; cmd_restore_config "$@" ;; restore-packages) shift; cmd_restore_packages "$@" ;; list) cmd_list ;; prune) shift; cmd_prune "$@" ;; *) echo "usage: $0 {snapshot-config|restore-config |restore-packages |list|prune [--keep N]}" >&2 exit 1 ;; esac