#! /usr/bin/env bash # vim: set filetype=sh ts=4 sw=4 sts=4 et: set -e umask 077 basedir=$(readlink -f "$(dirname "$0")"/../..) # shellcheck source=lib/shell/functions.inc . "$basedir"/lib/shell/functions.inc # default config values for this script DESTDIR="" DAYSTOKEEP="90" GPGKEYS="" SIGNING_KEY="" SIGNING_KEY_PASSPHRASE="" PUSH_REMOTE="" PUSH_OPTIONS="" # set error trap, read config, setup logging, exit early if script is disabled, etc. script_init osh-backup-acl-keys config_mandatory check_secure if [ -z "$DESTDIR" ] ; then exit_fail "$0: Missing DESTDIR in configuration, aborting." fi if ! echo "$DAYSTOKEEP" | grep -Eq '^[0-9]+$' ; then exit_fail "$0: Invalid specified DAYSTOKEEP value ($DAYSTOKEEP), aborting." fi _log "Starting backup..." mkdir -p "$DESTDIR" tarfile="$DESTDIR/backup-$(date +'%Y-%m-%d').tar.gz" _log "Creating $tarfile..." supp_entries="" for entry in /root/.gnupg /root/.ssh /var/otp /etc/master.passwd /etc/pwd.db /etc/spwd.db \ /etc/passwd /etc/group /etc/shadow /etc/gshadow /etc/bastion /usr/local/etc/bastion do [ -e "$entry" ] && supp_entries="$supp_entries $entry" done maxtries=50 for try in $(seq 1 $maxtries) do # tar may output unimportant warnings to stderr, so we don't want to get noisy # if it exits with 0: save its stderr in a tmpfile, and cat it to stderr only if it returns != 0 tarstderr=$(mktemp) set +e # SC2086: we don't want to quote $supp_entries, we want it expanded # shellcheck disable=SC2086 tar czf "$tarfile" -p --xattrs --acls --one-file-system --numeric-owner \ --exclude=".encrypt" \ --exclude="ttyrec" \ --exclude="*.sqlite" \ --exclude="*.log" \ --exclude="*.ttyrec" \ --exclude="*.gpg" \ --exclude="*.gz" \ --exclude="*.zst" \ /home/ /etc/ssh $supp_entries 2>"$tarstderr"; ret=$? set -e if [ $ret -eq 0 ]; then _log "File created" rm -f "$tarstderr" break else # special case: if a file changed/removed while we were reading it, tar fails, in that case: retry if [ $ret -eq 1 ] && grep -q -e 'changed as we read it' -e 'removed before we read it' "$tarstderr"; then _log "Transient tar failure (try $try):" while read -r line; do _log "tar: $line" done < "$tarstderr" rm -f "$tarstderr" _log "Retrying after $try seconds..." sleep "$try" continue fi _err "Error while creating file (sysret=$ret)" while read -r line; do _err "tar: $line" done < "$tarstderr" rm -f "$tarstderr" exit_fail fi done if [ "$try" = "$maxtries" ]; then exit_fail "Failed creating tar archive after $maxtries tries!" fi encryption_worked=0 if [ -n "$GPGKEYS" ] ; then cmdline="--encrypt --batch --trust-model always" # this only exists on recent gnupg versions (>= 2.1) if gpg --dump-options | grep -q -- --pinentry-mode; then cmdline="$cmdline --pinentry-mode loopback" fi sign=0 if [ -n "$SIGNING_KEY" ] && [ -n "$SIGNING_KEY_PASSPHRASE" ]; then sign=1 cmdline="$cmdline --sign --local-user $SIGNING_KEY" fi for recipient in $GPGKEYS do cmdline="$cmdline -r $recipient" done # just in case, encrypt all .tar.gz files we find in $DESTDIR while IFS= read -r -d '' file do if [ "$sign" = 1 ]; then _log "Encrypting & signing $file..." else _log "Encrypting $file..." fi rm -f "$file.gpg" # if the gpg file already exists, remove it # shellcheck disable=SC2086 if [ "$sign" = 1 ]; then gpg $cmdline --passphrase-fd 0 "$file" <<< "$SIGNING_KEY_PASSPHRASE"; ret=$? else gpg $cmdline "$file"; ret=$? fi if [ "$ret" = 0 ]; then encryption_worked=1 if command -v shred >/dev/null; then shred -u "$file" else rm -f "$file" fi else _err "Encryption failed" fi done < <(find "$DESTDIR/" -mindepth 1 -maxdepth 1 -type f -name 'backup-????-??-??.tar.gz' -print0) else _warn "$tarfile will not be encrypted! (no GPGKEYS specified)" fi # push to remote if needed if [ -n "$PUSH_REMOTE" ] && [ "$encryption_worked" = 1 ] && [ -r "$tarfile.gpg" ] ; then _log "Pushing backup file ($tarfile.gpg) remotely..." set +e # shellcheck disable=SC2086 scp $PUSH_OPTIONS "$tarfile.gpg" "$PUSH_REMOTE"; ret=$? set -e if [ $ret -eq 0 ]; then _log "Push done" else _err "Push failed (sysret=$ret)" fi fi # cleanup _log "Cleaning up old backups..." find "$DESTDIR/" -mindepth 1 -maxdepth 1 -type f -name 'backup-????-??-??.tar.gz' -mtime +"$DAYSTOKEEP" -delete find "$DESTDIR/" -mindepth 1 -maxdepth 1 -type f -name 'backup-????-??-??.tar.gz.gpg' -mtime +"$DAYSTOKEEP" -delete exit_success