From 49ed87b46b1a95816c1d3aef1403cada56362dbe Mon Sep 17 00:00:00 2001 From: Kevin Bataille Date: Sat, 25 Jul 2026 18:54:01 +0200 Subject: [PATCH] feat: add restore.sh scaffold with arg parsing and usage --- restore.sh | 129 ++++++++++++++++++++++++++++++++++++++++++ tests/test_restore.sh | 59 +++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 restore.sh create mode 100755 tests/test_restore.sh diff --git a/restore.sh b/restore.sh new file mode 100755 index 0000000..57c2594 --- /dev/null +++ b/restore.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# ============================================================================= +# Restore tooling for the borg-backup.sh / dump_db.sh backup system. +# Modes: +# restore.sh full [--archive NAME] [--force] [--dry-run] +# restore.sh db [--archive NAME] [--yes] [--dry-run] +# restore.sh file [--archive NAME] [--dest DIR] [--dry-run] +# restore.sh --list-archives +# ============================================================================= + +set -euo pipefail + +export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" +umask 077 + +# ========================= CONFIGURATION ========================= + +NAME="borg-2025" +REPO="/home/srv/files/backups/$NAME" +TARGET="/home/srv/files/content" +ARCHIVE_TARGET_PATH="${TARGET#/}" + +DB_CONTAINER="mariadb" +DB_START_TIMEOUT=180 + +BORG_PASSPHRASE_FILE="${BORG_PASSPHRASE_FILE:-/root/.borg-passphrase}" +ROOT_PASSWORD_FILE="${ROOT_PASSWORD_FILE:-/root/.mariadb-root.pw}" +DUMP_SUBDIR="mariadb/dump" + +LOGDIR="${RESTORE_LOGDIR:-/var/log/borg}" +mkdir -p "$LOGDIR" 2>/dev/null || LOGDIR="/tmp" +LOGFILE="$LOGDIR/restore-$(date +%Y-%m-%d-%H%M%S).log" + +export BORG_REPO="$REPO" +export BORG_PASSCOMMAND="cat $BORG_PASSPHRASE_FILE" + +# ================================================================= + +log() { + local line + line="[$(date '+%F %T')] $*" + echo "$line" + echo "$line" >> "$LOGFILE" 2>/dev/null || true +} + +step() { echo; echo "=== $* ==="; } + +die() { log "ERROR: $*"; exit 1; } + +run_cmd() { + log "[RUN] $*" + "$@" +} + +usage() { + cat <<'EOF' +Usage: + restore.sh full [--archive NAME] [--force] [--dry-run] + restore.sh db [--archive NAME] [--yes] [--dry-run] + restore.sh file [--archive NAME] [--dest DIR] [--dry-run] + restore.sh --list-archives + restore.sh -h | --help +EOF +} + +DRY_RUN=false +FORCE=false +YES=false +ARCHIVE_OVERRIDE="" +DEST="" + +parse_common_flags() { + while [[ $# -gt 0 ]]; do + case "$1" in + --archive) ARCHIVE_OVERRIDE="$2"; shift 2 ;; + --force) FORCE=true; shift ;; + --yes) YES=true; shift ;; + --dry-run) DRY_RUN=true; shift ;; + --dest) DEST="$2"; shift 2 ;; + *) die "unknown flag: $1" ;; + esac + done +} + +main() { + local cmd="${1:-}" + case "$cmd" in + "") + usage + exit 1 + ;; + -h|--help) + usage + exit 0 + ;; + --list-archives) + shift + parse_common_flags "$@" + list_archives + ;; + full) + shift + parse_common_flags "$@" + cmd_full + ;; + db) + shift + local dbname="${1:-}" + [[ -n "$dbname" ]] || die "db: missing " + shift + parse_common_flags "$@" + cmd_db "$dbname" + ;; + file) + shift + local relpath="${1:-}" + [[ -n "$relpath" ]] || die "file: missing " + shift + parse_common_flags "$@" + cmd_file "$relpath" + ;; + *) + usage + die "unknown command: $cmd" + ;; + esac +} + +main "$@" diff --git a/tests/test_restore.sh b/tests/test_restore.sh new file mode 100755 index 0000000..e618000 --- /dev/null +++ b/tests/test_restore.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -uo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +RESTORE="$HERE/../restore.sh" +FAILURES=0 + +assert_eq() { + local expected="$1" actual="$2" msg="$3" + if [[ "$expected" != "$actual" ]]; then + echo "FAIL: $msg (expected '$expected', got '$actual')" + FAILURES=$((FAILURES + 1)) + else + echo "PASS: $msg" + fi +} + +assert_contains() { + local haystack="$1" needle="$2" msg="$3" + if [[ "$haystack" != *"$needle"* ]]; then + echo "FAIL: $msg (expected to contain '$needle', got '$haystack')" + FAILURES=$((FAILURES + 1)) + else + echo "PASS: $msg" + fi +} + +test_help_exits_zero() { + local out rc + out="$(bash "$RESTORE" -h)" + rc=$? + assert_eq "0" "$rc" "help exits 0" + assert_contains "$out" "Usage:" "help prints usage" +} + +test_no_args_exits_one() { + local rc + bash "$RESTORE" >/dev/null 2>&1 + rc=$? + assert_eq "1" "$rc" "no args exits 1" +} + +test_unknown_command_exits_one() { + local rc + bash "$RESTORE" bogus >/dev/null 2>&1 + rc=$? + assert_eq "1" "$rc" "unknown command exits 1" +} + +test_help_exits_zero +test_no_args_exits_one +test_unknown_command_exits_one + +echo "-----" +if [[ "$FAILURES" -gt 0 ]]; then + echo "$FAILURES failure(s)" + exit 1 +fi +echo "All tests passed"