summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Rexfile4
-rwxr-xr-xscripts/home-backup139
-rw-r--r--systemd-user/home-backup.service10
-rw-r--r--systemd-user/home-backup.timer10
4 files changed, 163 insertions, 0 deletions
diff --git a/Rexfile b/Rexfile
index 8cfc771..c85ce65 100644
--- a/Rexfile
+++ b/Rexfile
@@ -470,6 +470,10 @@ task 'home_systemd_user', sub {
# Enable the random-wallpaper timer so it starts on login.
Rex::Logger::info('Enabling random-wallpaper.timer');
run 'systemctl --user enable random-wallpaper.timer';
+
+ # Enable the home-backup timer so it starts on login.
+ Rex::Logger::info('Enabling home-backup.timer');
+ run 'systemctl --user enable home-backup.timer';
};
desc 'Install all my ~ files';
diff --git a/scripts/home-backup b/scripts/home-backup
new file mode 100755
index 0000000..1182ca5
--- /dev/null
+++ b/scripts/home-backup
@@ -0,0 +1,139 @@
+#!/usr/bin/env bash
+# Backup the home directory to a remote SSH location using rsync.
+#
+# Excludes large, regenerable, or already-replicated data (caches, trash,
+# build artifacts, git checkouts, syncthing, media libraries, VM images,
+# etc.). See the EXCLUDES array below for the full list.
+#
+# Usage:
+# home-backup # Run the backup (real run)
+# home-backup -n # Dry run (show what would transfer)
+# home-backup -d user@host:/path/backup # Override destination
+#
+# The destination can also be set via the BACKUP_DEST environment variable.
+
+set -euo pipefail
+
+declare -r PROGRAM=${0##*/}
+
+# Remote SSH destination. Defaults to the encrypted ZFS dataset on f2
+# (zdata/enc/earth-backup, inherits aes-256-gcm encryption from zdata/enc).
+# Override with the BACKUP_DEST env var or the -d flag.
+declare DEST=${BACKUP_DEST:-"paul@f2.lan:/data/enc/earth-backup"}
+
+# Source directory (trailing slash matters for rsync: copy contents of $HOME).
+declare -r SRC="$HOME/"
+
+declare DRY_RUN=""
+
+# Paths/patterns to exclude from the backup. Patterns are relative to $HOME.
+declare -ra EXCLUDES=(
+ # --- Explicitly excluded by request ---
+ "/git/"
+ "/syncthing/"
+ "/Pictures/"
+ "/Music/"
+ "/.gradle/"
+ "/.thunderbird/"
+
+ # --- Caches / trash / regenerable data ---
+ "/.cache/"
+ "/.local/share/Trash/"
+ "/.local/share/baloo/"
+ "/.local/share/containers/"
+ "/.local/share/gnome-boxes/"
+ "/.npm/"
+ "/go/"
+ "/.cargo/"
+ "/.bun/"
+ "/.android/"
+
+ # --- Common regenerable junk anywhere in the tree ---
+ "node_modules/"
+ ".cache/"
+ "*.tmp"
+ "/.var/app/*/cache/"
+
+ # --- Runtime / volatile ---
+ "/.gvfs/"
+ "/.dbus/"
+)
+
+log() {
+ printf '%s\n' "$*"
+}
+
+error() {
+ log "$*" >&2
+}
+
+usage() {
+ cat <<EOF
+Usage: $PROGRAM [-n] [-d DEST]
+
+ -n Dry run (show what would be transferred, transfer nothing)
+ -d DEST Remote SSH destination (user@host:/path), overrides BACKUP_DEST
+ -h Show this help
+
+Destination may also be set via the BACKUP_DEST environment variable
+or by editing the DEST variable at the top of this script.
+EOF
+}
+
+main() {
+ while getopts ':nd:h' opt; do
+ case "$opt" in
+ n) DRY_RUN="--dry-run" ;;
+ d) DEST="$OPTARG" ;;
+ h) usage; exit 0 ;;
+ :) error "Option -$OPTARG requires an argument."; usage; exit 2 ;;
+ ?) error "Unknown option: -$OPTARG"; usage; exit 2 ;;
+ esac
+ done
+
+ if [[ "$DEST" == CHANGEME_* || -z "$DEST" ]]; then
+ error "No backup destination set."
+ error "Set BACKUP_DEST, pass -d user@host:/path, or edit DEST in $PROGRAM."
+ exit 1
+ fi
+
+ # Build the rsync exclude arguments.
+ local -a exclude_args=()
+ local pattern
+ for pattern in "${EXCLUDES[@]}"; do
+ exclude_args+=("--exclude=$pattern")
+ done
+
+ log "Source: $SRC"
+ log "Destination: $DEST"
+ [[ -n "$DRY_RUN" ]] && log "Mode: DRY RUN (no data transferred)"
+ log ""
+
+ # rsync flags:
+ # -a archive (recursive, perms, times, symlinks, etc.)
+ # -H preserve hard links
+ # -A preserve ACLs
+ # -X preserve extended attributes
+ # --numeric-ids don't map uid/gid by name on the remote
+ # --partial --progress resumable transfers with progress output
+ # --delete mirror: remove remote files that no longer exist locally
+ # --delete-excluded also remove previously-backed-up excluded paths
+ # --human-readable human-readable sizes
+ # -e ssh transport over SSH
+ rsync \
+ -aHAX \
+ --numeric-ids \
+ --partial --progress \
+ --delete --delete-excluded \
+ --human-readable \
+ ${DRY_RUN:+$DRY_RUN} \
+ -e ssh \
+ "${exclude_args[@]}" \
+ "$SRC" \
+ "$DEST"
+
+ log ""
+ log "Backup complete."
+}
+
+main "$@"
diff --git a/systemd-user/home-backup.service b/systemd-user/home-backup.service
new file mode 100644
index 0000000..0b9a69e
--- /dev/null
+++ b/systemd-user/home-backup.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Backup home directory to f2 (encrypted ZFS via rsync over SSH)
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=%h/scripts/home-backup
+Nice=10
+IOSchedulingClass=idle
diff --git a/systemd-user/home-backup.timer b/systemd-user/home-backup.timer
new file mode 100644
index 0000000..db26c4e
--- /dev/null
+++ b/systemd-user/home-backup.timer
@@ -0,0 +1,10 @@
+[Unit]
+Description=Run home-backup once per day
+
+[Timer]
+OnCalendar=daily
+Persistent=true
+RandomizedDelaySec=30m
+
+[Install]
+WantedBy=timers.target