Wren Linux

wren-linux / extras / backup.sh

#!/bin/bash
# Encrypted backups of your home folder with restic (from Debian), to a
# USB drive or any folder you choose. Everything in the backup is
# encrypted with a password that restic asks for each time; it isn't
# stored anywhere. Backups only store what changed since the last one.
# This sets up the backup place and a "backup" command.
# For backups that run without you (a timer, a script), the config can hold
# a command that prints the password, e.g. one that asks a password manager;
# the password itself is never written down. At login it says when the last
# backup is 30 days old or more (backup remind): in sway a notification, on a
# text console a line.
# files: lib/sway.sh
# Usage: bash backup.sh [--remove | --manual]
# summary: Encrypted backups of your home folder (restic)
# security: most
# category: files
# status: apt:restic
. "$(dirname -- "${BASH_SOURCE[0]}")/../lib.sh"
if [ "${1:-}" = --manual ]; then print_manual; exit; fi
need_user
RC=$HOME/.bashrc
RC_BEGIN='# >>> Wren Linux backup reminder >>>'
RC_END='# <<< Wren Linux backup reminder <<<'
rc_take_out() { [ -f "$RC" ] && sed -i "\\|^$RC_BEGIN\$|,\\|^$RC_END\$|d" "$RC"; return 0; }

CONF_DIR="$HOME/.config/wren"
CONF="$CONF_DIR/backup.conf"
EXCLUDES="$CONF_DIR/backup-excludes"

if [ "${1:-}" = --remove ]; then
    remove_bin backup
    remove_snippet backup-reminder
    rc_take_out
    rm -f "$CONF" "$EXCLUDES"
    apt_purge restic
    note "Your backups themselves are left where they are."
    exit
fi

say "Installing restic"
apt_install restic

# Where the backups go
repo=
if [ -f "$CONF" ]; then
    repo=$(sed -n 's/^BACKUP_REPO=//p' "$CONF")
    ask "Keep backing up to $repo?" y || repo=
fi
if [ -z "$repo" ]; then
    default=
    for d in /media/"$USER"/*/; do
        [ -d "$d" ] && { default="${d%/}/wren-backup"; break; }
    done
    note "Where should backups go? A folder on a USB drive is best (plug it in first)."
    [ -z "$default" ] || note "Press Enter for: $default"
    tty_read repo "    folder> "
    repo=${repo:-$default}
    [ -n "$repo" ] || die "No folder given."
    repo=${repo/#\~/$HOME}
    [[ $repo == /* ]] || die "Give the full path, starting with /."
fi

if [ -f "$repo/config" ]; then
    note "Using the existing backup in $repo."
else
    mkdir -p "$repo"
    say "Creating an encrypted backup in $repo"
    warn "Choose a strong password and keep it somewhere safe outside this laptop"
    warn "(on paper, too): without it the backup can't be opened, and your"
    warn "KeePassXC database is inside the backup."
    restic -r "$repo" init </dev/tty || die "restic couldn't create the backup."
fi

mkdir -p "$CONF_DIR"
cmd=$(sed -n 's/^PASSWORD_COMMAND=//p' "$CONF" 2>/dev/null || true)
if [ -n "$cmd" ]; then
    ask "Keep getting the backup password with: $cmd ?" y || cmd=
else
    note "restic asks for the backup password each time. To run backups without typing it,"
    note "give a command that prints it (for example one that reads a secret store you unlock at login)."
    tty_read cmd "    password command (Enter to be asked each time)> "
fi
printf '# Written by Wren Linux extras/backup.sh\nBACKUP_REPO=%s\n' "$repo" >"$CONF"
if [ -n "$cmd" ]; then printf 'PASSWORD_COMMAND=%s\n' "$cmd" >>"$CONF"; fi

# write_excludes FILE: what is left out of backups: caches, trash, and big
# things that can be downloaded or built again (VM disks: libvirt's, which
# Whonix uses, and ~/vpskvm, mockvps-kvm's). The file is yours to edit, one
# pattern per line; a later run only adds the lines Wren Linux's list gained.
write_excludes() {
    local f=$1 line
    if [ ! -f "$f" ]; then
        printf '%s\n' "# Left out of backups (written by Wren Linux extras/backup.sh)" \
            "$HOME/.cache" "$HOME/.local/share/Trash" "$HOME/.local/share/libvirt/images" >"$f"
    fi
    # Added to Wren Linux's list later (a line you deleted from the first three stays deleted)
    for line in "$HOME/vpskvm" "$OFFLINE_DIR"; do
        grep -qxF "$line" "$f" || echo "$line" >>"$f"
    done
}
write_excludes "$EXCLUDES"

install_bin backup
# At login: a word when the last backup is 30 days old or more
put_snippet backup-reminder <<'EOF'
# Says so at login when the last backup is 30 days old or more (Wren Linux extras/backup.sh)
exec ~/.local/bin/backup remind
EOF
rc_take_out
# shellcheck disable=SC2016  # written into ~/.bashrc as it is, expanded there
printf '%s\n' "$RC_BEGIN" '[ -n "${WAYLAND_DISPLAY:-}" ] || [ ! -x ~/.local/bin/backup ] || ~/.local/bin/backup remind' "$RC_END" >>"$RC"
note "Backups go to: $repo"
note "At login it reminds you when the last backup is 30 days old. Unplug the drive after each one."
note "Left out (edit to change): $EXCLUDES"

cat <<'EOF'

    How to use backup:
      backup                         back up your home folder now
      backup snapshots               list your backups
      backup ls latest               list the files in the newest backup
      backup restore latest --target ~/restored
                                     get everything back, into ~/restored
      backup restore latest --target ~/restored --include ~/.electrum
                                     get back one folder
      backup diff ID1 ID2            what changed between two backups
                                     (IDs come from "backup snapshots")
      backup check                   make sure the backup isn't damaged
      backup forget --keep-last 10 --prune
                                     keep only the 10 newest backups

    Each command asks for the backup password. Restored files keep their
    full path, e.g. ~/restored/home/you/.electrum: look there, then copy
    back what you need.
EOF