From e2c095eb7ddd23fe0ba47ce2d8c1f6149e299cc3 Mon Sep 17 00:00:00 2001 From: Martin Winkler Date: Mon, 1 Feb 2021 22:26:40 +0100 Subject: [PATCH] New seq for encrypted profile based backup management using duplicity --- seqs/ebackup.cfg.example | 62 +++++++++++ seqs/ebackup.sh | 231 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 seqs/ebackup.cfg.example create mode 100755 seqs/ebackup.sh diff --git a/seqs/ebackup.cfg.example b/seqs/ebackup.cfg.example new file mode 100644 index 0000000..14ac1a4 --- /dev/null +++ b/seqs/ebackup.cfg.example @@ -0,0 +1,62 @@ +#!/bin/bash + +# How often the backup job should be run +# Default is to run every day at 02:01 am +# (see man 5 crontab for syntax help) +# m h dom mon dow +#EBU_CRONTIME='1 2 * * *' + +# Passphrase for symmetrical(default) or asymmetrical encryption +EBU_PASSPHRASE= + +# scheme://[user[:password]@]host[:port]/[/]path +# e.g. +# file://[relative|/absolute]/local/path +# scp://user[:password]@other.host[:port]/[relative|/absolute]_path +# webdav[s]://user[:password]@other.host[:port]/some_dir +# alternatively try lftp+webdav[s]:// +EBU_TARGET_USER= +EBU_TARGET_PASS= +EBU_TARGET= + +# base directory to backup +EBU_SOURCE= + +# +## Age options + +# activates duplicity --full-if-older-than option (since duplicity v0.4.4.RC3) +# forces a full backup if last full backup reaches a specified age +# (see duplicity man page, chapter TIME_FORMATS) +#EBU_MAX_FULLBKP_AGE=1M + +# +## Purge options + +# Time frame for old backups to keep "remove-older-than" +# (see duplicity man page, chapter TIME_FORMATS) +#EBU_MAX_AGE=3M + +# Number of full backups to keep. +# (see duplicity man page, action "remove-all-but-n-full") +#EBU_MAX_FULL_BACKUPS=1 + +# Number of full backups for which incrementals will be kept for. +# (see duplicity man page, action "remove-all-inc-of-but-n-full") +#EBU_MAX_FULLS_WITH_INCRS=1 + +# +## Exclusion options + +# Standard excludes when backing up a full system +EBU_EXCLUDES=(\ + "/backup*"\ + "/dev/*"\ + "/proc/*"\ + "/sys/*"\ + "/tmp/*"\ + "/run/*"\ + "/mnt/*"\ + "/media/*"\ + "/lost+found"\ + ) diff --git a/seqs/ebackup.sh b/seqs/ebackup.sh new file mode 100755 index 0000000..96ce9f3 --- /dev/null +++ b/seqs/ebackup.sh @@ -0,0 +1,231 @@ +#!/bin/bash + +toolName=duplicity +toolPpa="ppa:duplicity-team/duplicity-release-git" +toolCronDir="/etc/cron.d" +toolCronPrefix="encBackup_" + +# Get script working directory +# (when called from a different directory) +WDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >>/dev/null 2>&1 && pwd )" +CONFIG=0 +SCRIPT_NAME=$(basename -- $0) +SCRIPT_NAME=${SCRIPT_NAME%%.*} +CONFIG_FILE_TEMPLATE="$WDIR/${SCRIPT_NAME}.cfg.example" + +step_config() { + initSeqConfig -p "$SCRIPT_NAME" "$CONFIG_FILE_TEMPLATE" + if [ $? -eq 0 ] ; then + CONFIG=1 + else + exit 1 + fi +} + +step_1_info() { + echo -n "Backup " + if [ $CONTEXT_HELP -ne 0 ] ; then + echo -n "selected profile" + else + echo -n "profile: $SEQ_PROFILE_NAME" + fi + echo " [OPTIONS] [full|incremental]" + echoinfo " [OPTIONS]" + echoinfo " --no-purge, -n : Do not purge old backups after backup" +} +step_1_alias() { ALIAS="backup"; } +step_1() { + checkInstalled + shift + + local arg + local dupArgs + local dupCommand + local purgeAfter=1 + + for arg in "$@" ; do + case "$1" in + --no-purge|-n) + purgeAfter=0 + shift + ;; + esac + done + + if [ -z $EBU_TARGET ] || [ -z $EBU_SOURCE ] ; then + echo " [I] Nothing to do. Check $SEQ_CONFIG_FILE" + return -1 + fi + + if [ ! -z "$1" ] && ( [ "$1" == "full" ] || [ "$1" == "incremental" ] ) ; then + dupCommand="$1" + elif [ ! -z "$1" ] ; then + echo " [W] $toolName command \"$1\" not recognized" + return -1 + fi + + echo " [I] Running backup profile [$SEQ_PROFILE_NAME]" + + if [ "$dupCommand" != "full" ] && [ ! -z "$EBU_MAX_FULLBKP_AGE" ] ; then + dupArgs+="--full-if-older-than=$EBU_MAX_FULLBKP_AGE " + fi + + setPassphrase + exe $toolName $dupCommand $dupArgs "$EBU_SOURCE" "$EBU_TARGET" + unsetPassphrase + + if [ $purgeAfter -ne 0 ] ; then + step purge + fi +} + +step_3_info() { echo "Restore [TARGET]"; } +step_3_alias() { ALIAS="restore"; } +step_3() { + shift + if [ -z "$1" ] ; then + echoerr " [E] No target provided" + return -1 + fi + local ebuTarget="$1" + + setPassphrase + exe $toolName restore "$EBU_TARGET" "$ebuTarget" + unsetPassphrase +} + +step_5_info() { echo "Purge old backups [TARGET]"; } +step_5_alias() { ALIAS="purge"; } +step_5() { + shift + local ebuTarget="$EBU_TARGET" + local dupCommand= + if [ ! -z "$1" ] ; then + ebuTarget="$1" + fi + + if [ ! -z "$EBU_MAX_AGE" ] ; then + dupCommand+="remove-older-than $EBU_MAX_AGE " + elif [ ! -z "$EBU_MAX_FULL_BACKUPS" ] ; then + dupCommand+="remove-all-but-n-full $EBU_MAX_FULL_BACKUPS " + elif [ ! -z "$EBU_MAX_FULLS_WITH_INCRS" ] ; then + dupCommand+="remove-all-but-n-full $EBU_MAX_FULLS_WITH_INCRS " + else + echoerr " [E] No purge option configured" + return -1 + fi + + setPassphrase + exe $toolName $dupCommand --force "$ebuTarget" + unsetPassphrase +} + +step_20_info() { echo "Status of [TARGET]"; } +step_20_alias() { ALIAS='status'; } +step_20() { + shift + local ebuTarget="$EBU_TARGET" + if [ ! -z "$1" ] ; then + ebuTarget="$1" + fi + + exe $toolName collection-status "$ebuTarget" +} + +step_22_info() { echo "List backup files [BACKUP TARGET]"; } +step_22_alias() { ALIAS='list'; } +step_22() { + shift + local ebuTarget="$EBU_TARGET" + if [ ! -z "$1" ] ; then + ebuTarget="$1" + fi + + exe $toolName list-current-files "$ebuTarget" +} + + +step_70_info() { + echo -n "Manage cron file for " + if [ $CONTEXT_HELP -ne 0 ] ; then + echo "selected profile" + else + echo "profile: $SEQ_PROFILE_NAME" + fi +} +step_70_alias() { ALIAS='cron'; } +step_70() { + local cronScript="$toolCronDir/${toolCronPrefix}$SEQ_PROFILE_NAME" + local cronEntry="$EBU_CRONTIME $(whoami) $WDIR/$(basename -- $0) -qq -p $SEQ_PROFILE_NAME >>/dev/null" + if [ -z "$EBU_CRONTIME" ] ; then + exe rm -r "$cronScript" + else + checkFileHead "$cronScript" "$EBU_CRONTIME" + if [ $? -ne 0 ] ; then + echo " [I] Update cron for profile $SEQ_PROFILE_NAME" + exep "sudo echo \"$cronEntry\" > \"$cronScript\"" + else + echo " [I] Cron for profile $SEQ_PROFILE_NAME is up to date" + fi + fi +} + +step_72_info() { echo "Update all profile cron files"; } +step_72_alias() { ALIAS="reload"; } +step_72() { + for seq in "$SEQ_CONFIG_HOME/"* ; do + seq=$(basename ${seq}) + $WDIR/$(basename -- $0) $SEQUENCER_ARGS -qq -p ${seq%%.*} cron + done +} + +step_74_info() { echo "Open configuration file"; } +step_74_alias() { ALIAS='config'; } +step_74() { + exe vi "$SEQ_CONFIG_FILE" +} + +step_100_info() { echo "Install $toolName $toolPpa"; } +step_100_alias() { ALIAS="install"; } +step_100() { + local aptOpt= + if [ $QUIET -ne 0 ] ; then + aptOpt="-y" + fi + + exe add-apt-repository $toolPpa $aptOpt + exe apt install $toolName $aptOpt +} + +setPassphrase() { + if [ -z $PASSPHRASE ] && [ ! -z $EBU_PASSPHRASE ] ; then + export PASSPHRASE="$EBU_PASSPHRASE" + fi +} + +unsetPassphrase() { + unset PASSPHRASE +} + +checkFileHead() { + local readChar + if [ ! -e "$1" ] ; then + echoerr " [E] File $1 not found" + return -1 + fi + read -r -n ${#2} readChar < "$1" + if [ "$readChar" == "$2" ] ; then + return 0 + fi + return 1 +} + +checkInstalled() { + command -v $toolName >>/dev/null + if [ $? -ne 0 ] ; then + step install + fi +} + +VERSION_SEQREV=12 +. /usr/local/bin/sequencer.sh