diff --git a/bin/restic.sh b/bin/restic.sh new file mode 100755 index 0000000..48d1824 --- /dev/null +++ b/bin/restic.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Script to execute a restic backup script specific to the current hostname. +# +set -o errexit +set -o nounset +set -o pipefail + +# Get the current hostname +HOSTNAME=$(hostname) + +# Define the directory where hostname-specific scripts are stored +RESTIC_SCRIPTS_DIR="${HOME}/bin/restic" + +# Construct the full path to the hostname-specific script +HOST_SPECIFIC_SCRIPT="${RESTIC_SCRIPTS_DIR}/${HOSTNAME}" + +# Check if the script exists and is executable +if [[ -f "${HOST_SPECIFIC_SCRIPT}" && -x "${HOST_SPECIFIC_SCRIPT}" ]]; then + echo "Executing restic script for hostname: ${HOSTNAME}" + "${HOST_SPECIFIC_SCRIPT}" +else + echo "Error: No executable restic script found for hostname '${HOSTNAME}' at '${HOST_SPECIFIC_SCRIPT}'." >&2 + echo "Please create an executable script at that path if you want to use this functionality." >&2 + exit 1 +fi diff --git a/bin/restic/baymax b/bin/restic/baymax new file mode 100755 index 0000000..7eb204d --- /dev/null +++ b/bin/restic/baymax @@ -0,0 +1,165 @@ +#!/bin/bash +# +# A script to backup a single-user MacBook using restic to either a local +# filesystem or Backblaze B2. + +set -o errexit +set -o nounset +set -o pipefail + +# --- Configuration --- +# Directory to be backed up. +# For this script, we assume the user's home directory. +SOURCE_DIR="${HOME}" + +# Exclude file location. We'll create a default one next to the script. +EXCLUDE_FILE="$HOME/.restic_exclude.darwin" + +# --- Functions --- +usage() { + cat << EOF +Usage: $(basename "$0") [-l /path/to/repo | -b [bucket]] [-u UPLOAD_LIMIT] + +A script to backup a single-user MacBook using restic. + +Options: + -l Backup to a local filesystem repository at the given path. + -b [bucket] Backup to a Backblaze B2 bucket. The bucket name is optional. + If not provided, it will be read from the B2_BUCKET_NAME + environment variable. + -u Limit the upload speed to the given value in KB/s. + -h Show this help message. +EOF +} + +# --- Main Script --- +# Check if restic is installed +if ! command -v restic &> /dev/null; then + echo "Error: restic command not found." >&2 + echo "Please install restic first: https://restic.net/" >&2 + exit 1 +fi + +if [[ $# -eq 0 ]]; then + usage + exit 1 +fi + +BACKUP_MODE="" +REPO="" +UPLOAD_LIMIT="" + +while getopts ":l:bu:h" opt; do + case ${opt} in + l) + BACKUP_MODE="local" + REPO="${OPTARG}" + ;; + b) + BACKUP_MODE="b2" + if [[ ${OPTIND} -le $# && "${!OPTIND}" != -* ]]; then + REPO="b2:${!OPTIND}:" + OPTIND=$((OPTIND + 1)) + fi + ;; + u) + UPLOAD_LIMIT="${OPTARG}" + ;; + h) + usage + exit 0 + ;; + \?) + echo "Invalid option: -${OPTARG}" >&2 + usage + exit 1 + ;; + :) + echo "Option -${OPTARG} requires an argument." >&2 + usage + exit 1 + ;; + esac +done + +# --- Pre-run checks --- +if [[ -z "${BACKUP_MODE}" ]]; then + echo "Error: You must specify a backup mode (-l or -b)." >&2 + usage + exit 1 +fi + +if [[ "${BACKUP_MODE}" == "b2" ]]; then + if [[ -f "${HOME}/.resticb2" ]] ; then + . "${HOME}/.resticb2" + fi + export B2_ACCOUNT_ID + export B2_ACCOUNT_KEY + export B2_BUCKET_NAME + if [[ -z "${B2_ACCOUNT_ID:-}" || -z "${B2_ACCOUNT_KEY:-}" ]]; then + echo "Error: For Backblaze B2 backups, you must set the B2_ACCOUNT_ID and B2_ACCOUNT_KEY environment variables." >&2 + exit 1 + fi + + if [[ -z "${REPO:-}" ]]; then + if [[ -n "${B2_BUCKET_NAME:-}" ]]; then + REPO="b2:${B2_BUCKET_NAME}:" + else + echo "Error: Backup mode is B2 but no bucket name was provided and the B2_BUCKET_NAME environment variable is not set." >&2 + usage + exit 1 + fi + fi +fi + +KEYCHAIN_ENTRY_NAME="restic_repo_password" +if security find-generic-password -a "$(whoami)" -s "${KEYCHAIN_ENTRY_NAME}" >/dev/null 2>&1 ; then + export RESTIC_PASSWORD_COMMAND="security find-generic-password -a \"$(whoami)\" -s \"${KEYCHAIN_ENTRY_NAME}\" -w" +# Source file? +elif [[ -f "${HOME}/.resticpass" ]] ; then + export RESTIC_PASSWORD_FILE="${HOME}/.resticpass" +fi + +# If the repository does not exist, initialize it. +# The user will be prompted for a password, which will be required for all +# future interactions with the repository. +if ! restic -r "${REPO}" snapshots &> /dev/null; then + echo "Restic repository not found or not accessible. Initializing..." + restic init -r "${REPO}" +fi + + +# --- Run Backup --- +echo "Starting restic backup..." +echo "Source: ${SOURCE_DIR}" +echo "Repository: ${REPO}" + +BACKUP_CMD="restic backup \ + --verbose \ + --repo \"${REPO}\" \ + --exclude-file \"${EXCLUDE_FILE}\" \ + --one-file-system \ + --tag \"macbook-backup\"" + +if [[ -n "${UPLOAD_LIMIT}" ]]; then + BACKUP_CMD="${BACKUP_CMD} --limit-upload ${UPLOAD_LIMIT}" +fi + +BACKUP_CMD="${BACKUP_CMD} \"${SOURCE_DIR}\"" + +eval "${BACKUP_CMD}" + +echo "Backup complete." + +# --- Prune old snapshots (optional, but recommended) --- +# Keeps the last 7 daily, 4 weekly, and 6 monthly snapshots. +echo "Pruning old snapshots..." +restic forget \ + --repo "${REPO}" \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 \ + --prune + +echo "Pruning complete." +echo "Restic backup script finished." diff --git a/bin/restic-backup.sh b/bin/restic/scar similarity index 100% rename from bin/restic-backup.sh rename to bin/restic/scar diff --git a/dotfiles/restic_exclude.darwin b/dotfiles/restic_exclude.darwin new file mode 100644 index 0000000..f9c0bf9 --- /dev/null +++ b/dotfiles/restic_exclude.darwin @@ -0,0 +1,47 @@ +# Restic exclude file for macOS + +# General Cache Folders +/Users/*/Library/Caches +/Users/*/.cache +.Trash +/private/var/folders + +# Specific Application Caches +/Users/*/Library/Application Support/Google/Chrome/Default/Application Cache +/Users/*/Library/Application Support/Firefox/Profiles/*.default/cache2 +/Users/*/Library/Application Support/Spotify/PersistentCache +/Users/*/Library/Metadata/CoreSpotlight + +# Developer related +/Users/*/.m2 +/Users/*/.gradle +/Users/*/node_modules +/Users/*/go/pkg +/Users/*/.espressif + +# Virtual Machine Disks +/Users/*/VirtualBox VMs +/Users/*/Parallels + +# Cloud Storage (to avoid backing up already synced files) +/Users/*/Dropbox +/Users/*/Google Drive +/Users/*/.onedrive + +# Other +/Users/*/Music/iTunes/iTunes Music Library.xml +/Users/*/Music/iTunes/Previous iTunes Libraries + +# macOS specific +/Users/*/.DS_Store +/Users/*/Public/Drop Box +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +hibernationfile +sleepimage +swapfile