From 3af024579ec99e9ecf2eefaab81dd8dd0d27021b Mon Sep 17 00:00:00 2001 From: Glen Marchesani Date: Sat, 16 May 2026 09:41:45 -0400 Subject: [PATCH] Initial commit: restic backup configuration Co-Authored-By: Claude Opus 4.6 (1M context) --- .envrc | 4 ++ .gitignore | 2 + backup.sh | 67 +++++++++++++++++++++++++++++++++ excludes.txt | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ flake.lock | 27 +++++++++++++ flake.nix | 20 ++++++++++ 6 files changed, 224 insertions(+) create mode 100755 .envrc create mode 100644 .gitignore create mode 100755 backup.sh create mode 100644 excludes.txt create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100755 index 0000000..b9c0aa1 --- /dev/null +++ b/.envrc @@ -0,0 +1,4 @@ +if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=" +fi +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..826c9b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.password +.direnv diff --git a/backup.sh b/backup.sh new file mode 100755 index 0000000..4322424 --- /dev/null +++ b/backup.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +#export RESTIC_REPOSITORY="sftp:glen@thalassa.local:/Users/glen/backups/restic-starbak" +export RESTIC_REPOSITORY="sftp:glen@192.168.0.247:/Users/glen/backups/restic-starbak" +export RESTIC_PASSWORD_FILE="${SCRIPT_DIR}/.password" + +EXCLUDE_FILE="${SCRIPT_DIR}/excludes.txt" + +# Force macOS system ssh — the nix restic wrapper prepends nix openssh to PATH, +# which lacks macOS network entitlements and fails inside tmux +SFTP_OPTS=(-o "sftp.command=/usr/bin/ssh glen@192.168.0.247 -s sftp") + +if [ ! -f "$RESTIC_PASSWORD_FILE" ]; then + echo "Error: Password file not found at $RESTIC_PASSWORD_FILE" + echo "Create it with: head -c 32 /dev/urandom | base64 > ${RESTIC_PASSWORD_FILE}" + echo "Then: chmod 600 ${RESTIC_PASSWORD_FILE}" + exit 1 +fi + +case "${1:-backup}" in + init) + restic "${SFTP_OPTS[@]}" init + ;; + backup) + restic "${SFTP_OPTS[@]}" backup \ + /Users/glen \ + --exclude-file="$EXCLUDE_FILE" \ + --exclude-caches \ + --one-file-system \ + --verbose + ;; + snapshots) + restic "${SFTP_OPTS[@]}" snapshots + ;; + prune) + restic "${SFTP_OPTS[@]}" forget \ + --keep-hourly 24 \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 12 \ + --keep-yearly 3 \ + --prune + ;; + check) + restic "${SFTP_OPTS[@]}" check + ;; + stats) + # per-snapshot sizes; pass snapshot ID for a specific one + restic "${SFTP_OPTS[@]}" stats "${2:-latest}" + ;; + diff) + # diff two snapshots: ./backup.sh diff + restic "${SFTP_OPTS[@]}" diff "${2:?snapshot1 ID required}" "${3:?snapshot2 ID required}" + ;; + mount) + MOUNT_POINT="${2:-/tmp/restic-mount}" + mkdir -p "$MOUNT_POINT" + restic "${SFTP_OPTS[@]}" mount "$MOUNT_POINT" + ;; + *) + echo "Usage: $0 {init|backup|snapshots|prune|check|stats [id]|diff |mount [path]}" + exit 1 + ;; +esac diff --git a/excludes.txt b/excludes.txt new file mode 100644 index 0000000..3e9007b --- /dev/null +++ b/excludes.txt @@ -0,0 +1,104 @@ +# ============================================ +# Restic exclude list for macOS user data backup +# Mirrors Time Machine standard exclusions + extras +# ============================================ + +# --- macOS system/app caches --- +Library/Caches +Library/Logs +Library/Cookies +Library/HTTPStorages +Library/Saved Application State +Library/WebKit/MediaKeys +Library/Containers/*/Data/Library/Caches +Library/Containers/*/Data/Library/Logs +Library/Group Containers/*/Library/Caches +Library/Application Support +Library/Containers + +# --- Spotlight & indexing --- +.Spotlight-V100 +.fseventsd + +# --- Trash --- +.Trash + +# --- macOS metadata --- +.DS_Store +.TemporaryItems +.DocumentRevisions-V100 + +# --- Nix store symlinks (reconstructable) --- +.nix-defexpr +.nix-profile +# Skip files that are nix store symlinks +.zshrc +.zshenv +.manpath + +# --- Build artifacts & dependency caches --- +node_modules +.gradle +.ivy2 +.m2 +.sbt +.npm +.cache +target +__pycache__ +*.pyc +.tox +.venv +venv +.data + +# --- Go --- +go/pkg + +# --- IDE/editor state --- +.vscode/extensions +.vscode-server + +# --- Git large objects (restic deduplicates, but no point backing up pack files) --- +.git/objects/pack + +# --- Large/reconstructable/synced --- +big-files +Seafile +cloud +Downloads + +# --- Application-specific transient data --- +Library/Application Support/Google/Chrome/Default/Service Worker +Library/Application Support/Google/Chrome/Default/Code Cache +Library/Application Support/Google/Chrome/Default/GPUCache +Library/Application Support/Google/Chrome/Default/IndexedDB +Library/Application Support/Google/Chrome/ShaderCache +Library/Application Support/Slack/Service Worker +Library/Application Support/Slack/Code Cache +Library/Application Support/Slack/GPUCache +Library/Application Support/Discord/Code Cache +Library/Application Support/Discord/GPUCache + +# --- Photos library (synced from iCloud, very large) --- +Library/Photos + +# --- Mail downloads (re-downloadable) --- +Library/Mail/V*/MailData/Envelope Index* + +# --- Swap/sleep/VM --- +.SleepImage +.swap* + +# --- Claude Code --- +#.claude.json.backup + +# --- SSH sockets --- +.ssh/sockets + +# --- atuin (shell history sync, reconstructable) --- +#.atuin + +# --- zsh compiled/session state --- +.zcompdump* +.zsh_sessions diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..3fdb452 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1778794387, + "narHash": "sha256-BL04pOS9453Awkeb9f90XBJXBSkWxN+vB7HIgnL0iMM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8a1b0127302ea51e05bf4ea5a291743fac442406", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..580bfdb --- /dev/null +++ b/flake.nix @@ -0,0 +1,20 @@ +{ + description = "Restic backup for starbak -> thalassa.local"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, nixpkgs }: + let + system = "aarch64-darwin"; + pkgs = nixpkgs.legacyPackages.${system}; + in + { + devShells.${system}.default = pkgs.mkShell { + packages = [ + pkgs.restic + ]; + }; + }; +}