#!/bin/bash set -x set -eu 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 gzip --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 gzip, but actually pbzip2 is faster - just less common." exit "$retval" } mode=unknown source_dir="" destination_dir="" compressor=gzip 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 ;; 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=1 else ONTTY=0 fi export ONTTY # 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. if [ "$ONTTY" = 1 ] then bytes="$(du -skx "$source_dir" | awk ' { print $1 * 1024 }')" (cd "$source_dir" && tar cvlSf - . 2> "$destination_dir"/backups/daily.1/tvf) \ | reblock -e "$bytes" -b $((1024*1024)) -t 300 \ | eval "$compressor" > daily.1/archive || true else (cd "$source_dir" && tar cvlSf - . 2> daily.1/tvf) | eval "$compressor" > daily.1/archive || true fi 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. if [ "$mode" = normal ] then 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 fi