#!/usr/bin/env bash

## Back up my files to each external drive using rsync.
##
## Usage: backup
##
## Dependencies: GNU coreutils, GNU grep, GNU sed, rsync
##
## This script creates an incremental backup in <drive>/backups/ using a list of
## source paths. Unchanged files are hard linked to the last backup. This script
## also syncs my home directory to <drive>/mirror/ just in case I someday fail
## to add an important path to the list of sources for my backups.

set -euo pipefail
shopt -s inherit_errexit

die() { echo "$(basename "$0"): $1" >&2; exit 1; }

[[ " $* " =~ ' --help ' ]] && sed -n 's/^## \?//p' "$0" && exit
(( UID != 0 )) || die 'do not run as root'

# Source paths for backups (not mirrors).
sources=(
  ~/./.archive
  ~/./.bashrc
  ~/./.config/.archive
  ~/./.config/Xresources
  ~/./.config/dunst
  ~/./.config/emacs/.archive
  ~/./.config/emacs/init.el
  ~/./.config/emacs/lisp
  ~/./.config/emacs/themes
  ~/./.config/feh
  ~/./.config/fontconfig
  ~/./.config/git
  ~/./.config/gtk-3.0
  ~/./.config/gtk-4.0
  ~/./.config/mpv
  ~/./.config/npmrc
  ~/./.config/openbox
  ~/./.config/shellcheckrc
  ~/./.config/user-dirs.dirs
  ~/./.config/zathura
  ~/./.gnupg
  ~/./.ignore
  ~/./.local/share/.archive
  ~/./.local/share/password-store
  ~/./.local/share/selectcmd.dat
  ~/./.local/share/systemd/user
  ~/./.local/share/zathura
  ~/./.mozilla/firefox/j8lriz5r.4/.archive
  ~/./.mozilla/firefox/j8lriz5r.4/bookmarkbackups
  ~/./.mozilla/firefox/j8lriz5r.4/bookmarks.html
  ~/./.mozilla/firefox/j8lriz5r.4/chrome
  ~/./.mozilla/firefox/j8lriz5r.4/persdict.dat
  ~/./.mozilla/firefox/j8lriz5r.4/sessionstore-backups
  ~/./.mozilla/firefox/j8lriz5r.4/user.js
  ~/./.ssh
  ~/./.xinitrc
  ~/./main
)

# Files excluded from backups and mirrors.
exclusions=(
  --exclude='*.asc' # OpenPGP keys exported for temporary use.
  --exclude='*.log'
  --exclude='*.log.*' # foo.log.2, foo.log.old, etc.
  --exclude='*.sqlite-journal' # Temporary files. May vanish.
  --exclude='*.sqlite-wal' # Write-ahead log files.
  --exclude='.build/*'
  --exclude='.cache/*'
  --exclude='.config/emacs/eln-cache/*'
  --exclude='.config/emacs/elpa/*'
  --exclude='.config/google-chrome/*'
  --exclude='.gnupg/.#*' # Lock files. https://forum.gnupg.org/t/4174
  --exclude='.local/share/Steam/*' # Temporary exclusion.
  --exclude='.mozilla/firefox/*/datareporting/*' # Files may vanish.
  --exclude='.mozilla/firefox/*/storage/*'
  --exclude='.next/*' # Build directory for Next.js apps.
  --exclude='.wrangler/*' # Temporary files for projects that use Wrangler.
  --exclude='Maildir/*' # Temporary exclusion.
  --exclude='__pycache__/*'
  --exclude='build/*'
  --exclude='dist/*' # Build directory for Astro projects, etc.
  --exclude='joshscalisi.com/src/dotfiles/*' # Copies from ~/.config, etc.
  --exclude='joshscalisi.com/src/scripts/*' # Copies from ~/main/s/scripts.
  --exclude='joshscalisi.com/vendor/*'
  --exclude='node_modules/*'
  --exclude='pending/*'
)

destinations=(
  /media/wd-25a2-2tb
  /media/wd-25e2-2tb-1
  /media/wd-25e2-2tb-2
)

for dest in "${destinations[@]}"; do
  [[ -d $dest/backups ]] || die "directory not found: $dest/backups"

  latest_backup=$dest/backups/latest
  new_backup=$dest/backups/$(date +%Y/%m/%d%H%M%S)
  rsync_backup_opts=(-vaRHAXh --mkpath "${exclusions[@]}")

  # Enable hard linking of unchanged files in the new backup if the destination
  # has a previous backup.
  if [[ -d $latest_backup ]]; then
    rsync_backup_opts+=(--link-dest="$latest_backup")
  else
    echo "Previous backup not found: $latest_backup"
    read -rp 'Create a new full backup without hard links? ' input
    [[ $input =~ ^[Yy] ]] && break
  fi

  echo "Creating backup in $new_backup"
  rsync "${rsync_backup_opts[@]}" "${sources[@]}" "$new_backup" |
    grep -Ev '^$|/$|/.git/' # Don't print blank lines, directories, or Git files.
  ln -fns "$new_backup" "$latest_backup"
  echo

  echo "Syncing home directory to $dest/mirror"
  rsync -vaRHAXh --delete "${exclusions[@]}" ~/./ "$dest"/mirror |
    grep -E '^sent|^total' # Print summary only.
  echo
done