#!/bin/bash set -x # Note that we' aren't doing a set -e set -u set -o pipefail function usage { retval="$1" case "$retval" in 0) ;; *) exec 1>&2 ;; esac echo "Usage: $0 [--normal|--overwrite] --source-dir /source/directory --dest-dir /destination/directory --compressor zstd --help" echo echo "Normal mode will do a backup with rotation." echo echo "Overwrite mode will overwrite the last daily backup, without doing any rotation at all. It's for redoing a backup." echo "when a previous one fails." echo echo "We default to compressing with zstd." echo echo "This script can deal pretty well with not being run every day, but it probably shouldn't be run more than once a day." exit "$retval" } mode=unknown source_dir="" destination_dir="" compressor=zstd while [ "$#" -ge 1 ] do case "$1" in --normal) mode=normal ;; --overwrite) mode=overwrite ;; --source-dir) source_dir="$2" shift ;; --dest-dir) destination_dir="$2" shift ;; --compressor) compressor="$2" shift ;; -h|--help) usage 0 ;; *) usage 1 ;; esac shift done all_good=True case "$mode" in normal) echo "$0: OK, I will do a normal backup with rotation" 1>&2 ;; overwrite) echo "$0: OK, I will overwrite the most recent backup, without any sort of rotation" 1>&2 ;; unknown) all_good=False echo "$0: You must specify either --normal or --overwrite" 1>&2 ;; *) echo "$0: internal error: \$mode has a strange value: $mode" 1>&2 exit 1 ;; esac case "$source_dir" in "") echo "$0: --srcdir is a required option" 1>&2 all_good=False esac case "$destination_dir" in "") echo "$0: --dstdir is a required option" 1>&2 all_good=False esac case "$all_good" in True) ;; False) echo "$0: one or more preflight checks failed" 1>&2 usage 1 ;; *) echo "$0: Internal error: \$all_good has a strange value: $all_good" 1>&2 exit 1 ;; esac # disabled Wed Nov 3 09:54:46 PST 2004, because $destination_dir exists, # but is not mounted # Mon Sep 12 10:23:13 PDT 2005: reenabled, now that we have 3511+QFS #exit 1 umask 077 PATH=/usr/local/bin:"$PATH" mkdir -p "$destination_dir"/backups if ! cd "$destination_dir"/backups then echo "$destination_dir"/backups does not exit 1>&2 exit 1 fi mkdir -p daily.1 mkdir -p daily.2 mkdir -p weekly.1 mkdir -p weekly.2 mkdir -p monthly.1 mkdir -p monthly.2 seven_days=$((60*60*24*7)) thirty_days=$((60*60*24*30)) curtime=$(python3 -c 'import time; t=int(time.time()); print(t)') if [ "$mode" = normal ] then rm -rf daily.2 mv daily.1 daily.2 mkdir -p daily.1 fi if tty -s then ONTTY=True else ONTTY=False fi # tar likes to exit false if even one file has a problem, but we don't want that to interfere with rotation, # hence the || true's. case "$ONTTY" in True) bytes="$(du -skx "$source_dir" | awk ' { print $1 * 1024 }')" cd "$destination_dir/backups" (cd "$source_dir" && tar --create --one-file-system --sparse .) \ | reblock -e "$bytes" -b $((1024*1024)) -t 300 \ | mtee --quiet -- \ "$compressor > daily.1/archive" \ "tar tvf - | $compressor > daily.1/tvf" ;; False) cd "$destination_dir/backups" (cd "$source_dir" && tar --create --one-file-system --sparse .) \ | mtee --quiet -- \ "$compressor > daily.1/archive" \ "tar tvf - | $compressor > daily.1/tvf" ;; *) echo "$0: internal error: \$ONTTY has a strange value: $ONTTY" 1>&2 exit 1 ;; esac function mtime_of_file { filename="$1" stat -c '%Y' "$filename" } function should_do_weekly { if ! [ -f "weekly.1/archive" ] then return 0 fi mtime_of_weekly=$(mtime_of_file weekly.1/archive) if [ $((curtime - mtime_of_weekly)) -gt "$seven_days" ] then return 0 else return 1 fi } function should_do_monthly { if ! [ -f "monthly.1/archive" ] then return 0 fi mtime_of_monthly=$(mtime_of_file monthly.1/archive) if [ $((curtime - mtime_of_monthly)) -gt "$thirty_days" ] then return 0 else return 1 fi } # ln (hardlinking) appears to work even on CIFS now, though our CIFS server is running Samba, so maybe that's why. case "$mode" in normal) if should_do_weekly then rm -rf weekly.2 mv weekly.1 weekly.2 mkdir weekly.1 for file in daily.1/* do ln "$file" weekly.1/. done fi if should_do_monthly then rm -rf monthly.2 mv monthly.1 monthly.2 mkdir monthly.1 for file in daily.1/* do ln "$file" monthly.1/. done fi ;; overwrite) ;; *) echo "$0: internal error: \$mode has a strange value: $mode" 1>&2 exit 1 ;; esac