#!/bin/bash # $HeadURL: https://svn.pasta.freemyip.com/main/miniade/trunk/bin/nop-sh $ $LastChangedRevision: 10133 $ # A quick note to myself about this script: given the name of another # machine to backup, which we call the backup client (even though technically # the backup client is an ssh server), it determines the client's rdiff-backup's # version from a lookup function (i.e. a hostname-to-version map) and then, # if necessary, downloads the matching version, compiles it (really just # using pip3) and invokes it. This script has to support all the options that # the real rdiff-backup supports (or at least those of them that *I* use) and # a lot of the code and variables are there to support that. It only supports # the specific versions of rdiff-backup that are distributed by Debian. # Modules . $(miniade) || { echo "${0##*/}: ERROR: miniade failed (hint: run 'miniade' to see error)" >&2; exit 1; } # Configurable stuff # Other globals main() { local MY_ARGS CACHE_DIR PROGNAME # Defaults for options MODE=backup FORCE_FLAG=false CURRENT_TIME= REMOTE_SCHEMA= EXCLUDE_SOCKETS_FLAG=false EXCLUDE_OTHER_FILESYSTEMS_FLAG=false # Process options special_opts_handler() { case $1 in --exclude-sockets) EXCLUDE_SOCKETS_FLAG=true ;; --current-time) CURRENT_TIME=$2 extra_shift ;; --force) FORCE_FLAG=true ;; --exclude-other-filesystems) EXCLUDE_OTHER_FILESYSTEMS_FLAG=true ;; --remote-schema) REMOTE_SCHEMA="$2" extra_shift ;; -l) MODE=list ;; --remove-older-than) MODE=remove ;; -*) miniade_internal "$1: unhandled rdiff-backup option?" ;; *) return 1 ;; esac } miniade_process_options --help-handler=help --special-opts-handler=special_opts_handler MY_ARGS "$@" && set -- "${MY_ARGS[@]}" # Process arguments miniade_debug 10 "main: MODE=$MODE, \$#=$#, \$*=$*" if [ $MODE = backup ] && [ $# = 2 ]; then SRC=$1 DST=$2 elif [ $MODE = list ] && [ $# = 1 ]; then SRC=$1 elif [ $MODE = remove ] && [ $# = 2 ]; then PERIOD=$1 SRC=$2 else miniade_bad_usage fi miniade_debug 10 "main: MODE=$MODE, SRC=$SRC, DST=$DST, PERIOD=$PERIOD" # Sanity checks and derivations # Note that there are *two* occurences of '[[ ... =~ ... ]]' and it is the *last* one that # sets BASH_REMATCH *even* if there are no capture groups inside it. For this reason we # must put the one that *does* have capture groups last in the command line. if [ $MODE = backup ] && { ! [[ $DST =~ .*::.* ]]; } && [[ $SRC =~ (.*)::(.*) ]]; then CLIENT=${BASH_REMATCH[1]} elif [ $MODE = backup ]; then miniade_internal "backup arguments not parsable" elif [[ $MODE =~ (list|remove) ]] && [[ $SRC =~ /srv/vtape005/vtape005.0[1-9]/(oss|user-data|multimedia|laptops)/([^-]*)-* ]]; then CLIENT=${BASH_REMATCH[2]} elif [[ $MODE =~ (list|remove) ]]; then miniade_internal "list argument not parsable" else miniade_internal "?" fi # Clean client name and get server name CLIENT=${CLIENT%%.*} miniade_debug 10 "main: CLIENT=$CLIENT" get_rdiffbackup_version $CLIENT CLIENT_RDIFFBACKUP_VERSION miniade_debug 10 "main: CLIENT_RDIFFBACKUP_VERSION=$CLIENT_RDIFFBACKUP_VERSION" miniade_get_progname PROGNAME CACHE_DIR=$HOME/.cache/$PROGNAME # Guts # We could set RDIFFBACKUP_CMD to the native version if that matched the client's # version, but for simplicity we *always* build rdiff-backup. RDIFFBACKUP_CMD=$CACHE_DIR/$CLIENT_RDIFFBACKUP_VERSION/bin/rdiff-backup miniade_debug 10 "main: the diff-backup command this will invoke is $RDIFFBACKUP_CMD" if [ -x $RDIFFBACKUP_CMD ]; then : # rdiff-backup 2.0.5 is not compatible with Debian 12 because its source code won't # compile against Debian 12's python 3.11's libraries. See # https://forum.artixlinux.org/index.php/topic,5553.0.html. I tried pretty hard to # compile it but in the end, installing it in debootstrap was simpler, and then # modifying it so that, when called from *outside* the chroot environment, it still # called the python *inside* the chroot environment. elif [ $CLIENT_RDIFFBACKUP_VERSION = 2.0.5 -a $(lsb_release -sr 2>/dev/null) = 12 ]; then for CMD in debootstrap; do type -p $CMD > /dev/null || miniade_error "$CMD: not found" done miniade_info "rdiff-backup version $CLIENT_RDIFFBACKUP_VERSION not found, building (using debootstrap) ..." { # Create *temp* chroot CHROOT_DIR=$CACHE_DIR/$CLIENT_RDIFFBACKUP_VERSION rm -fr $CHROOT_DIR $CHROOT_DIR.tmp mkdir $CHROOT_DIR.tmp # Populate temp chroot debootstrap --arch amd64 bullseye $CHROOT_DIR.tmp http://deb.debian.org/debian/ > /dev/null mount -t proc proc $CHROOT_DIR.tmp/proc mount -t sysfs sysfs $CHROOT_DIR.tmp/sys cp /etc/hosts $CHROOT_DIR.tmp/etc/hosts rm -f $MY_CHROOT.tmp/etc/mtab # wasn't there but be sure ln -s ../proc/self/mounts $MY_CHROOT.tmp/etc/mtab chroot $CHROOT_DIR.tmp /bin/bash -c "apt-get -y install rdiff-backup" umount $CHROOT_DIR.tmp/sys umount $CHROOT_DIR.tmp/proc # Amend chroot-rdiff-backup source so that it refers to the chroot-python, # but in a way that it is callable from outside the chroot. sed -i "s@/usr/bin/python3@$CHROOT_DIR/usr/bin/python3.9@" $CHROOT_DIR.tmp/usr/bin/rdiff-backup # Move temp chroot to final location. mv $CHROOT_DIR.tmp $CHROOT_DIR } > /tmp/$PROGNAME.$$.log else # Other releases will hopefully be installable using python virtual environments. for CMD in virtualenv; do type -p $CMD > /dev/null || miniade_error "$CMD: not found" done miniade_info "rdiff-backup version $CLIENT_RDIFFBACKUP_VERSION not found, building (using virtualenv) ..." { miniade_debug 10 "main: calling [virtualenv -p python3.11 $CACHE_DIR/$CLIENT_RDIFFBACKUP_VERSION] ..." virtualenv -p python3.11 $CACHE_DIR/$CLIENT_RDIFFBACKUP_VERSION || true miniade_debug 10 "main: calling [$CACHE_DIR/$CLIENT_RDIFFBACKUP_VERSION/bin/pip3 install pyxattr pylibacl rdiff-backup==$CLIENT_RDIFFBACKUP_VERSION] ..." $CACHE_DIR/$CLIENT_RDIFFBACKUP_VERSION/bin/pip3 install pyxattr pylibacl rdiff-backup==$CLIENT_RDIFFBACKUP_VERSION || true } > /tmp/$PROGNAME.$$.log fi [ -x $RDIFFBACKUP_CMD ] || miniade_error "failed to build, see /tmp/$PROGNAME.$$.log for more info" # Construct command line. RDIFFBACKUP_CMDLINE=( $RDIFFBACKUP_CMD ) if [ $CLIENT_RDIFFBACKUP_VERSION = 2.0.5 ]; then { ! $FORCE_FLAG; } || RDIFFBACKUP_CMDLINE+=( --force ) [ "X$CURRENT_TIME" = X ] || RDIFFBACKUP_CMDLINE+=( --current-time "$CURRENT_TIME" ) [ "X$REMOTE_SCHEMA" = X ] || RDIFFBACKUP_CMDLINE+=( --remote-schema "$REMOTE_SCHEMA" ) [ "X$MODE" != Xlist ] || RDIFFBACKUP_CMDLINE+=( -l ) [ "X$MODE" != Xremote ] || RDIFFBACKUP_CMDLINE+=( --remove-older-than ) { ! $EXCLUDE_SOCKETS_FLAG; } || RDIFFBACKUP_CMDLINE+=( --exclude-sockets ) { ! $EXCLUDE_OTHER_FILESYSTEMS_FLAG; } || RDIFFBACKUP_CMDLINE+=( --exclude-other-filesystems ) elif [ $CLIENT_RDIFFBACKUP_VERSION = 2.2.2 ]; then RDIFFBACKUP_CMDLINE+=( --new ) { ! $FORCE_FLAG; } || RDIFFBACKUP_CMDLINE+=( --force ) [ "X$CURRENT_TIME" = X ] || RDIFFBACKUP_CMDLINE+=( --current-time "$CURRENT_TIME" ) [ "X$REMOTE_SCHEMA" = X ] || RDIFFBACKUP_CMDLINE+=( --remote-schema "${REMOTE_SCHEMA/--server/server}" ) [ "X$MODE" != Xlist ] || RDIFFBACKUP_CMDLINE+=( -l ) [ "X$MODE" != Xremote ] || RDIFFBACKUP_CMDLINE+=( --remove-older-than ) RDIFFBACKUP_CMDLINE+=( backup ) { ! $EXCLUDE_SOCKETS_FLAG; } || RDIFFBACKUP_CMDLINE+=( --exclude-sockets ) { ! $EXCLUDE_OTHER_FILESYSTEMS_FLAG; } || RDIFFBACKUP_CMDLINE+=( --exclude-other-filesystems ) else miniade_internal "should not reach here until Debian 13" fi miniade_debug 10 "main: calling [${RDIFFBACKUP_CMDLINE[*]} $*] ..." exec "${RDIFFBACKUP_CMDLINE[@]}" "$@" # This is in case that exec fails. miniade_error "CLIENT_RDIFFBACKUP_VERSION is set but associated command not found" } get_rdiffbackup_version() { local HOST local RDIFFBACKUP_VERSION_REF [ $# = 2 ] || miniade_internal "get_rdiffbackup_version: bad arg count ($#)" HOST=$1 RDIFFBACKUP_VERSION_REF=$2 case $HOST in chifferi) LOCAL_RDIFFBACKUP_VERSION=2.0.5 ;; *) LOCAL_RDIFFBACKUP_VERSION=2.2.2 ;; #*) miniade_error "$HOST: don't know host's version of rdiff-backup (hint: has the hostname been correctly calculated? is the client missing from this script's big case statement?)" ;; esac eval "$RDIFFBACKUP_VERSION_REF=\"$LOCAL_RDIFFBACKUP_VERSION\"" } help() { local PROGNAME miniade_get_progname PROGNAME echo "Usage: $PROGNAME [ ... ] ..." exit 0 } main "$@"