Files
proxmox-config/backup.sh
T
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

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