#!/bin/bash # felhom-restore-beside.sh — restore a whole-guest archive BESIDE a live original, safely (R-834). # # Run as root on a Proxmox host. The restored guest is for READING (pct mount, a file copy, a check): # - it is created with onboot 0, so a host reboot never starts it; # - every mpN that binds a HOST path (mp8 = the household's real drives, mp9 = the original guest's # bootstrap) is removed before anything can start it; # - every NIC is set link_down=1 (the archive keeps the original's MAC and island address); # - it is NEVER started by this script. # The script then reads the config back and fails loudly if any of that is not true. # # NOT for a replaced host where the original is gone — there the binds are right; use the agent's DR # bring-up or RUNBOOK-manual-guest-restore.md §3. # # Usage: felhom-restore-beside.sh # e.g. felhom-restore-beside.sh 9299 tmp-dooplex-copy:backup/ct/9201/2026-10-03T19:00:00Z nvme-scratch # # Test: scripts/test_felhom_restore_beside.py (a fake pct on PATH). set -euo pipefail die() { echo "restore-beside: REFUSED: $*" >&2; exit 1; } log() { echo "restore-beside: $*"; } [ $# -eq 3 ] || die "usage: $0 " vmid=$1 volid=$2 storage=$3 [[ "$vmid" =~ ^[0-9]+$ ]] || die "vmid '$vmid' is not a number" # The restore-test band and the standing scratch are the agent's; a customer guest is never a target. if [ "$vmid" -ge 990000 ] && [ "$vmid" -le 990009 ]; then die "vmid $vmid is the agent's restore-test band"; fi [ "$vmid" != 9999 ] || die "vmid 9999 is the agent's standing scratch" if pct status "$vmid" >/dev/null 2>&1; then die "vmid $vmid already exists — this script never restores over a guest"; fi log "restoring $volid -> $vmid on $storage with onboot 0 (never started)" pct restore "$vmid" "$volid" --storage "$storage" --unprivileged 1 --onboot 0 # Strip host-path binds and take the NICs down, BEFORE anything else can touch the guest. conf=$(pct config "$vmid" --current) binds=$(printf '%s\n' "$conf" | sed -n -E 's/^(mp[0-9]+): \/.*/\1/p' | paste -sd, -) if [ -n "$binds" ]; then log "removing host-path binds: $binds" pct set "$vmid" --delete "$binds" fi printf '%s\n' "$conf" | sed -n -E 's/^(net[0-9]+): (.*)$/\1 \2/p' | while read -r key val; do case ",$val," in *",link_down=1,"*) continue ;; esac log "link down: $key" pct set "$vmid" "--$key" "$val,link_down=1" done pct set "$vmid" --onboot 0 # Read back: the CONSEQUENCE, not the commands. conf=$(pct config "$vmid" --current) bad="" printf '%s\n' "$conf" | grep -qx 'onboot: 0' || bad="$bad onboot-not-0" printf '%s\n' "$conf" | grep -qE '^mp[0-9]+: /' && bad="$bad host-bind-left" printf '%s\n' "$conf" | grep -E '^net[0-9]+: ' | grep -qv 'link_down=1' && bad="$bad nic-up" if [ -n "$bad" ]; then echo "restore-beside: FAILED read-back on $vmid:$bad — do NOT start it; destroy it with: pct destroy $vmid" >&2 exit 2 fi log "OK: $vmid has onboot 0, no host-path binds, every NIC link_down; it was not started" log "read it with: pct mount $vmid (then pct unmount $vmid; pct destroy $vmid --purge when done)"