Files
myron 04fc9941ff Initial: backup/restore scripts, README
- backup.sh: weekly cron script for PVE1+PVE2
- restore.sh: interactive disaster recovery wizard
- README.md: step-by-step recovery guide for both nodes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 03:42:39 +00:00

304 lines
13 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# Proxmox Config Restore — run on a freshly installed Proxmox node
#
# Usage:
# bash restore.sh pve1 — restore PVE1 (primary, hostname "pve")
# bash restore.sh pve2 — restore PVE2 (secondary, hostname "pve2")
#
# Prerequisites on fresh Proxmox install:
# apt install -y git
# git clone https://<PAT>@github.com/myronblair/proxmox-config.git /opt/proxmox-config
# bash /opt/proxmox-config/restore.sh pve1
# =============================================================================
TARGET="${1:-}"
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m'
header() { echo -e "\n${CYAN}══════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════${NC}"; }
success() { echo -e "${GREEN}$*${NC}"; }
warn() { echo -e "${YELLOW}$*${NC}"; }
info() { echo -e " → $*"; }
die() { echo -e "${RED}$*${NC}" >&2; exit 1; }
confirm() {
local msg="$1"
echo -e "\n${YELLOW} $msg${NC}"
read -rp " Apply this section? [Y/n] " ans
[[ "${ans:-Y}" =~ ^[Yy]$ ]]
}
# ---------------------------------------------------------------------------
# Validate arguments
# ---------------------------------------------------------------------------
if [[ -z "$TARGET" ]]; then
echo "Usage: $0 pve1|pve2"
echo ""
echo " pve1 — restore primary node (hostname: pve, IP: 10.48.200.90)"
echo " pve2 — restore secondary node (hostname: pve2, IP: 10.48.200.91)"
exit 1
fi
if [[ "$TARGET" == "pve1" ]]; then
NODE_DIR="$REPO_DIR/pve"
EXPECTED_HOSTNAME="pve"
EXPECTED_IP="10.48.200.90"
elif [[ "$TARGET" == "pve2" ]]; then
NODE_DIR="$REPO_DIR/pve2"
EXPECTED_HOSTNAME="pve2"
EXPECTED_IP="10.48.200.91"
else
die "Unknown target: $TARGET. Use pve1 or pve2."
fi
SHARED_DIR="$REPO_DIR/shared"
[[ -d "$NODE_DIR" ]] || die "Node directory not found: $NODE_DIR"
[[ $(id -u) -eq 0 ]] || die "Must run as root"
# ---------------------------------------------------------------------------
# Welcome banner
# ---------------------------------------------------------------------------
clear
echo -e "${CYAN}"
cat << 'BANNER'
╔═══════════════════════════════════════════════════════╗
║ PROXMOX CONFIG RESTORE — OrbiShosting ║
╚═══════════════════════════════════════════════════════╝
BANNER
echo -e "${NC}"
echo " Target : $TARGET ($EXPECTED_HOSTNAME$EXPECTED_IP)"
echo " Source : $REPO_DIR"
echo " Date : $(date)"
echo ""
warn "This script restores configs to a FRESH Proxmox install."
warn "Running on an existing node may overwrite running config."
echo ""
read -rp " Type 'yes' to continue: " confirm_start
[[ "$confirm_start" == "yes" ]] || { echo "Aborted."; exit 0; }
APPLIED=()
SKIPPED=()
# ---------------------------------------------------------------------------
# SECTION 1: Hostname
# ---------------------------------------------------------------------------
header "1 / 8 — Hostname"
info "Will set hostname to: $EXPECTED_HOSTNAME"
[[ -f "$NODE_DIR/network/hostname" ]] && info "Backed-up hostname: $(cat $NODE_DIR/network/hostname)"
if confirm "Set hostname to $EXPECTED_HOSTNAME?"; then
echo "$EXPECTED_HOSTNAME" > /etc/hostname
hostname "$EXPECTED_HOSTNAME"
success "Hostname set to $EXPECTED_HOSTNAME"
APPLIED+=("hostname")
else
SKIPPED+=("hostname")
fi
# ---------------------------------------------------------------------------
# SECTION 2: Network interfaces
# ---------------------------------------------------------------------------
header "2 / 8 — Network Configuration"
if [[ -f "$NODE_DIR/network/interfaces" ]]; then
info "Current /etc/network/interfaces:"
cat /etc/network/interfaces | sed 's/^/ /'
info ""
info "Backed-up interfaces:"
cat "$NODE_DIR/network/interfaces" | sed 's/^/ /'
warn "Applying network config requires a reboot to take effect."
if confirm "Restore network interfaces?"; then
cp /etc/network/interfaces "/etc/network/interfaces.bak.$(date +%s)"
cp "$NODE_DIR/network/interfaces" /etc/network/interfaces
[[ -d "$NODE_DIR/network/interfaces.d" ]] && \
rsync -a "$NODE_DIR/network/interfaces.d/" /etc/network/interfaces.d/
success "Network interfaces restored (reboot required)"
APPLIED+=("network")
else
SKIPPED+=("network")
fi
else
warn "No network interfaces backup found — skipping"
SKIPPED+=("network")
fi
# ---------------------------------------------------------------------------
# SECTION 3: /etc/hosts
# ---------------------------------------------------------------------------
header "3 / 8 — /etc/hosts"
if [[ -f "$NODE_DIR/network/hosts" ]]; then
if confirm "Restore /etc/hosts?"; then
cp /etc/hosts "/etc/hosts.bak.$(date +%s)"
cp "$NODE_DIR/network/hosts" /etc/hosts
success "/etc/hosts restored"
APPLIED+=("hosts")
else
SKIPPED+=("hosts")
fi
fi
# ---------------------------------------------------------------------------
# SECTION 4: Custom scripts
# ---------------------------------------------------------------------------
header "4 / 8 — Custom Scripts (/usr/local/bin)"
if [[ -d "$NODE_DIR/scripts" ]] && [[ -n "$(ls -A $NODE_DIR/scripts 2>/dev/null)" ]]; then
info "Scripts to restore:"
ls "$NODE_DIR/scripts" | sed 's/^/ /'
if confirm "Restore custom scripts to /usr/local/bin/?"; then
cp "$NODE_DIR/scripts/"* /usr/local/bin/ 2>/dev/null || true
chmod +x /usr/local/bin/*.sh /usr/local/bin/*.py 2>/dev/null || true
success "Scripts restored"
APPLIED+=("scripts")
if [[ "$TARGET" == "pve1" ]]; then
warn "Large binaries NOT restored (ollama, filebrowser, etc.)"
warn "Reinstall manually:"
warn " ollama : curl -fsSL https://ollama.ai/install.sh | sh"
warn " filebrowser: curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash"
fi
else
SKIPPED+=("scripts")
fi
else
info "No custom scripts backed up"
fi
# ---------------------------------------------------------------------------
# SECTION 5: Systemd units
# ---------------------------------------------------------------------------
header "5 / 8 — Systemd Service Units"
if [[ -d "$NODE_DIR/systemd" ]] && [[ -n "$(ls -A $NODE_DIR/systemd 2>/dev/null)" ]]; then
info "Units to restore:"
ls "$NODE_DIR/systemd" | sed 's/^/ /'
if confirm "Restore and enable systemd units?"; then
for unit in "$NODE_DIR/systemd/"*.service; do
[[ -f "$unit" ]] || continue
bname=$(basename "$unit")
cp "$unit" /etc/systemd/system/
systemctl enable "$bname" 2>/dev/null || true
info " Enabled: $bname"
done
systemctl daemon-reload
success "Systemd units restored and enabled"
APPLIED+=("systemd")
warn "Units are ENABLED but not started — start manually after verifying prerequisites"
warn " e.g.: systemctl start jarvis-agent filebrowser"
else
SKIPPED+=("systemd")
fi
fi
# ---------------------------------------------------------------------------
# SECTION 6: JARVIS agent
# ---------------------------------------------------------------------------
header "6 / 8 — JARVIS Monitoring Agent"
if [[ -f "$NODE_DIR/jarvis-agent/config.json" ]]; then
info "Agent config found:"
cat "$NODE_DIR/jarvis-agent/config.json" | sed 's/^/ /'
if confirm "Restore JARVIS agent config?"; then
mkdir -p /opt/jarvis-agent
cp "$NODE_DIR/jarvis-agent/config.json" /opt/jarvis-agent/config.json
# Install agent if not present
if [[ ! -f /opt/jarvis-agent/jarvis-agent.py ]]; then
info "Downloading JARVIS agent..."
curl -sk https://jarvis.orbishosting.com/install-agent.sh | bash -s "$(hostname)" proxmox || \
warn "Auto-install failed — manually install: curl -sk https://jarvis.orbishosting.com/install-agent.sh | bash -s $(hostname) proxmox"
fi
success "JARVIS agent restored"
APPLIED+=("jarvis-agent")
else
SKIPPED+=("jarvis-agent")
fi
fi
# ---------------------------------------------------------------------------
# SECTION 7: Root crontab
# ---------------------------------------------------------------------------
header "7 / 8 — Root Crontab"
if [[ -f "$NODE_DIR/cron/root" ]] && ! grep -q "^# no crontab" "$NODE_DIR/cron/root"; then
info "Cron entries to restore:"
cat "$NODE_DIR/cron/root" | sed 's/^/ /'
if confirm "Restore root crontab?"; then
crontab "$NODE_DIR/cron/root"
success "Crontab restored"
APPLIED+=("crontab")
else
SKIPPED+=("crontab")
fi
else
info "No crontab entries backed up — skipping"
fi
# ---------------------------------------------------------------------------
# SECTION 8: PVE cluster configs (pve1 only — pve2 joins cluster instead)
# ---------------------------------------------------------------------------
header "8 / 8 — PVE Cluster / VM Configs"
if [[ "$TARGET" == "pve1" ]] && [[ -d "$SHARED_DIR" ]]; then
echo ""
warn "PVE cluster configs (storage, VM configs, users, firewall) are"
warn "managed by Proxmox's cluster filesystem (/etc/pve/)."
warn ""
warn "IMPORTANT: Do NOT copy /etc/pve/ files directly on a fresh install."
warn "Proxmox will auto-populate these when the node is online."
warn ""
info "Instead, after Proxmox is installed and running:"
info " 1. Restore VMs from Proxmox Backup Server (PBS) at 10.48.200.85"
info " → In PVE web UI: Datacenter → Storage → ProxBackups"
info " → PBS fingerprint: $(grep 'fingerprint' $SHARED_DIR/storage.cfg 2>/dev/null | head -1 | tr -d ' \t' || echo 'see shared/storage.cfg')"
info ""
info " 2. Re-add storage manually if PBS isn't auto-discovered:"
info " → Datacenter → Storage → Add → Proxmox Backup Server"
info ""
info " 3. VM configs for reference (not to copy directly):"
[[ -d "$SHARED_DIR/nodes/pve/qemu-server" ]] && ls "$SHARED_DIR/nodes/pve/qemu-server/" | sed 's/^/ /'
echo ""
if confirm "Copy VM .conf files to /etc/pve/nodes/pve/qemu-server/ (only do this if cluster is NOT active)?"; then
mkdir -p /etc/pve/nodes/pve/qemu-server /etc/pve/nodes/pve/lxc
cp "$SHARED_DIR/nodes/pve/qemu-server/"*.conf /etc/pve/nodes/pve/qemu-server/ 2>/dev/null || true
cp "$SHARED_DIR/nodes/pve/lxc/"*.conf /etc/pve/nodes/pve/lxc/ 2>/dev/null || true
cp "$SHARED_DIR/storage.cfg" /etc/pve/ 2>/dev/null || true
cp "$SHARED_DIR/datacenter.cfg" /etc/pve/ 2>/dev/null || true
success "PVE configs copied — restart pve-cluster to apply"
systemctl restart pve-cluster 2>/dev/null || true
APPLIED+=("pve-configs")
else
SKIPPED+=("pve-configs")
info "Skipped — restore VMs from PBS backup instead (recommended)"
fi
elif [[ "$TARGET" == "pve2" ]]; then
warn "PVE2 does not hold the primary cluster config."
info "To restore PVE2 as a cluster member:"
info " 1. Fresh Proxmox install with hostname 'pve2' and IP 10.48.200.91"
info " 2. On PVE1: pvecm add 10.48.200.91"
info " 3. PVE2 will auto-sync all VM configs and cluster state from PVE1"
info " 4. VM 106 and 302 will reappear automatically"
SKIPPED+=("pve-configs")
fi
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
header "Restore Complete"
echo ""
if [[ ${#APPLIED[@]} -gt 0 ]]; then
success "Applied sections: ${APPLIED[*]}"
fi
if [[ ${#SKIPPED[@]} -gt 0 ]]; then
warn "Skipped sections: ${SKIPPED[*]}"
fi
echo ""
echo -e "${YELLOW} Next steps:${NC}"
if [[ " ${APPLIED[*]} " =~ " network " ]]; then
echo " • REBOOT to apply network changes: reboot"
fi
echo " • Verify Proxmox web UI at https://$EXPECTED_IP:8006"
echo " • Restore VMs from PBS backup server at https://10.48.200.85:8007"
echo " • Start JARVIS agent: systemctl start jarvis-agent"
if [[ "$TARGET" == "pve1" ]]; then
echo " • Reinstall ollama if needed: curl -fsSL https://ollama.ai/install.sh | sh"
echo " • Reinstall filebrowser: curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash"
fi
echo ""