#!/bin/bash # # Borg script for home folder backups. # Adapted from https://borgbackup.readthedocs.io/en/stable/quickstart.html#automating-backups # # See also: # # https://borgbackup.readthedocs.io/en/stable/faq.html#if-a-backup-stops-mid-way-does-the-already-backed-up-data-stay-there # https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-placeholders # Parameters FULLNAME="$0" BASENAME="`basename $0`" DESTINATION="$1" OPTION="$2" BASE_CONFIG="$HOME/.config/borger" CONFIG="$BASE_CONFIG/$DESTINATION" # Print info function info { printf "[$BASENAME] [%s] %s\n" "$(date)" "$*" >&2; } # Fata error function fatal { info [fatal] $* exit 1; } # Display usage function borger_usage { echo "usage: $BASENAME [--continuous|--list|--check|--info]" echo -n "available destinations from $BASE_CONFIG: " ls $BASE_CONFIG exit 1 } # Process configuration function borger_config { if [ ! -e "$CONFIG" ]; then fatal "No such config $CONFIG" elif [ -d "$CONFIG" ]; then info "Multiple destination \"$DESTINATION\" found. Processing each subconfig..." # Config is a folder, so we iterate over all items # and call borger for each config in parallel for config in `ls $CONFIG`; do info "Calling borger for $DESTINATION/$config..." ( $FULLNAME $DESTINATION/$config $OPTION 2>&1 | sed -e "s/^\[borger\]/[borger] [$config]/" -e "s/^\([^\[]\)/[borger] [$config] \1/" ) & done # Since we dispatched everything to subprocesses, # there's nothing to do here. #exit wait fi # Ensure we have an username if [ -z "$USER" ]; then USER="`whoami`" fi # In case your home folder is a symlink if [ ! -z "`readlink $HOME`" ]; then ORIG="`readlink $HOME`" else ORIG="$HOME" fi # Default backup config KEEPDAILY="7" KEEPWEEKLY="4" KEEPMONTHLY="6" ENCRYPTION="keyfile" PLACEHOLDER="{user}" INTERVAL="1h" source $CONFIG # Setting this, so the repo does not need to be given on the commandline: if [ -z "$BORG_REPO" ]; then export BORG_REPO_DIR="/var/backups/users/$USER/borg" export BORG_REPO="ssh://$SSH_SERVER:$SSH_PORT/$BORG_REPO_DIR" fi } # List function borger_list { borg list exit $? } # Check function borger_check { borg check exit $? } # Info function borger_info { borg info exit $? } # Our trap function borger_trap { trap 'info Backup interrupted >&2; exit 2' INT TERM } # Initialize function borger_init { if [ ! -z "$SSH_SERVER" ]; then # Remote backup over SSH if ! ssh $SSH_SERVER -p $SSH_PORT test -f $BORG_REPO_DIR/config; then info "Initializing borg repository at $BORG_REPO..." borg init --encryption=$ENCRYPTION $BORG_REPO init_exit=$? if [ "$init_exit" != "0" ]; then fatal "Error initializing repository" fi fi else # Local backup if [ ! -f "$BORG_REPO/config" ]; then info "Initializing borg repository at $BORG_REPO..." borg init --encryption=$ENCRYPTION $BORG_REPO init_exit=$? if [ "$init_exit" != "0" ]; then fatal "Error initializing repository" fi fi fi } # Backup the most important directories into an archive named after # the machine this script is currently running on: function borger_create { info "Starting backup..." borg create \ --verbose \ --filter AME \ --list \ --stats \ --show-rc \ --compression lz4 \ --exclude-caches \ ::"${PLACEHOLDER}-{now}" \ $ORIG backup_exit=$? #if [ "$backup_exit" != "0" ]; then # fatal "Error creating snapshot" #fi } # Use the `prune` subcommand to maintain daily, weekly and monthly archives. # The '${PLACEHOLDER}-' prefix is very important to limit prune's operation to # one specific archive and not apply to archives also. function borger_prune { info "Pruning repository..." borg prune \ --list \ --prefix "${PLACEHOLDER}-" \ --show-rc \ --keep-daily $KEEPDAILY \ --keep-weekly $KEEPWEEKLY \ --keep-monthly $KEEPMONTHLY \ prune_exit=$? #if [ "$prune_exit" != "0" ]; then # fatal "Error pruning repository" #fi } # Main backup procedure function borger_run { borger_config borger_trap borger_init borger_create borger_prune borger_exit } function borger_exit { # Use highest exit code as global exit code global_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit )) if [ ${global_exit} -eq 1 ]; then info "Backup and/or Prune finished with a warning" fi if [ ${global_exit} -gt 1 ]; then info "Backup and/or Prune finished with an error" fi exit ${global_exit} } # Ensure we have our base config folder mkdir -p $BASE_CONFIG # Dispatch if [ -z "$DESTINATION" ]; then borger_usage elif [ -z "$OPTION" ]; then borger_run elif [ "$OPTION" == "--list" ]; then borger_config borger_list elif [ "$OPTION" == "--check" ]; then borger_config borger_check elif [ "$OPTION" == "--info" ]; then borger_config borger_info elif [ "$OPTION" == "--continuous" ]; then borger_config # Convert the pass command to passphrase otherwise # the user would be interrupted by a passphrase prompt # at every iteration if [ ! -z "$BORG_PASSCOMMAND" ]; then export BORG_PASSPHRASE="`$BORG_PASSCOMMAND`" export BORG_PASSCOMMAND="" fi while true; do # Run as a subprocess so we do not exit on any fatal error $FULLNAME $DESTINATION info "Running on continous mode... sleeping $INTERVAL..." sleep $INTERVAL done fi