#!/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 ""