diff --git a/README.md b/README.md index 0d506e9..b8470c0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ This script automates encrypted, deduplicated backups of local directories using - Checks the latest Restic release, downloads checksums and PGP signature. - Verifies the signature and checksum before installing (x86_64 and aarch64 supported). - **Optional script self-update (interactive)**: Checks GitHub release, downloads, verifies checksum, and updates the script. +- **3-2-1 backup support**: Supports 3-2-1 backup architecture via the `--fallback` flag. ----- @@ -102,6 +103,7 @@ For those familiar with setting up backup scripts, here is a fast track to get y - `sudo ./restic-backup.sh --ls [snapshot_id] [path ...]` — List files/dirs within a snapshot (paged with `less`). - `sudo ./restic-backup.sh --find ` - Search for files/dirs across all snapshots (e.g., --find \"*.log\" -l). - `sudo ./restic-backup.sh --recovery-kit` - Generate a self-contained recovery script (with embedded password). +- `sudo ./restic-backup.sh --fallback` - Temporarily route command to the fallback repository. Tip: `--verbose` is interactive; cron should use the default quiet mode. The script auto-reexecs with sudo if not run as root. @@ -177,6 +179,93 @@ sudo ./restic-backup.sh --sync-restore latest /srv/app/data && systemctl restart ----- +## 3-2-1 Backup Strategy & Fallback Mode + +This script supports 3-2-1 backup architecture via the `--fallback` flag. + +If your primary cloud repository (e.g., a Hetzner Storage Box or AWS S3) becomes unreachable, you can instantly route any read/restore command to a secondary mirrored repository (e.g., a local NAS) without altering your core configuration. + +### Prerequisite & Core Assumption + +**This script does not automatically replicate or synchronize your data between different storage backends.** To utilize Fallback Mode, **it is assumed that you have independently configured an automated synchronization or mirroring task** (using tools like `rsync`, `rclone`, or your storage platform's native replication tools) that pulls or pushes a **1:1 exact copy** of your primary Restic repository directory to your local NAS or alternative remote server. + +Because Restic repositories are completely self-contained and portable, this mirrored directory is immediately readable as a valid repository by the script. + +### How it Works + +1. **The Mirror:** Your external sync task (e.g., a TrueNAS Rsync Pull task) runs on its own schedule to mirror your primary repository (like a Hetzner Storage Box) to a secondary location (like a home server). +2. **The Config:** Define your secondary mirrored repository URL in `restic-backup.conf`: + + ```bash + RESTIC_FALLBACK_REPOSITORY="sftp:local-nas:/mnt/backups/server_01" + # Optional: Set RESTIC_FALLBACK_PASSWORD_FILE if the secondary uses a different key + ``` + +3. **The Execution:** Append `--fallback` to any operational command. The script will dynamically swap the environment variables and route the traffic to your mirror. + +### Fallback Use Cases + +The fallback flag safely works with all non-destructive commands. It is highly recommended to use it for restores if your primary connection is degraded or severed: + +```sh +# Check the integrity of your local mirrored copy +sudo ./restic-backup.sh --fallback --check + +# Browse the files inside your local mirror +sudo ./restic-backup.sh --fallback --ls latest + +# Execute an interactive restore directly from the local mirror +sudo ./restic-backup.sh --fallback --restore +``` + +> **Warning:** Do not run standard backup jobs or `--forget` operations using the `--fallback` flag if your secondary repository is maintained by a one-way sync (like Rsync). Writing new snapshots directly to a synchronized mirror can cause merge conflicts or data loss during the next sync cycle. Use fallback strictly for recovery and verification. + +### Setting Up the Secure Fallback Connection + +To securely connect a cloud VPS to a home server or NAS (like TrueNAS), **we highly recommend using an Overlay Network / Mesh VPN** (such as Tailscale or NetBird). This assigns a private, static IP to your NAS and allows the VPS to bypass NAT and firewalls without exposing your home SSH port to the public internet. + +To configure this secure connection for Restic's fallback mode, follow these steps on your VPS: + +**1. Generate and Copy SSH Keys** +Restic requires passwordless authentication to run automatically. Generate an SSH key on your VPS (if you don't already have one) and copy the public key to your secondary backup server. + +```sh +# Generate a key as root (Press Enter for no passphrase) +sudo ssh-keygen -t ed25519 -f /root/.ssh/id_fallback_backup + +# Copy the PUBLIC key to your NAS/TrueNAS admin user +# (Replace with your NAS user and Mesh VPN IP) +sudo ssh-copy-id -i /root/.ssh/id_fallback_backup.pub truenas_admin@100.x.x.x +``` + +**2. Configure the SSH Alias** +Create or edit the SSH config file for the root user to define exactly how the VPS should connect to the NAS. This alias (`local-nas`) is what you will use in your `RESTIC_FALLBACK_REPOSITORY` variable. + +```sh +sudo nano /root/.ssh/config +``` + +Add the following block, adjusting the IP and user for your specific Mesh VPN setup: + +```text +Host local-nas + HostName 100.x.x.x # Your Tailscale/NetBird IP + User truenas_admin # The user on your NAS + IdentityFile /root/.ssh/id_fallback_backup + StrictHostKeyChecking accept-new +``` + +**3. Test the Connection** +Verify that your VPS can successfully tunnel to the NAS without prompting for a password. + +```sh +sudo ssh local-nas pwd +``` + +If this command successfully prints the working directory on your NAS, your secure pipeline is established. You can now set `RESTIC_FALLBACK_REPOSITORY="sftp:local-nas:/path/to/backup"` in your configuration file. + +----- + ## Diagnostics & Exit Codes The script uses specific exit codes for different failures to help with debugging automated runs. diff --git a/restic-backup.conf b/restic-backup.conf index 65fa605..36e75e1 100644 --- a/restic-backup.conf +++ b/restic-backup.conf @@ -148,3 +148,12 @@ EXCLUDE_FILE="/etc/restic-excludes.txt" # Additional exclude patterns (space-separated) EXCLUDE_PATTERNS="*.tmp *.log *.cache .DS_Store Thumbs.db" + +# --- Fallback Repository (3-2-1 Backup Strategy) --- +# Use the --fallback flag to route commands to a secondary mirrored repository. +# Ideal for restoring from a local TrueNAS/NAS pull if the primary cloud repo is unreachable. +RESTIC_FALLBACK_REPOSITORY="sftp:truenas-backup:/mnt/HDDs/Hatzner_VPS_Backups/home/i3vps" + +# Optional: If your secondary mirror uses a different restic password, specify it here. +# Leave blank to automatically use the primary RESTIC_PASSWORD_FILE. +RESTIC_FALLBACK_PASSWORD_FILE="" diff --git a/restic-backup.sh b/restic-backup.sh index d765688..ef9a440 100644 --- a/restic-backup.sh +++ b/restic-backup.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # ================================================================= -# Restic Backup Script v0.45 - 2026.05.09 +# Restic Backup Script v0.46 - 2026.05.24 # ================================================================= export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH @@ -9,7 +9,7 @@ set -euo pipefail umask 077 # --- Script Constants --- -SCRIPT_VERSION="0.45" +SCRIPT_VERSION="0.46" SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) PROG_NAME=$(basename "$0"); readonly PROG_NAME CONFIG_FILE="${SCRIPT_DIR}/restic-backup.conf" @@ -46,6 +46,7 @@ display_help() { echo echo -e "${C_BOLD}${C_YELLOW}OPTIONS:${C_RESET}" printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--verbose" "Show detailed live output." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--fallback" "Temporarily route command to the fallback repository." printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--fix-permissions" "Interactive only: auto-fix 600/400 on conf/secret." printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--help, -h" "Display this help message." echo @@ -811,8 +812,24 @@ send_notification() { } setup_environment() { - export RESTIC_REPOSITORY - export RESTIC_PASSWORD_FILE + # Handle Fallback Routing + if [[ "${USE_FALLBACK:-false}" == "true" ]]; then + if [ -z "${RESTIC_FALLBACK_REPOSITORY:-}" ]; then + echo -e "${C_RED}ERROR: --fallback flag used, but RESTIC_FALLBACK_REPOSITORY is not set in config.${C_RESET}" >&2 + exit 1 + fi + export RESTIC_REPOSITORY="${RESTIC_FALLBACK_REPOSITORY}" + if [ -n "${RESTIC_FALLBACK_PASSWORD_FILE:-}" ]; then + export RESTIC_PASSWORD_FILE="${RESTIC_FALLBACK_PASSWORD_FILE}" + else + export RESTIC_PASSWORD_FILE # Fallback to primary password + fi + log_message "FALLBACK MODE ENGAGED: Routing to $RESTIC_REPOSITORY" + echo -e "${C_YELLOW}⚠️ Running in FALLBACK mode. Using alternate repository.${C_RESET}" + else + export RESTIC_REPOSITORY + export RESTIC_PASSWORD_FILE + fi if [ -n "${GOMAXPROCS_LIMIT:-}" ]; then export GOMAXPROCS="${GOMAXPROCS_LIMIT}" @@ -1808,6 +1825,7 @@ EOF # 1. Parse flags. VERBOSE_MODE=false SKIP_OWNERSHIP_FIX=false +USE_FALLBACK=false AUTO_FIX_PERMS=${AUTO_FIX_PERMS:-false} while [[ $# -gt 0 ]]; do case "$1" in @@ -1815,6 +1833,10 @@ while [[ $# -gt 0 ]]; do VERBOSE_MODE=true shift ;; + --fallback) + USE_FALLBACK=true + shift + ;; --exact-ownership) SKIP_OWNERSHIP_FIX=true shift diff --git a/restic-backup.sh.sha256 b/restic-backup.sh.sha256 index bbd72a3..cbbe90f 100644 --- a/restic-backup.sh.sha256 +++ b/restic-backup.sh.sha256 @@ -1 +1 @@ -52a1734972189b3e0509055659678722d58b72eb9baa5a22dbc1b1d207e7c262 restic-backup.sh +b8b5fb61ee330a2352eff74825366050c7ecfc512a0c6a20c306a594883dbfe6 restic-backup.sh