feat: extract deployment config into configs/ for multi-server reuse

Both borg-backup.sh and restore.sh now source a config file instead of
hardcoding paths and container names. Default is backup.conf next to the
script; override with BACKUP_CONF=/path/to/other.conf.

- configs/srv.conf     — current server (unchanged behaviour)
- configs/nexusvoice.conf — nexusvoice server (/home/acid/nexusvoice,
                            separate Scaleway path par-backup-1/nexusvoice)
- DB steps in borg-backup.sh are now guarded on [[ -n "$DB_CONTAINER" ]]
  so a config with DB_CONTAINER="" skips the dump entirely
- RUNBOOK.md §2 documents single and multi-deployment patterns

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A3rQSEidP6Y61kaCtxjVV1
This commit is contained in:
2026-08-30 23:45:10 +01:00
co-authored by Claude Sonnet 4.6
parent bfafb09ee6
commit 74390b78af
5 changed files with 161 additions and 65 deletions
+46 -50
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# =============================================================================
# Borg backup: /content + MariaDB -> local repo -> Scaleway S3 mirror
# Borg backup: $TARGET + MariaDB -> local borg repo -> Scaleway S3 mirror
# =============================================================================
# CHANGES vs. original:
# • Encryption enabled (repokey-blake2 via BORG_PASSCOMMAND)
@@ -12,6 +12,7 @@
# excluded from the archive (drop a .nobackup file in it - see
# --exclude-if-present below) so no live InnoDB file is ever copied.
# Zero DB downtime during backup.
# • Config split into per-deployment backup.conf (see configs/)
# =============================================================================
set -euo pipefail
@@ -20,41 +21,28 @@ export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
umask 077
# ========================= CONFIGURATION =========================
# All deployment-specific constants live in a config file.
# Default: backup.conf next to this script.
# Override: BACKUP_CONF=/path/to/other.conf borg-backup.sh
# See configs/ for per-deployment examples.
NAME="borg-2025"
REPO="/home/srv/files/backups/$NAME"
TARGET="/home/srv/files/content"
_SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
BACKUP_CONF="${BACKUP_CONF:-${_SCRIPT_DIR}/backup.conf}"
[[ -r "$BACKUP_CONF" ]] || { echo "ERROR: config not found: $BACKUP_CONF"; exit 1; }
# shellcheck source=/dev/null
source "$BACKUP_CONF"
# dump_db.sh lives with the rest of this toolkit, not inside $TARGET - its
# own default dump dir is relative to wherever IT lives, so DUMP_DIR must be
# passed explicitly (below) to keep dumps inside $TARGET where borg can see them.
DUMP_SCRIPT="/opt/backup-agent/dump_db.sh"
export DUMP_DIR="${TARGET}/mariadb/dump"
# Derived values (override in config only if you need a non-standard layout)
# dump_db.sh lives with this toolkit; DUMP_DIR is passed explicitly so dumps
# land inside $TARGET where borg can see them.
export DUMP_DIR="${DUMP_DIR:-${TARGET}/${DUMP_SUBDIR}}"
REPO_MOUNT=""
LOGDIR="/var/log/borg"
LOG_RETENTION_DAYS=90
RCLONE_REMOTE="scaleway"
RCLONE_PATH="par-backup-1/$NAME"
RCLONE_MAX_DELETE=200
LOCKFILE="/var/lock/borg-backup.lock"
DB_CONTAINER="mariadb"
DB_START_TIMEOUT=180
CREATE_TIMEOUT="6h"
PRUNE_TIMEOUT="2h"
SYNC_TIMEOUT="12h"
CHECK_TIMEOUT="4h"
# Passphrase file: chmod 600, owned by the backup user.
# Create it with: echo 'your-strong-passphrase' > /root/.borg-passphrase
BORG_PASSPHRASE_FILE="${BORG_PASSPHRASE_FILE:-/root/.borg-passphrase}"
REQUIRED_CMDS=(borg rclone docker timeout flock date find)
# docker is only required when DB_CONTAINER is set
if [[ -n "${DB_CONTAINER:-}" ]]; then
REQUIRED_CMDS=(borg rclone docker timeout flock date find)
else
REQUIRED_CMDS=(borg rclone timeout flock date find)
fi
# =================================================================
@@ -128,7 +116,7 @@ cleanup() {
# We never stop the container ourselves anymore, but if it crashed for
# an unrelated reason during the backup window, try to bring it back.
if ! container_running "$DB_CONTAINER"; then
if [[ -n "${DB_CONTAINER:-}" ]] && ! container_running "$DB_CONTAINER"; then
step "Cleanup: $DB_CONTAINER is down, attempting restart"
start_db || { log "CRITICAL: $DB_CONTAINER is DOWN - manual action required"
[ "$exit_code" -eq 0 ] && exit_code=1; }
@@ -192,8 +180,10 @@ preflight() {
return 1
fi
container_running "$DB_CONTAINER" \
|| { log "ERROR: $DB_CONTAINER is not running before we start"; return 1; }
if [[ -n "${DB_CONTAINER:-}" ]]; then
container_running "$DB_CONTAINER" \
|| { log "ERROR: $DB_CONTAINER is not running before we start"; return 1; }
fi
log "Preflight OK"
}
@@ -224,20 +214,24 @@ preflight
# --- 1. Logical dumps (container stays up the whole time) ---------------
step "Step 1: MariaDB dumps"
if [[ ! -x "$DUMP_SCRIPT" ]]; then
log "ERROR: dump script missing or not executable: $DUMP_SCRIPT"
exit 1
fi
run_cmd "$DUMP_SCRIPT"
if [[ -n "${DB_CONTAINER:-}" ]]; then
if [[ ! -x "$DUMP_SCRIPT" ]]; then
log "ERROR: dump script missing or not executable: $DUMP_SCRIPT"
exit 1
fi
run_cmd "$DUMP_SCRIPT"
if [[ -d "$DUMP_DIR" ]]; then
fresh=$(find "$DUMP_DIR" -type f -size +1k -mmin -60 | wc -l)
empty=$(find "$DUMP_DIR" -type f -size -1k -mmin -60 | wc -l)
log "Dumps: $fresh fresh non-trivial file(s), $empty suspiciously small"
(( fresh > 0 )) || { log "ERROR: no usable dumps produced"; exit 1; }
(( empty == 0 )) || log "WARNING: $empty near-empty dump file(s) - check $DUMP_DIR"
if [[ -d "$DUMP_DIR" ]]; then
fresh=$(find "$DUMP_DIR" -type f -size +1k -mmin -60 | wc -l)
empty=$(find "$DUMP_DIR" -type f -size -1k -mmin -60 | wc -l)
log "Dumps: $fresh fresh non-trivial file(s), $empty suspiciously small"
(( fresh > 0 )) || { log "ERROR: no usable dumps produced"; exit 1; }
(( empty == 0 )) || log "WARNING: $empty near-empty dump file(s) - check $DUMP_DIR"
else
log "WARNING: dump directory not found: $DUMP_DIR"
fi
else
log "WARNING: dump directory not found: $DUMP_DIR"
log "DB_CONTAINER not set - skipping database dump"
fi
# --- 2. Create the archive -----------------------------------------------
@@ -248,8 +242,10 @@ fi
# are excluded too - they churn on every request, so backing them up adds
# noise and dedup overhead for no recovery value.
step "Step 2: Creating archive $ARCHIVE"
container_running "$DB_CONTAINER" \
|| { log "ERROR: $DB_CONTAINER is not running - refusing to archive"; exit 1; }
if [[ -n "${DB_CONTAINER:-}" ]]; then
container_running "$DB_CONTAINER" \
|| { log "ERROR: $DB_CONTAINER is not running - refusing to archive"; exit 1; }
fi
run_cmd timeout --signal=INT --kill-after=120s "$CREATE_TIMEOUT" \
borg create \
--lock-wait 600 \