#!/bin/bash BORGUSER="{{ backup_owner }}"; TARGETFOLDER="{{ backup_remotes_folder }}"; LOCALREPOLOCATION="{{ backup_location }}/{{ backup_app }}"; APPLIST="{{ backup_clients | json_query('*.app') | join(' ') }}"; declare -A BORG_CLIENT_MAP {% for client in backup_clients %} BORG_CLIENT_MAP[{{ backup_clients[client].app }}]="{{ client }}"; {% endfor %} declare -A BORG_USER_MAP {% for client in backup_clients %} BORG_USER_MAP[{{ backup_clients[client].app }}]="{{ backup_clients[client].owner }}"; {% endfor %} help (){ echo "cloud-server-backup - backup and restore script on cloud backup with borg target v1.0 by L.Hahn. Usage: $0 COMMAND [APPS] COMMAND: - remote-apps-list List available apps in remote borg repositories. - remote-list [APPS] List available archives for apps in remote borg repositories. - remote-prune [APPS] Prune remote app (or all) borg repositories. - list List available archives of remote repositories on local repository. - backup Perform backup of remote borg repositories and create a new archive in local borg repository. - prune Prune local borg backup of remote repositories. - restore ARCHIVENAME [APPS] Upload backup to borg repository to restore older file states. May turn off your application if still running. If latest is provided as ARCHIVENAME, the latest one based on timestamp is taken. IF ARCHIVENAME is provided, will try to download it; throws error if not found. If APP is provided, only the mentioned app repositories are restored. APPS: A single app or comma-separated (sub-)set of below listed apps to be considered. {% for client in backup_clients %} - {{ backup_clients[client].app }} {% endfor %} "; } ############################################################################### #=== HELP FUNCTIONS ==========================================================# ############################################################################### remote_delete_single_backup () { REPOLOCATION=$1; ARCHIVENAME=$2; BORG_PASSPHRASE=$3; KEYFILE=$4; echo "Remove archive $ARCHIVENAME"; sudo -H -u $BORGUSER bash -c ' ARCHIVENAME='$ARCHIVENAME'; export BORG_PASSPHRASE='$BORG_PASSPHRASE'; KEYFILE='$KEYFILE'; REPOLOCATION='$REPOLOCATION'; borg delete $REPOLOCATION::$ARCHIVENAME --rsh "/usr/bin/ssh -i $KEYFILE"'; } remote_free_space () { REPOLOCATION=$1; KEYFILE=$2; APP=$3; echo "Free space on repository for app '$APP'."; sudo -H -u $BORGUSER bash -c ' KEYFILE='$KEYFILE'; REPOLOCATION='$REPOLOCATION'; borg compact $REPOLOCATION --rsh "/usr/bin/ssh -i $KEYFILE"'; } local_delete_single_backup () { ARCHIVENAME=$1; echo "Remove archive $ARCHIVENAME"; sudo -H -u $BORGUSER bash -c ' ARCHIVENAME='$ARCHIVENAME'; export BORG_PASSPHRASE='$(cat {{ backup_home }}/.borg.key)'; REPOLOCATION='$LOCALREPOLOCATION'; borg delete $REPOLOCATION::$ARCHIVENAME'; } local_free_space () { echo "Free space on local repository."; sudo -H -u $BORGUSER bash -c ' REPOLOCATION='$LOCALREPOLOCATION'; borg compact $REPOLOCATION'; } has_argument () { if [[ -z "$1" ]]; then echo "Missing required paramter!"; exit 1; fi } validate_apps_or_default () { VALID=1; if [[ -z "$1" ]]; then echo $APPLIST; else CHECKLIST=$(echo $1 | tr ',' ' '); for APP in $CHECKLIST; do if [[ ! ${APPLIST[@]} =~ $APP ]] then VALID=0; echo "$APP is not a valid app."; fi done if [[ ! $VALID == 1 ]] then echo "Aborting."; exit 1; else echo $CHECKLIST; fi fi } ############################################################################### #=== USECASE FUNCTIONS =======================================================# ############################################################################### remote_list_app () { for APP in $APPLIST; do echo $APP; done } remote_list () { REMOTELIST=$1; for APP in $REMOTELIST; do REPOSITORYCLIENT=${BORG_CLIENT_MAP[$APP]} BORG_REPO_USER=${BORG_USER_MAP[$APP]} REPOLOCATION="ssh://$BORG_REPO_USER@{{ backup_host }}:{{ backup_ssh_port }}/./$APP" BORG_PASSPHRASE=$(cat {{ backup_key_folder }}/$APP) KEYFILE="{{ backup_home }}/.ssh/$REPOSITORYCLIENT" ARCHIVEIDS=$( sudo -H -u $BORGUSER bash -c ' export BORG_PASSPHRASE='$BORG_PASSPHRASE'; KEYFILE='$KEYFILE'; REPOLOCATION='$REPOLOCATION'; borg list $REPOLOCATION --rsh "/usr/bin/ssh -i $KEYFILE"' | sort -r ); echo "$ARCHIVEIDS"; done } remote_prune () { REMOTEPRUNELIST=$1; for APP in $REMOTEPRUNELIST; do ARCHIVELIST=$(remote_list $APP | cut -f 1 -d ' '); echo "$ARCHIVELIST" > {{ backup_inst }}/backuplist.txt; DELETELIST=$(python3 {{ backup_inst }}/filter_backups.py -f {{ backup_inst }}/backuplist.txt -d;) BORG_REPO_USER=${BORG_USER_MAP[$APP]} BORG_PASSPHRASE=$(cat {{ backup_key_folder }}/$APP) REPOLOCATION="ssh://$BORG_REPO_USER@{{ backup_host }}:{{ backup_ssh_port }}/./$APP" KEYFILE="{{ backup_home }}/.ssh/${BORG_CLIENT_MAP[$APP]}" for DELETEBACKUP in $DELETELIST; do remote_delete_single_backup $REPOLOCATION $DELETEBACKUP $BORG_PASSPHRASE $KEYFILE; done remote_free_space $REPOLOCATION $KEYFILE $APP; rm {{ backup_inst }}/backuplist.txt; done } local_list () { ARCHIVEIDS=$( sudo -H -u $BORGUSER bash -c ' REPOLOCATION='$LOCALREPOLOCATION'; export BORG_PASSPHRASE=$(cat {{ backup_home }}/.borg.key); borg list $REPOLOCATION' | grep {{ backup_app }} | sort -r); echo "$ARCHIVEIDS"; } local_prune () { ARCHIVELIST=$(local_list | cut -f 1 -d ' '); echo "$ARCHIVELIST" > {{ backup_inst }}/locallist.txt; DELETELIST=$(python3 {{ backup_inst }}/filter_backups.py -f {{ backup_inst }}/locallist.txt -d;) for DELETEBACKUP in $DELETELIST; do local_delete_single_backup $DELETEBACKUP; done local_free_space; rm {{ backup_inst }}/locallist.txt; } local_backup () { ARCHIVENAME="{{ backup_app }}-$(date '+%s')"; mkdir -p $TARGETFOLDER/$ARCHIVENAME; chown $BORGUSER: $TARGETFOLDER/$ARCHIVENAME; for APP in $APPLIST; do sudo -H -u $BORGUSER bash -c ' TARGETFOLDER='$TARGETFOLDER'; ARCHIVENAME='$ARCHIVENAME'; REPOLOCATION='${BORG_CLIENT_MAP[$APP]}':'$APP'; rsync -azr $REPOLOCATION $TARGETFOLDER/$ARCHIVENAME/;'; done sudo -H -u $BORGUSER bash -c ' TARGETFOLDER='$TARGETFOLDER'; REPOLOCATION='$LOCALREPOLOCATION'; ARCHIVENAME='$ARCHIVENAME'; export BORG_PASSPHRASE=$(cat {{ backup_home }}/.borg.key); borg create -C lzma $REPOLOCATION::$ARCHIVENAME $TARGETFOLDER/$ARCHIVENAME;'; rm -rf $TARGETFOLDER/$ARCHIVENAME; } local_delete () { ARCHIVENAME=$1; ARCHIVEIDS=$(local_list | cut -f 1 -d ' '); if [[ "$ARCHIVEIDS" != *$ARCHIVENAME* ]]; then echo "ERROR! Provided archivename '$ARCHIVENAME' is not part of the available archives! Aborting."; exit 1; fi local_delete_single_backup $ARCHIVENAME; } ############################################################################### #=== MAIN FUNCTIONS ==========================================================# ############################################################################### ( flock -n 9 || { echo "BACKUP ALREADY RUNNING! ABORTING."; exit 1; } if [ $# = 0 ]; then help; exit 0; fi action=$1 case $action in "remote-apps-list") remote_list_app; ;; "remote-list") USERLIST=$(validate_apps_or_default "$2"); remote_list "$USERLIST"; ;; "remote-prune") USERLIST=$(validate_apps_or_default "$2"); remote_prune "$USERLIST"; ;; "list") local_list; ;; "prune") local_prune; ;; "backup") local_backup; ;; "restore") has_argument $2; USERLIST=$(validate_apps_or_default $3); #local_restore $2; ;; "delete") has_argument $2; local_delete $2; ;; *) help; exit 0; ;; esac ) 9>/var/run/lock/cloud-server-backup.lock;