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>
162 lines
6.8 KiB
Bash
162 lines
6.8 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# Proxmox Config Backup — runs on PVE1 or PVE2
|
|
# Backs up all critical configs to GitHub weekly
|
|
# Install: /usr/local/bin/proxmox-backup
|
|
# Cron: 0 3 * * 0 /usr/local/bin/proxmox-backup >> /var/log/proxmox-backup.log 2>&1
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
PAT="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"
|
|
REPO_URL="https://${PAT}@github.com/myronblair/proxmox-config.git"
|
|
REPO_DIR="/opt/proxmox-config"
|
|
NODE=$(hostname)
|
|
LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')] [$NODE]"
|
|
|
|
log() { echo "$LOG_PREFIX $*"; }
|
|
die() { echo "$LOG_PREFIX ERROR: $*" >&2; exit 1; }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Clone or update local repo
|
|
# ---------------------------------------------------------------------------
|
|
if [[ -d "$REPO_DIR/.git" ]]; then
|
|
log "Pulling latest from GitHub"
|
|
cd "$REPO_DIR"
|
|
git config user.email "proxmox-backup@orbishosting.com"
|
|
git config user.name "Proxmox Backup"
|
|
git pull --rebase origin main -q || { log "Pull failed — continuing with local state"; true; }
|
|
else
|
|
log "Cloning repo to $REPO_DIR"
|
|
git clone "$REPO_URL" "$REPO_DIR"
|
|
cd "$REPO_DIR"
|
|
git config user.email "proxmox-backup@orbishosting.com"
|
|
git config user.name "Proxmox Backup"
|
|
fi
|
|
|
|
cd "$REPO_DIR"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Node-specific directories
|
|
# ---------------------------------------------------------------------------
|
|
NODE_DIR="$REPO_DIR/$NODE"
|
|
mkdir -p \
|
|
"$NODE_DIR/network" \
|
|
"$NODE_DIR/cron" \
|
|
"$NODE_DIR/scripts" \
|
|
"$NODE_DIR/systemd" \
|
|
"$NODE_DIR/jarvis-agent"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Shared PVE cluster configs (only back up from primary node to avoid
|
|
# conflicts — PVE1/pve is the primary; pve2 skips this section)
|
|
# ---------------------------------------------------------------------------
|
|
if [[ "$NODE" == "pve" ]]; then
|
|
log "Backing up shared PVE cluster configs"
|
|
SHARED_DIR="$REPO_DIR/shared"
|
|
mkdir -p \
|
|
"$SHARED_DIR/firewall" \
|
|
"$SHARED_DIR/ha" \
|
|
"$SHARED_DIR/sdn" \
|
|
"$SHARED_DIR/nodes/pve/qemu-server" \
|
|
"$SHARED_DIR/nodes/pve/lxc" \
|
|
"$SHARED_DIR/nodes/pve2/qemu-server" \
|
|
"$SHARED_DIR/nodes/pve2/lxc"
|
|
|
|
# Top-level cluster configs
|
|
for f in storage.cfg datacenter.cfg user.cfg corosync.conf vzdump.cron jobs.cfg ceph.conf replication.cfg; do
|
|
[[ -f "/etc/pve/$f" ]] && cp "/etc/pve/$f" "$SHARED_DIR/$f" || true
|
|
done
|
|
|
|
# Firewall
|
|
[[ -d /etc/pve/firewall ]] && rsync -a --delete /etc/pve/firewall/ "$SHARED_DIR/firewall/" 2>/dev/null || true
|
|
|
|
# HA
|
|
[[ -d /etc/pve/ha ]] && rsync -a --delete /etc/pve/ha/ "$SHARED_DIR/ha/" 2>/dev/null || true
|
|
|
|
# SDN
|
|
[[ -d /etc/pve/sdn ]] && rsync -a --delete /etc/pve/sdn/ "$SHARED_DIR/sdn/" 2>/dev/null || true
|
|
|
|
# VM/LXC configs (per-node)
|
|
for PVENODE in pve pve2; do
|
|
[[ -d "/etc/pve/nodes/$PVENODE/qemu-server" ]] && \
|
|
rsync -a --delete /etc/pve/nodes/$PVENODE/qemu-server/ "$SHARED_DIR/nodes/$PVENODE/qemu-server/" 2>/dev/null || true
|
|
[[ -d "/etc/pve/nodes/$PVENODE/lxc" ]] && \
|
|
rsync -a --delete /etc/pve/nodes/$PVENODE/lxc/ "$SHARED_DIR/nodes/$PVENODE/lxc/" 2>/dev/null || true
|
|
done
|
|
|
|
# PBS fingerprint (needed for restore)
|
|
grep -A5 "pbs: ProxBackups" /etc/pve/storage.cfg > "$SHARED_DIR/pbs_fingerprint.txt" 2>/dev/null || true
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Network config
|
|
# ---------------------------------------------------------------------------
|
|
log "Backing up network config"
|
|
cp /etc/network/interfaces "$NODE_DIR/network/interfaces"
|
|
[[ -d /etc/network/interfaces.d ]] && \
|
|
rsync -a --delete /etc/network/interfaces.d/ "$NODE_DIR/network/interfaces.d/" 2>/dev/null || true
|
|
cp /etc/hosts "$NODE_DIR/network/hosts"
|
|
cp /etc/hostname "$NODE_DIR/network/hostname"
|
|
[[ -f /etc/resolv.conf ]] && cp /etc/resolv.conf "$NODE_DIR/network/resolv.conf" || true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Root crontab
|
|
# ---------------------------------------------------------------------------
|
|
log "Backing up crontab"
|
|
crontab -l 2>/dev/null > "$NODE_DIR/cron/root" || echo "# no crontab" > "$NODE_DIR/cron/root"
|
|
[[ -f /etc/vzdump.conf ]] && cp /etc/vzdump.conf "$NODE_DIR/vzdump.conf" || true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. Custom scripts (text only — skip large binaries)
|
|
# ---------------------------------------------------------------------------
|
|
log "Backing up custom scripts"
|
|
for f in /usr/local/bin/*.sh /usr/local/bin/*.py /usr/local/bin/*.pl; do
|
|
[[ -f "$f" ]] || continue
|
|
size=$(stat -c%s "$f" 2>/dev/null || echo 0)
|
|
if [[ $size -lt 524288 ]]; then # skip files > 512KB (binaries)
|
|
cp "$f" "$NODE_DIR/scripts/"
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. Custom systemd units (only non-stock units)
|
|
# ---------------------------------------------------------------------------
|
|
log "Backing up custom systemd units"
|
|
STOCK_UNITS="chronyd.service iscsi.service pve-manager.service smartd.service sshd.service zed.service"
|
|
for f in /etc/systemd/system/*.service; do
|
|
[[ -f "$f" ]] || continue
|
|
bname=$(basename "$f")
|
|
if ! echo "$STOCK_UNITS" | grep -qw "$bname"; then
|
|
cp "$f" "$NODE_DIR/systemd/"
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 8. JARVIS agent config
|
|
# ---------------------------------------------------------------------------
|
|
log "Backing up JARVIS agent config"
|
|
[[ -f /opt/jarvis-agent/config.json ]] && \
|
|
cp /opt/jarvis-agent/config.json "$NODE_DIR/jarvis-agent/config.json" || true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 9. SSH authorized_keys (for access restoration — no private keys)
|
|
# ---------------------------------------------------------------------------
|
|
mkdir -p "$NODE_DIR/ssh"
|
|
[[ -f /root/.ssh/authorized_keys ]] && cp /root/.ssh/authorized_keys "$NODE_DIR/ssh/authorized_keys" || true
|
|
[[ -f /root/.ssh/id_rsa.pub ]] && cp /root/.ssh/id_rsa.pub "$NODE_DIR/ssh/id_rsa.pub" || true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 10. Commit and push
|
|
# ---------------------------------------------------------------------------
|
|
log "Committing changes"
|
|
git add -A
|
|
if git diff --cached --quiet; then
|
|
log "No changes to commit"
|
|
else
|
|
CHANGES=$(git diff --cached --stat | tail -1)
|
|
git commit -m "[$NODE] Weekly backup $(date '+%Y-%m-%d') — $CHANGES"
|
|
log "Pushing to GitHub"
|
|
git push origin main
|
|
log "Backup complete"
|
|
fi
|