Files
infrastructure/backup.sh
T
2026-05-05 20:59:22 +00:00

155 lines
6.6 KiB
Bash
Executable File

#!/bin/bash
# backup.sh — Manual backup of all apps on this VM to the Gitea VPS
#
# Usage:
# ./backup.sh
#
# What gets backed up:
# - Secrets (.env, config.py files, SSL certs)
# - NocoDB PostgreSQL database (pg_dump)
# - Runtime data (SQLite DBs, XML templates, saved switch configs,
# AT presets, lldp topology DB)
#
# Output:
# A single encrypted tar archive pushed to the VPS via SCP.
# The VPS keeps the last 7 backups — older ones are pruned automatically.
#
# Requirements:
# - SSH access to the VPS configured (key-based, no password prompt)
# - 'openssl' installed on this VM (standard on Ubuntu)
set -euo pipefail
# ── Configuration ────────────────────────────────────────────────────────────
VPS_HOST="vps.yoda.ddnsgeek.com"
VPS_PORT="22"
VPS_USER="root"
VPS_BACKUP_DIR="/opt/backups/vm-apps"
KEEP_BACKUPS=7
BACKUP_PASSWORD="" # Leave blank to be prompted each run (recommended)
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
ARCHIVE_NAME="vm-apps-backup_${TIMESTAMP}.tar.gz"
ENCRYPTED_NAME="${ARCHIVE_NAME}.enc"
WORK_DIR=$(mktemp -d)
# ── Colours ──────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
section() { echo -e "\n${YELLOW}── $1 ──${NC}"; }
cleanup() { rm -rf "$WORK_DIR"; }
trap cleanup EXIT
# ── Preflight ────────────────────────────────────────────────────────────────
section "Preflight checks"
[ "$(id -u)" -eq 0 ] || warn "Not running as root — pg_dump may fail if postgres socket needs root"
docker ps | grep -q rv50x-postgres || error "rv50x-postgres is not running — start the stack first"
info "Preflight OK"
# ── Require backup password ──────────────────────────────────────────────────
if [ -z "$BACKUP_PASSWORD" ]; then
echo ""
while true; do
read -rsp "Enter backup encryption password: " BACKUP_PASSWORD
echo ""
[ -n "$BACKUP_PASSWORD" ] && break
warn "Password cannot be blank — the archive contains all secrets and must be encrypted"
done
read -rsp "Confirm password: " BACKUP_PASSWORD_CONFIRM
echo ""
[ "$BACKUP_PASSWORD" = "$BACKUP_PASSWORD_CONFIRM" ] || error "Passwords do not match"
fi
# ── Stage files ──────────────────────────────────────────────────────────────
section "Staging files"
STAGE="$WORK_DIR/stage"
mkdir -p "$STAGE"
# Secrets
mkdir -p "$STAGE/secrets"
cp /opt/rv50x-manager/.env "$STAGE/secrets/rv50x-manager.env"
cp /opt/switch-config-manager/config.py "$STAGE/secrets/switch-config-manager-config.py"
cp /opt/lldp-mapper/config.py "$STAGE/secrets/lldp-mapper-config.py"
cp /opt/rv50x-manager/certs/cert.pem "$STAGE/secrets/cert.pem"
cp /opt/rv50x-manager/certs/key.pem "$STAGE/secrets/key.pem"
info "Secrets staged"
# rv50x-manager runtime data
mkdir -p "$STAGE/rv50x-manager"
[ -f /opt/rv50x-manager/rv50x.db ] && cp /opt/rv50x-manager/rv50x.db "$STAGE/rv50x-manager/"
[ -f /opt/rv50x-manager/at_presets.json ] && cp /opt/rv50x-manager/at_presets.json "$STAGE/rv50x-manager/"
[ -d /opt/rv50x-manager/xml_templates ] && cp -r /opt/rv50x-manager/xml_templates "$STAGE/rv50x-manager/"
info "rv50x-manager runtime data staged"
# switch-config-manager saved configs
mkdir -p "$STAGE/switch-config-manager"
[ -d /opt/switch-config-manager/configs ] && cp -r /opt/switch-config-manager/configs "$STAGE/switch-config-manager/"
info "Switch configs staged"
# lldp-mapper topology database
mkdir -p "$STAGE/lldp-mapper"
[ -d /opt/lldp-mapper/data ] && cp -r /opt/lldp-mapper/data "$STAGE/lldp-mapper/"
info "LLDP mapper data staged"
# NocoDB PostgreSQL dump
section "Dumping NocoDB PostgreSQL database"
docker exec rv50x-postgres pg_dump -U nocodb nocodb > "$STAGE/rv50x-manager/nocodb_postgres.sql"
info "PostgreSQL dump complete ($(du -sh "$STAGE/rv50x-manager/nocodb_postgres.sql" | cut -f1))"
# ── Create archive ───────────────────────────────────────────────────────────
section "Creating archive"
tar -czf "$WORK_DIR/$ARCHIVE_NAME" -C "$STAGE" .
ARCHIVE_SIZE=$(du -sh "$WORK_DIR/$ARCHIVE_NAME" | cut -f1)
info "Archive created: $ARCHIVE_NAME ($ARCHIVE_SIZE)"
# ── Encrypt ──────────────────────────────────────────────────────────────────
section "Encrypting archive"
openssl enc -aes-256-cbc -pbkdf2 -iter 100000 \
-in "$WORK_DIR/$ARCHIVE_NAME" \
-out "$WORK_DIR/$ENCRYPTED_NAME" \
-pass pass:"$BACKUP_PASSWORD"
UPLOAD_FILE="$WORK_DIR/$ENCRYPTED_NAME"
UPLOAD_NAME="$ENCRYPTED_NAME"
info "Archive encrypted"
# ── Upload to VPS ────────────────────────────────────────────────────────────
section "Uploading to VPS"
ssh -p "$VPS_PORT" "${VPS_USER}@${VPS_HOST}" "mkdir -p ${VPS_BACKUP_DIR}"
scp -P "$VPS_PORT" "$UPLOAD_FILE" "${VPS_USER}@${VPS_HOST}:${VPS_BACKUP_DIR}/${UPLOAD_NAME}"
info "Uploaded to ${VPS_HOST}:${VPS_BACKUP_DIR}/${UPLOAD_NAME}"
# Prune old backups on the VPS (keep last N)
ssh -p "$VPS_PORT" "${VPS_USER}@${VPS_HOST}" \
"ls -t ${VPS_BACKUP_DIR}/vm-apps-backup_* 2>/dev/null | tail -n +$((KEEP_BACKUPS + 1)) | xargs -r rm --"
info "Old backups pruned (keeping last ${KEEP_BACKUPS})"
# ── Done ─────────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}Backup complete.${NC}"
echo " Archive : ${UPLOAD_NAME}"
echo " Size : ${ARCHIVE_SIZE}"
echo " Location: ${VPS_HOST}:${VPS_BACKUP_DIR}/"
echo ""