Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 90 additions & 63 deletions restic-backup.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/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

# --- 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"
Expand All @@ -35,6 +35,77 @@ 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 <id> <path>" "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 <snapshot_id>" "List files and directories inside a specific snapshot."
printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--find <pattern...>" "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
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/"
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}"
Expand Down Expand Up @@ -194,7 +265,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
Expand Down Expand Up @@ -296,62 +367,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 <id> <path>" "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 <snapshot_id>" "List files and directories inside a specific snapshot."
printf " ${C_GREEN}%-20s${C_RESET} %s\n" "--find <pattern...>" "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
Expand Down Expand Up @@ -808,6 +823,9 @@ cleanup() {
if [ -n "${LOCK_FD:-}" ]; then
flock -u "$LOCK_FD"
fi
if [ -n "$(jobs -p)" ]; then
pkill -P $$ || true
fi
}

run_preflight_checks() {
Expand Down Expand Up @@ -931,6 +949,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
Expand Down Expand Up @@ -1881,9 +1911,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
Expand Down
2 changes: 1 addition & 1 deletion restic-backup.sh.sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
13de8cc2e5874e419ddc4a10f80ab56b76c5d4a6ba28af3b05fe79e6cf51173e restic-backup.sh
a7206371dd37d9803bd1df98083332c55f570da8bc6be3ac8a7e7538356d6430 restic-backup.sh