From 8cd00e260a657c55dfc9ae65e7ad35289a9998e7 Mon Sep 17 00:00:00 2001 From: buildplan Date: Wed, 17 Dec 2025 09:10:06 +0000 Subject: [PATCH 1/6] refactor: enable non-root help, safer cleanup, and disk checks - Moved `display_help` to top and added early argument scanning to allow viewing help without requiring `sudo` or triggering dependency checks. - Updated `PATH` export to append to existing system path rather than overwriting it, preserving custom environment paths. - Enhanced `cleanup()` to kill child processes (orphans) upon script exit or interruption. - Added pre-flight check for 500MB free disk space in cache directory to prevent restic crashes. - Removed unreachable `--help` case from main execution block. --- restic-backup.sh | 144 +++++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 62 deletions(-) diff --git a/restic-backup.sh b/restic-backup.sh index 547a99a..90b822e 100644 --- a/restic-backup.sh +++ b/restic-backup.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # ================================================================= -# Restic Backup Script v0.41 - 2025.11.24 +# Restic Backup Script v0.42 - 2025.12.17 # ================================================================= -export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH set -euo pipefail umask 077 @@ -35,6 +35,70 @@ else C_CYAN='' fi +# --- Ensure running as root --- +display_help() { + local readme_url="https://github.com/buildplan/restic-backup-script/blob/main/README.md" + + echo -e "${C_BOLD}${C_CYAN}Restic Backup Script (v${SCRIPT_VERSION})${C_RESET}" + echo "Encrypted, deduplicated backups with restic." + echo + echo -e "${C_BOLD}${C_YELLOW}USAGE:${C_RESET}" + echo -e " sudo $PROG_NAME ${C_GREEN}[options] [command]${C_RESET}" + 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" "--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 + echo -e "${C_BOLD}${C_YELLOW}COMMANDS:${C_RESET}" + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "[no command]" "Run a standard backup and apply the retention policy." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--init" "Initialize a new restic repository (one-time setup)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--diff" "Show a summary of changes between the last two snapshots." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--snapshots" "List all available snapshots in the repository." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--snapshots-delete" "Interactively select and permanently delete snapshots." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--stats" "Display repository size and file counts." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--check" "Verify repository integrity (subset)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--check-full" "Verify all repository data (slow)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--forget" "Apply retention policy; optionally prune." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--unlock" "Remove stale repository locks." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--dump " "Dump a single file from a snapshot to stdout." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--restore" "Interactive restore wizard." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--ls " "List files and directories inside a specific snapshot." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--find " "Search for files/dirs across all snapshots (e.g., --find \"*.log\" -l)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--background-restore" "Run a non-interactive restore in the background." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--sync-restore" "Run a non-interactive restore in the foreground (for cron)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--dry-run" "Preview backup changes (no snapshot)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--test" "Validate config, permissions, connectivity." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--install-scheduler" "Install an automated schedule (systemd/cron)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--recovery-kit" "Generate a self-contained recovery script (with embedded password)." + printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--uninstall-scheduler" "Remove an automated schedule." + echo + echo -e "${C_BOLD}${C_YELLOW}QUICK EXAMPLES:${C_RESET}" + echo -e " Run a backup now: ${C_GREEN}sudo $PROG_NAME${C_RESET}" + echo -e " Verbose diff summary: ${C_GREEN}sudo $PROG_NAME --verbose --diff${C_RESET}" + echo -e " Fix perms (interactive): ${C_GREEN}sudo $PROG_NAME --fix-permissions --test${C_RESET}" + echo -e " Background restore: ${C_GREEN}sudo $PROG_NAME --background-restore latest /mnt/restore${C_RESET}" + echo -e " List snapshot contents: ${C_GREEN}sudo $PROG_NAME --ls latest /path/to/dir${C_RESET}" + echo -e " Find a file everywhere: ${C_GREEN}sudo $PROG_NAME --find \"*.log\" -l${C_RESET}" + echo -e " Dump one file to stdout: ${C_GREEN}sudo $PROG_NAME --dump latest /etc/hosts > hosts.txt${C_RESET}" + echo + echo -e "${C_BOLD}${C_YELLOW}DEPENDENCIES:${C_RESET}" + echo -e " This script requires: ${C_GREEN}restic, curl, gpg, bzip2, less, jq, flock${C_RESET}" + echo + echo -e "Config: ${C_DIM}${CONFIG_FILE}${C_RESET} Log: ${C_DIM}${LOG_FILE:-"(not set)"}${C_RESET}" + echo + echo -e "For full details, see the online documentation: \e]8;;${readme_url}\a${C_CYAN}README.md${C_RESET}\e]8;;\a" + echo -e "${C_YELLOW}Note:${C_RESET} For restic official documentation See: https://restic.readthedocs.io/" + echo +} +# Scan arguments for help flag immediately +for arg in "$@"; do + if [[ "$arg" == "-h" || "$arg" == "--help" ]]; then + display_help + exit 0 + fi +done + # --- Ensure running as root --- if [[ $EUID -ne 0 ]]; then echo -e "${C_BOLD}${C_YELLOW}This script requires root privileges.${C_RESET}" @@ -194,7 +258,7 @@ check_and_install_restic() { echo "Decompressing and installing to /usr/local/bin/restic..." if bunzip2 -c "$temp_binary" > /usr/local/bin/restic.tmp; then chmod +x /usr/local/bin/restic.tmp - mv /usr/local/bin/restic.tmp /usr/local/bin/restic + mv /usr/local/bin/restic.tmp /usr/local/bin/restic if [[ "$IS_SELINUX_DISTRO" == "true" ]] && command -v restorecon &>/dev/null; then echo "Applying SELinux context to binary..." restorecon -v /usr/local/bin/restic || true @@ -296,62 +360,6 @@ done # UTILITY FUNCTIONS # ================================================================= -display_help() { - local readme_url="https://github.com/buildplan/restic-backup-script/blob/main/README.md" - - echo -e "${C_BOLD}${C_CYAN}Restic Backup Script (v${SCRIPT_VERSION})${C_RESET}" - echo "Encrypted, deduplicated backups with restic." - echo - echo -e "${C_BOLD}${C_YELLOW}USAGE:${C_RESET}" - echo -e " sudo $PROG_NAME ${C_GREEN}[options] [command]${C_RESET}" - 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" "--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 - echo -e "${C_BOLD}${C_YELLOW}COMMANDS:${C_RESET}" - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "[no command]" "Run a standard backup and apply the retention policy." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--init" "Initialize a new restic repository (one-time setup)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--diff" "Show a summary of changes between the last two snapshots." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--snapshots" "List all available snapshots in the repository." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--snapshots-delete" "Interactively select and permanently delete snapshots." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--stats" "Display repository size and file counts." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--check" "Verify repository integrity (subset)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--check-full" "Verify all repository data (slow)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--forget" "Apply retention policy; optionally prune." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--unlock" "Remove stale repository locks." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--dump " "Dump a single file from a snapshot to stdout." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--restore" "Interactive restore wizard." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--ls " "List files and directories inside a specific snapshot." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--find " "Search for files/dirs across all snapshots (e.g., --find \"*.log\" -l)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--background-restore" "Run a non-interactive restore in the background." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--sync-restore" "Run a non-interactive restore in the foreground (for cron)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--dry-run" "Preview backup changes (no snapshot)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--test" "Validate config, permissions, connectivity." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--install-scheduler" "Install an automated schedule (systemd/cron)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--recovery-kit" "Generate a self-contained recovery script (with embedded password)." - printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--uninstall-scheduler" "Remove an automated schedule." - echo - echo -e "${C_BOLD}${C_YELLOW}QUICK EXAMPLES:${C_RESET}" - echo -e " Run a backup now: ${C_GREEN}sudo $PROG_NAME${C_RESET}" - echo -e " Verbose diff summary: ${C_GREEN}sudo $PROG_NAME --verbose --diff${C_RESET}" - echo -e " Fix perms (interactive): ${C_GREEN}sudo $PROG_NAME --fix-permissions --test${C_RESET}" - echo -e " Background restore: ${C_GREEN}sudo $PROG_NAME --background-restore latest /mnt/restore${C_RESET}" - echo -e " List snapshot contents: ${C_GREEN}sudo $PROG_NAME --ls latest /path/to/dir${C_RESET}" - echo -e " Find a file everywhere: ${C_GREEN}sudo $PROG_NAME --find \"*.log\" -l${C_RESET}" - echo -e " Dump one file to stdout: ${C_GREEN}sudo $PROG_NAME --dump latest /etc/hosts > hosts.txt${C_RESET}" - echo - echo -e "${C_BOLD}${C_YELLOW}DEPENDENCIES:${C_RESET}" - echo -e " This script requires: ${C_GREEN}restic, curl, gpg, bzip2, less, jq, flock${C_RESET}" - echo - echo -e "Config: ${C_DIM}${CONFIG_FILE}${C_RESET} Log: ${C_DIM}${LOG_FILE:-"(not set)"}${C_RESET}" - echo - echo -e "For full details, see the online documentation: \e]8;;${readme_url}\a${C_CYAN}README.md${C_RESET}\e]8;;\a" - echo -e "${C_YELLOW}Note:${C_RESET} For restic official documentation See: https://restic.readthedocs.io/" - echo -} - detect_distro() { if [ -f /etc/os-release ]; then # shellcheck source=/dev/null @@ -808,6 +816,9 @@ cleanup() { if [ -n "${LOCK_FD:-}" ]; then flock -u "$LOCK_FD" fi + if [ -n "$(jobs -p)" ]; then + pkill -P $$ || true + fi } run_preflight_checks() { @@ -931,6 +942,18 @@ run_preflight_checks() { else if [[ "$verbosity" == "verbose" ]]; then echo -e "[${C_GREEN} OK ${C_RESET}]"; fi fi + # Disk Space Check for restic cache + local check_dir="${RESTIC_CACHE_DIR:-/tmp}" + mkdir -p "$check_dir" 2>/dev/null || true + if command -v df >/dev/null && command -v awk >/dev/null; then + if [[ "$verbosity" == "verbose" ]]; then printf " %-65s" "Free space check ($check_dir)..."; fi + local available_kb + available_kb=$(df -k --output=avail "$check_dir" | tail -n1) + if [[ "$available_kb" -lt 512000 ]]; then + handle_failure "Insufficient free space in $check_dir. Need >500MB, found $((available_kb/1024))MB." "16" + fi + if [[ "$verbosity" == "verbose" ]]; then echo -e "[${C_GREEN} OK ${C_RESET}]"; fi + fi # Backup Sources if [[ "$mode" == "backup" || "$mode" == "diff" ]]; then if [[ "$verbosity" == "verbose" ]]; then echo -e "\n ${C_DIM}- Checking Backup Sources${C_RESET}"; fi @@ -1881,9 +1904,6 @@ case "${1:-}" in run_preflight_checks "unlock" "quiet" run_unlock ;; - --help | -h) - display_help - ;; *) if [ -n "${1:-}" ]; then echo -e "${C_RED}Error: Unknown command '$1'${C_RESET}\n" >&2 From 3a99c956e718c5e9307a8fdcedb293a0a849b620 Mon Sep 17 00:00:00 2001 From: buildplan Date: Wed, 17 Dec 2025 09:11:50 +0000 Subject: [PATCH 2/6] checksum v0.42 --- restic-backup.sh.sha256 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restic-backup.sh.sha256 b/restic-backup.sh.sha256 index 47e93c4..e64efda 100644 --- a/restic-backup.sh.sha256 +++ b/restic-backup.sh.sha256 @@ -1 +1 @@ -13de8cc2e5874e419ddc4a10f80ab56b76c5d4a6ba28af3b05fe79e6cf51173e restic-backup.sh +6782a0a85e266fb3cb920c9dca0565fc4df0499b7ae21b29778aa34423c23739 restic-backup.sh From 2510fd957dc0adbd2dd72893233cce9928bf2b18 Mon Sep 17 00:00:00 2001 From: buildplan Date: Wed, 17 Dec 2025 09:23:09 +0000 Subject: [PATCH 3/6] version bump --- restic-backup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restic-backup.sh b/restic-backup.sh index 90b822e..87a3388 100644 --- a/restic-backup.sh +++ b/restic-backup.sh @@ -9,7 +9,7 @@ set -euo pipefail umask 077 # --- Script Constants --- -SCRIPT_VERSION="0.41" +SCRIPT_VERSION="0.42" SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) PROG_NAME=$(basename "$0"); readonly PROG_NAME CONFIG_FILE="${SCRIPT_DIR}/restic-backup.conf" From e5fceecab34ebb26495855c2e34314075e93b919 Mon Sep 17 00:00:00 2001 From: buildplan Date: Wed, 17 Dec 2025 09:23:56 +0000 Subject: [PATCH 4/6] checksum v0.42 --- restic-backup.sh.sha256 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restic-backup.sh.sha256 b/restic-backup.sh.sha256 index e64efda..c2599e6 100644 --- a/restic-backup.sh.sha256 +++ b/restic-backup.sh.sha256 @@ -1 +1 @@ -6782a0a85e266fb3cb920c9dca0565fc4df0499b7ae21b29778aa34423c23739 restic-backup.sh +3ed5c39a09375b1c17461a45a27956b746b74f89b76e09b191566a4f03f56efb restic-backup.sh From 808d295a8a8762bf798d2d442fc1b9b450067515 Mon Sep 17 00:00:00 2001 From: buildplan Date: Wed, 17 Dec 2025 09:48:20 +0000 Subject: [PATCH 5/6] improve help: log file path --- restic-backup.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/restic-backup.sh b/restic-backup.sh index 87a3388..68f6985 100644 --- a/restic-backup.sh +++ b/restic-backup.sh @@ -85,7 +85,14 @@ display_help() { echo -e "${C_BOLD}${C_YELLOW}DEPENDENCIES:${C_RESET}" echo -e " This script requires: ${C_GREEN}restic, curl, gpg, bzip2, less, jq, flock${C_RESET}" echo - echo -e "Config: ${C_DIM}${CONFIG_FILE}${C_RESET} Log: ${C_DIM}${LOG_FILE:-"(not set)"}${C_RESET}" + local display_log + if [ -r "$CONFIG_FILE" ]; then + # shellcheck source=/dev/null disable=SC2153 + display_log=$(source "$CONFIG_FILE" >/dev/null 2>&1; echo "$LOG_FILE") + else + display_log="(requires sudo to read config)" + fi + echo -e "Config: ${C_DIM}${CONFIG_FILE}${C_RESET} Log: ${C_DIM}${display_log:-"(not set)"}${C_RESET}" echo echo -e "For full details, see the online documentation: \e]8;;${readme_url}\a${C_CYAN}README.md${C_RESET}\e]8;;\a" echo -e "${C_YELLOW}Note:${C_RESET} For restic official documentation See: https://restic.readthedocs.io/" From eb475e6e0c89bf8e6cede8b799c3c39d68d85abc Mon Sep 17 00:00:00 2001 From: buildplan Date: Wed, 17 Dec 2025 09:48:53 +0000 Subject: [PATCH 6/6] checksum v0.42 --- restic-backup.sh.sha256 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/restic-backup.sh.sha256 b/restic-backup.sh.sha256 index c2599e6..f7f9d4f 100644 --- a/restic-backup.sh.sha256 +++ b/restic-backup.sh.sha256 @@ -1 +1 @@ -3ed5c39a09375b1c17461a45a27956b746b74f89b76e09b191566a4f03f56efb restic-backup.sh +a7206371dd37d9803bd1df98083332c55f570da8bc6be3ac8a7e7538356d6430 restic-backup.sh