From dbc87be2bd04ffb822539c95356f8d7ce0a22146 Mon Sep 17 00:00:00 2001 From: D Stephenson Date: Tue, 5 May 2026 20:34:14 +0000 Subject: [PATCH] Add backup script and redeployment guide --- REDEPLOY.md | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++ backup.sh | 153 +++++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 REDEPLOY.md create mode 100755 backup.sh diff --git a/REDEPLOY.md b/REDEPLOY.md new file mode 100644 index 0000000..83fc228 --- /dev/null +++ b/REDEPLOY.md @@ -0,0 +1,214 @@ +# VM Redeployment Guide + +This guide covers rebuilding all apps from scratch on a new VM after a total loss of `192.168.16.130`. + +**What you need before starting:** +- Access to your Gitea server (`git.yoda.ddnsgeek.com`) +- The latest backup archive from `/opt/backups/vm-apps/` on the VPS +- The backup encryption password (if you set one) +- A fresh Ubuntu VM + +--- + +## Step 1 — Prepare the new VM + +```bash +# Install Docker +sudo apt-get update +sudo apt-get install -y docker.io docker-compose-plugin +sudo systemctl enable --now docker +sudo usermod -aG docker $USER +# Log out and back in after this +``` + +```bash +# Install Git +sudo apt-get install -y git + +# Create app directories +sudo mkdir -p /opt/rv50x-manager /opt/switch-config-manager /opt/lldp-mapper /opt/stunnel +sudo chown $USER:$USER /opt/rv50x-manager /opt/switch-config-manager /opt/lldp-mapper /opt/stunnel +``` + +--- + +## Step 2 — Set up SSH key for Gitea + +```bash +ssh-keygen -t ed25519 -C "new-vm" -f ~/.ssh/id_ed25519 -N "" +cat ~/.ssh/id_ed25519.pub +``` + +Add the public key to Gitea: `https://git.yoda.ddnsgeek.com/user/settings/keys` + +Add the SSH config: +```bash +cat >> ~/.ssh/config << 'EOF' +Host git.yoda.ddnsgeek.com + HostName git.yoda.ddnsgeek.com + User git + Port 2222 + IdentityFile ~/.ssh/id_ed25519 +EOF +``` + +Test: +```bash +ssh -T git@git.yoda.ddnsgeek.com +# Should print: Hi there, dcstephenson! You've successfully authenticated... +``` + +--- + +## Step 3 — Clone all repositories + +```bash +cd /opt/rv50x-manager +git clone git@git.yoda.ddnsgeek.com:dcstephenson/rv50x-manager.git . + +cd /opt/switch-config-manager +git clone git@git.yoda.ddnsgeek.com:dcstephenson/switch-config-manager.git . + +cd /opt/lldp-mapper +git clone git@git.yoda.ddnsgeek.com:dcstephenson/lldp-mapper.git . + +cd /opt/stunnel +git clone git@git.yoda.ddnsgeek.com:dcstephenson/stunnel.git . +``` + +--- + +## Step 4 — Restore from backup + +Copy the latest backup archive from the VPS: + +```bash +scp root@git.yoda.ddnsgeek.com:/opt/backups/vm-apps/vm-apps-backup_YYYYMMDD_HHMMSS.tar.gz.enc /tmp/ +``` + +Decrypt and extract: +```bash +# If encrypted: +openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 \ + -in /tmp/vm-apps-backup_YYYYMMDD_HHMMSS.tar.gz.enc \ + -out /tmp/vm-apps-backup.tar.gz \ + -pass pass:"YOUR_BACKUP_PASSWORD" + +# Extract +mkdir /tmp/restore +tar -xzf /tmp/vm-apps-backup.tar.gz -C /tmp/restore +``` + +Restore secrets: +```bash +cp /tmp/restore/secrets/rv50x-manager.env /opt/rv50x-manager/.env +cp /tmp/restore/secrets/switch-config-manager-config.py /opt/switch-config-manager/config.py +cp /tmp/restore/secrets/lldp-mapper-config.py /opt/lldp-mapper/config.py +mkdir -p /opt/rv50x-manager/certs +cp /tmp/restore/secrets/cert.pem /opt/rv50x-manager/certs/ +cp /tmp/restore/secrets/key.pem /opt/rv50x-manager/certs/ +``` + +Restore rv50x-manager runtime data: +```bash +cp /tmp/restore/rv50x-manager/rv50x.db /opt/rv50x-manager/ +cp /tmp/restore/rv50x-manager/at_presets.json /opt/rv50x-manager/ +cp -r /tmp/restore/rv50x-manager/xml_templates /opt/rv50x-manager/ +``` + +Restore switch configs: +```bash +cp -r /tmp/restore/switch-config-manager/configs /opt/switch-config-manager/ +``` + +Restore lldp-mapper topology data: +```bash +cp -r /tmp/restore/lldp-mapper/data /opt/lldp-mapper/ +``` + +--- + +## Step 5 — Start the rv50x stack and restore NocoDB database + +```bash +cd /opt/rv50x-manager + +# Start postgres and nocodb only (not the app yet) +docker compose up -d postgres +# Wait for postgres to be healthy +docker compose up -d nocodb +``` + +Restore the PostgreSQL dump: +```bash +docker exec -i rv50x-postgres psql -U nocodb nocodb < /tmp/restore/rv50x-manager/nocodb_postgres.sql +``` + +Start the full stack: +```bash +docker compose up -d +``` + +Verify: +```bash +docker compose ps +# All three containers should be running/healthy +``` + +--- + +## Step 6 — Start remaining apps + +```bash +cd /opt/switch-config-manager +docker compose up -d --build + +cd /opt/lldp-mapper +docker compose up -d --build + +cd /opt/stunnel +docker compose up -d --build +``` + +--- + +## Step 7 — Verify everything is working + +| App | URL | +|-----|-----| +| rv50x-manager | `http://NEW-VM-IP:8002` | +| rv50x-nocodb | `http://NEW-VM-IP:8090` | +| switch-config-manager | `http://NEW-VM-IP:8003` | +| lldp-mapper | `http://NEW-VM-IP:5000` | +| stunnel | `https://NEW-VM-IP:4500` (through to first switch) | + +Check rv50x-manager loads devices from NocoDB — if devices appear, the Postgres restore worked. + +--- + +## Step 8 — Clean up + +```bash +rm -rf /tmp/restore /tmp/vm-apps-backup* +``` + +--- + +## Backup location on VPS + +Backups are stored at: +``` +root@git.yoda.ddnsgeek.com:/opt/backups/vm-apps/ +``` + +The last 7 backups are kept. To list them: +```bash +ssh root@git.yoda.ddnsgeek.com "ls -lht /opt/backups/vm-apps/" +``` + +## Running a backup + +On this VM: +```bash +/opt/backup.sh +``` diff --git a/backup.sh b/backup.sh new file mode 100755 index 0000000..1d948d4 --- /dev/null +++ b/backup.sh @@ -0,0 +1,153 @@ +#!/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="git.yoda.ddnsgeek.com" +VPS_PORT="22" +VPS_USER="root" +VPS_BACKUP_DIR="/opt/backups/vm-apps" +KEEP_BACKUPS=7 + +BACKUP_PASSWORD="" # Set this to encrypt the archive, or leave blank to skip encryption +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" + +# ── Prompt for backup password if not set ──────────────────────────────────── + +if [ -z "$BACKUP_PASSWORD" ]; then + echo "" + read -rsp "Enter backup encryption password (leave blank to skip encryption): " BACKUP_PASSWORD + echo "" +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 (optional) ─────────────────────────────────────────────────────── + +if [ -n "$BACKUP_PASSWORD" ]; then + 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" +else + warn "No password set — archive will NOT be encrypted" + UPLOAD_FILE="$WORK_DIR/$ARCHIVE_NAME" + UPLOAD_NAME="$ARCHIVE_NAME" +fi + +# ── 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 ""