#!/bin/bash set -e PROGNAME=`basename $0` BUSERVER_HOSTNAME=macaroni BUMASTER_HOSTNAME=fiori # should be a host with unfettered ssh access everywhere #BUCLIENT_HOSTNAMES="lasagne $(rocon -m LinuxHosts and not SometimesDownHosts)" BUCLIENT_HOSTNAMES="$(rocon -m LinuxHosts and not SometimesDownHosts and not torchio)" main() { local BUCLIENT_HOSTNAME # Set defaults for stuff modifiable by command line options like '-v' or '--debug=xxx' VERBOSELEVEL=2 # Option processing while [ "X$1" != X ]; do case $1 in -v|--verbose) VERBOSELEVEL=3 ;; -d) VERBOSELEVEL=$2; shift ;; --debug=*) VERBOSELEVEL=${1#*=} ;; -h|--help) usage 0 ;; # Any other option is handled within each individual function *) break ;; esac shift done info "checking ssh access ..." [ $(uname -n) = $BUMASTER_HOSTNAME ] || { error "$(uname -n): backup master is $BUMASTER_HOSTNAME"; return 1; } HAD_AN_ERROR=false for HOST in $BUSERVER_HOSTNAME $BUCLIENT_HOSTNAMES; do SSH_OUTPUT="$(setsid ssh -n $HOST "echo OK" 2>&1 || true)" [ "X$SSH_OUTPUT" = XOK ] || { error "$HOST: no unprompted ssh access ($SSH_OUTPUT)"; HAD_AN_ERROR=true; } done ! $HAD_AN_ERROR || return 1 # If backup is within 15 minutes of the last one, then assume settings are the same, else ask. if [ -f ~/.${PROGNAME}rc ] && [[ $(date +%s) < $(($(stat -c %Z ~/.${PROGNAME}rc) + 900)) ]]; then . ~/.${PROGNAME}rc BUSERVER_KDEV=$DFLT_BUSERVER_KDEV # Touch the file so that next time we compare the time against *now*, not # against the first time we asked. touch ~/.${PROGNAME}rc else echo -n "device on $BUSERVER_HOSTNAME [$DFLT_BUSERVER_KDEV]: " read BUSERVER_KDEV [ "X$BUSERVER_KDEV" != X ] || BUSERVER_KDEV=$DFLT_BUSERVER_KDEV echo "DFLT_BUSERVER_KDEV=$BUSERVER_KDEV" > ~/.${PROGNAME}rc fi BUSERVER_MNTPNT=/mnt BUCLIENT_MNTPNT=/mnt info "cleaning up previous incomplete backups ..." debug 10 "main: unmounting share on clients ..." for BUCLIENT_HOSTNAME in $BUCLIENT_HOSTNAMES; do debug 10 "main: unmounting share on $BUCLIENT_HOSTNAME ..." unshare $BUCLIENT_HOSTNAME || true done debug 10 "main: unmounting device on server ..." ssh $BUSERVER_HOSTNAME "umount $BUSERVER_MNTPNT" || true info "prologue ..." ssh $BUSERVER_HOSTNAME "mount $BUSERVER_KDEV $BUSERVER_MNTPNT" info "faking backing up all clients ..." FAILED=false for BUCLIENT_HOSTNAME in $BUCLIENT_HOSTNAMES; do share $BUCLIENT_HOSTNAME backup --fake $BUCLIENT_HOSTNAME || FAILED=true unshare $BUCLIENT_HOSTNAME done ! $FAILED || { error "at least one fake backup failed"; return 1; } # This 'if true' is there to quicky disable the real backups when I'm testing if true; then info "really backing up all clients ..." for BUCLIENT_HOSTNAME in $BUCLIENT_HOSTNAMES; do share $BUCLIENT_HOSTNAME backup $BUCLIENT_HOSTNAME unshare $BUCLIENT_HOSTNAME done fi info "epilogue ..." ssh $BUSERVER_HOSTNAME "umount $BUSERVER_MNTPNT" } share() { local BUCLIENT_HOSTNAME=$1 info "backing up $BUCLIENT_HOSTNAME ..." # Mount USB disk and share it. ssh $BUSERVER_HOSTNAME "[ $BUCLIENT_HOSTNAME = $BUSERVER_HOSTNAME ] || exportfs -v -o rw,no_root_squash,sync $BUCLIENT_HOSTNAME:$BUSERVER_MNTPNT > /dev/null" # Mount share ssh $BUCLIENT_HOSTNAME "[ $BUCLIENT_HOSTNAME = $BUSERVER_HOSTNAME ] || mount $BUSERVER_HOSTNAME:$BUSERVER_MNTPNT $BUCLIENT_MNTPNT" } backup() { local BUCLIENT_HOSTNAME FAKE_FLAG FAKE_FLAG=false while [ "X$1" != X ]; do case $1 in --fake) FAKE_FLAG=true ;; --) shift; break ;; -*) internal "backup: $1: unknown option" ;; *) break ;; esac shift done [ $# = 1 ] || internal "backup: $#: invalid argument count" BUCLIENT_HOSTNAME=$1 # Do backup (recording time it took) if ! $FAKE_FLAG; then BACKUP_CMDLINE="ssh $BUCLIENT_HOSTNAME \"~/bin/usbbu2 -f ~/etc/usbbu2.conf $BUCLIENT_MNTPNT\"" debug 10 "backup: BACKUP_CMDLINE=[$BACKUP_CMDLINE]" TIME_BEFORE=`date +%s` eval "$BACKUP_CMDLINE" || warning "usbbu2 failed" TIME_AFTER=`date +%s` TIME_DIFF=`echo "scale=2; $TIME_AFTER-$TIME_BEFORE" | bc -q` FORMATTED_DIFF=`printf "%02d:%02d:%02d\n" $(($TIME_DIFF / 3600)) $(($TIME_DIFF / 60 % 60)) $(($TIME_DIFF % 60))` echo "||[\"$BUCLIENT_HOSTNAME\"]||`date +%d/%m/%Y`||$FORMATTED_DIFF||" else BACKUP_CMDLINE="ssh $BUCLIENT_HOSTNAME \"~/bin/usbbu2 --fake -f ~/etc/usbbu2.conf $BUCLIENT_MNTPNT\"" debug 10 "backup: BACKUP_CMDLINE=[$BACKUP_CMDLINE]" eval "$BACKUP_CMDLINE" || { error "usbbu2 failed"; return 1; } fi } unshare() { local BUCLIENT_HOSTNAME=$1 # Unmount share ssh $BUCLIENT_HOSTNAME "[ $BUCLIENT_HOSTNAME = $BUSERVER_HOSTNAME ] || umount $BUCLIENT_MNTPNT" # Unshare USB disk and unmount it. ssh $BUSERVER_HOSTNAME "[ $BUCLIENT_HOSTNAME = $BUSERVER_HOSTNAME ] || exportfs -uv $BUCLIENT_HOSTNAME:$BUSERVER_MNTPNT > /dev/null" } # Messaging functions debug() { [ $VERBOSELEVEL -lt $1 ] || echo "$PROGNAME: DEBUG[$1]: $2" >&2; } info() { [ $VERBOSELEVEL -lt 3 ] || echo "$PROGNAME: INFO: $1" >&2; } warning() { [ $VERBOSELEVEL -lt 2 ] || echo "$PROGNAME: WARNING: $1" >&2; } error() { [ $VERBOSELEVEL -lt 1 ] || echo "$PROGNAME: ERROR: $1" >&2; } internal() { echo "$PROGNAME: INTERNAL ERROR: $1" >&2; exit 2; } main "$@"