Add backup script and redeployment guide
This commit is contained in:
+214
@@ -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
|
||||||
|
```
|
||||||
@@ -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 ""
|
||||||
Reference in New Issue
Block a user