#!/bin/bash # jsbk.sh - runs whatever backup commands you put in backup() below, # logs each one, and emails the result. # Cron example (2:00am daily): 0 2 * * * /usr/local/bin/jsbk.sh version="nov 2026" # Job name comes from the script's filename, so copies (jsbk-clientname.sh, name=$(basename "$0" .sh) ############################ USER EDIT BEGINS ########################### to="jason@schaeferconsulting.com" #comma-separated recipients; blank = no email from="no-reply@schaeferconsulting.com" #sender address support="Schaefer Consulting at 505.570.1114" # contact info, sent in email bkpath="" # local backup destination (disk-space report + "is it mounted" check); blank for remote-only (rclone etc.) logdir="" # blank = /var/log/jsbk as root, ~/.local/state/jsbk otherwise keepdays="60" # delete logs older than this # ---- backup commands go inside backup() function ------------------------ # step COMMAND ARGS... runs one command, logs it, and flags # a failure. Use "|| return 1" to stop on failure, or leave it off to carry on # with the remaining steps (the run is still reported as FAILED). # # example below is rsnapshot (needs sync_first 1 and link_dest 1, and lockfile /var/run/uniqueName.pid in rsnapshot.conf # intervals beta/gamma/delta). Other examples: # step "rclone to remote" rclone sync /home remote:backup --log-level INFO # step "rsync /etc" rsync -a --delete /etc/ "$bkpath/etc-mirror/" # step "tar /home" tar czf "$bkpath/home-$(date +%F).tar.gz" /home # step "pg_dump" bash -c 'pg_dump -Z 9 dbname > "$0"' "$bkpath/dbname-$(date +%F).sql.gz" backup() { #step COMMAND ARGS rsnapconf="/storage/backup/backup-scripts/rsnapshot_dropbox.conf" #rclone defaults: --transfers 4 --checkers 8 --retries 3 --low-level-retries 10 #rclone optional: --fast-list --tpslimit 12 --max-delete 20000 --dry-run step rclone sync dropbox_archive:/ /storage/backup/rclone-staging/dropbox_archive --fast-list --tpslimit 12 --max-delete 20000 --log-level INFO || return 1 step rsnapshot -c $rsnapconf sync || return 1 [ "$(date +%d)" = 01 ] && { step rsnapshot -c $rsnapconf delta || return 1; } [ "$(date +%A)" = Saturday ] && { step rsnapshot -c $rsnapconf gamma || return 1; } step rsnapshot -c $rsnapconf beta } # ------------------------------------------------------------------------ ############################# USER EDIT ENDS ############################ if [ -z "$logdir" ]; then if [ "$(id -u)" -eq 0 ]; then logdir="/var/log/jsbk"; else logdir="$HOME/.local/state/jsbk"; fi fi mkdir -p "$logdir" || { echo "cannot create $logdir" >&2; exit 1; } log="$logdir/$name-$(date +%F_%H%M%S).log" lock="$logdir/$name.lock" start=$(date +%s) failed="" # Only one run at a time. The lock is released automatically if we die, # so there is no stale-log cleanup to do. exec 9>"$lock" flock -n 9 || { echo "jsbk is already running" >&2; exit 1; } : > "$log" ln -sf "$log" "$logdir/$name-latest.log" find "$logdir" -name "$name-2*.log" -mtime +"$keepdays" -delete say() { echo -e "$*" >> "$log"; } finish() { local elapsed=$(( $(date +%s) - start )) say "Elapsed: $((elapsed/3600))h $((elapsed%3600/60))m $((elapsed%60))s" if [ -n "$bkpath" ]; then say "\nDisk space on $bkpath:" df -h "$bkpath" >> "$log" 2>&1 fi if [ -n "$failed" ]; then say "\n*** BACKUP FAILED on $(date +%c). Keep this log for diagnosis." say "*** If you need assistance, contact $support" subject="FAILED: $name on $(hostname)" else say "\nSUCCESS: backup complete." subject="$name log from $(hostname)" fi if [ -n "$to" ]; then mail -r "$from" -s "$subject" "$to" < "$log" fi [ -z "$failed" ] # exit status: 0 = success exit } trap 'say "*** Interrupted by signal"; failed=1; finish' HUP INT TERM # Run one command: log the exact command line, append its output, flag failure. # printf %q prints the command in a copy-pasteable form (quotes spaces etc.). step() { say "\n>>> $(printf '%q ' "$@")" "$@" >> "$log" 2>&1 local rc=$? [ $rc -eq 0 ] || { say "ERROR: command failed (exit $rc)"; failed=1; return 1; } } say "BACKUP LOG: $name" say "version: $version" say "server: $(hostname)" say "started: $(date +%c)\n" if [ -n "$bkpath" ] && [ ! -d "$bkpath" ]; then say "ERROR: $bkpath does not exist (is the disk mounted?)" failed=1 finish fi backup finish