mirror of
https://github.com/myronblair/proxmox-config
synced 2026-06-30 15:59:57 -05:00
04fc9941ff
- 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>
304 lines
13 KiB
Bash
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 ""
|