# -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*- # vim: set filetype=sh sw=3 sts=3 expandtab autoindent: # # borg handler script for backupninja # requires borgbackup # # Guillaume Subiron, Sysnove, 2016 # # Copyright 2016 Guillaume Subiron # # This work is free. You can redistribute it and/or modify it under the # terms of the Do What The Fuck You Want To Public License, Version 2, # as published by Sam Hocevar. See the http://www.wtfpl.net/ file for more details. # # debug "export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes" export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes debug "export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes" export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes ### GET CONFIG ### getconf testconnect yes getconf nicelevel 0 getconf ionicelevel getconf bwlimit setsection source getconf init yes getconf include getconf exclude getconf create_options getconf prune yes getconf keep 30d getconf prune_options getconf cache_directory getconf ignore_missing setsection dest getconf user getconf host getconf port 22 getconf directory # strip trailing / directory=${directory%/} getconf archive {now:%Y-%m-%dT%H:%M:%S} getconf compression lz4 getconf encryption none getconf passphrase getconf sshoptions debug "export BORG_PASSPHRASE=\"\"" export BORG_PASSPHRASE="$passphrase" if [ -n "$sshoptions" ]; then debug "export BORG_RSH=\"ssh $sshoptions\"" export BORG_RSH="ssh $sshoptions" fi ### CHECK CONFIG ### # source includes at least one path [ -n "$include" ] || fatal "No source includes specified" # destination specific checks [ -n "$directory" ] || fatal "Destination directory not set" if [ "$host" != "localhost" ]; then [ -n "$user" ] || fatal "Destination user not set" [ -n "$host" ] || fatal "Destination host not set" execstr_repository="ssh://${user}@${host}:${port}${directory}" else execstr_repository="$directory" fi execstr_archive="$archive" if [ -n "$cache_directory" ]; then cache_parent_dir=$(dirname "$(readlink -f "$cache_directory")") [ -d "$cache_parent_dir" ] || fatal "Cache directory parent dir '$cache_parent_dir' is absent or is not a directory." debug "export BORG_CACHE_DIR=\"${cache_directory}\"" export BORG_CACHE_DIR="$cache_directory" else # Cache dir not set, let's clear out the environment variable to avoid # having this directory be pointed to a random destination. # Also apparently if we set the variable to an empty string, borg uses the # empty string as though it was some path we specified and backup runs # error out, so we need to unset the variable completely. debug "unset BORG_CACHE_DIR" unset BORG_CACHE_DIR fi # Check that the ionicelevel is valid if [ -n "$ionicelevel" ] && echo "$ionicelevel" | grep -vq "^[0-7]$"; then fatal "The value of ionicelevel is expected to be either empty or an integer from 0 to 7. Got: $ionicelevel" fi # Only use ionice if ionicelevel is not empty nice="nice -n $nicelevel" if [ -n "$ionicelevel" ]; then nice="ionice -c2 -n $ionicelevel $nice" fi # check the connection at the source and destination [ -n "$test" ] || test=0 if [ "$host" != "localhost" ] && ([ "$testconnect" = "yes" ] || [ "${test}" -eq 1 ]); then debug "ssh $sshoptions -o PasswordAuthentication=no $host -p $port -l $user 'echo -n 1'" local ret=`ssh $sshoptions -o PasswordAuthentication=no $host -p $port -l $user 'echo -n 1'` if [ "$ret" = 1 ]; then debug "Connected to $host as $user successfully" else teststr="borg list --show-rc -v $execstr_repository" debug "$teststr" output=`su -c "$teststr" 2>&1` if echo "$output" | grep "terminating with success status" \ || echo "$output" | grep "^\S\+ is not a valid repository." \ || echo "$output" | grep "^Repository \S\+ does not exist."; then debug "Connected to $host as $user successfully (forced command)" else error $output fatal "Can't connect to $host as $user." fi fi fi ### INIT IF NEEDED ### if [ "$init" == "yes" ]; then initstr="borg init --encryption=$encryption $execstr_repository" debug "executing borg init" debug "$initstr" if [ $test = 0 ]; then output="`su -c "$initstr" 2>&1`" if [ $? = 2 ]; then debug $output info "Repository was already initialized" else warning $output warning "Repository has been initialized" fi fi fi ### EXECUTE ### execstr="borg create --stats --compression $compression" set -o noglob # includes SAVEIFS=$IFS IFS=$(echo -en "\n\b") for i in $include; do includes="${includes} '$i'" done IFS=$SAVEIFS # excludes SAVEIFS=$IFS IFS=$(echo -en "\n\b") for i in $exclude; do excludes="${excludes} --exclude '$i'" done IFS=$SAVEIFS set +o noglob if [ ! -z $bwlimit ]; then execstr="${execstr} --remote-ratelimit=${bwlimit}" fi if [ ! -z "$create_options" ]; then execstr="${execstr} ${create_options}" fi # include client-part and server-part execstr="${execstr} ${excludes} $execstr_repository::$execstr_archive ${includes}" debug "executing borg create" debug "$nice $execstr" if [ $test = 0 ]; then output=`$nice su -c "$execstr" 2>&1` ret=$? if [ $ret = 0 ]; then debug $output info "Successfully finished backing up source." elif [ $ret = 1 ]; then warnmsg=$(echo "$output" | @SED@ -n '1,/^-\+$/{x;p;d;}; x' | @SED@ '/^$/d') if [ "$ignore_missing" = "yes" ] && ! echo "$warnmsg" | grep -qv '\[Errno 2\] No such file or directory:'; then debug $output info "Backing up source finished with missing file warnings." else warning $output warning "Backing up source finished with warnings." fi else error $output fatal "Failed backing up source." fi fi ### REMOVE OLD BACKUPS ### # borg prune if [ "$prune" == "yes" ]; then if [ ! "$keep" == "0" ]; then prune_options="${prune_options} --keep-within=${keep}" fi prunestr="borg prune $prune_options $execstr_repository" debug "executing borg prune" debug "$prunestr" if [ $test = 0 ]; then output="`su -c "$prunestr" 2>&1`" ret=$? if [ $ret = 0 ]; then debug $output info "Removing old backups succeeded." elif [ $ret = 1 ]; then warning $output warning "Removing old backups finished with warnings." else error $output fatal "Failed removing old backups." fi fi fi debug "unset BORG_PASSPHRASE" unset BORG_PASSPHRASE debug "unset BORG_RSH" unset BORG_RSH return 0