# -*- mode: sh; sh-basic-offset: 2; indent-tabs-mode: nil; -*- # vim: set filetype=sh sw=2 sts=2 expandtab autoindent: # # restic script for backupninja # ### GETCONF ################################################################### setsection general getconf testconnect yes getconf nicelevel getconf ionicelevel getconf run_backup "no" getconf run_forget "no" getconf run_check "no" getconf run_prune "no" getconf cacert getconf cache_dir getconf cleanup_cache getconf json getconf limit_download getconf limit_upload getconf no_cache getconf no_lock getconf option getconf password getconf password_file getconf quiet getconf repository getconf tls_client_cert setsection s3 getconf aws_access_key_id getconf aws_secret_access_key getconf aws_session_token setsection swift getconf os_auth_url getconf os_tenant_id getconf os_tenant_name getconf os_username getconf os_password getconf os_region_name setsection b2 getconf b2_account_id getconf b2_account_key setsection azure getconf azure_account_name getconf azure_account_key setsection gs getconf google_project_id getconf google_application_credentials # Check that the ionicelevel is valid if [ -n "$nicelevel" ] && { [ "$nicelevel" -lt -20 ] || [ "$nicelevel" -gt 19 ]; }; then fatal "The value of nicelevel is expected to be either empty or an integer from -20 to 19. Got: $nicelevel" 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 ### HELPERS ################################################################### function export_debug { export "$1"="$2" debug "$1=${!1}" } function test_sftp_connection() { local remote local sftpport local output local ret remote=$1 if echo "$remote" | grep -q ':[0-9]\+$'; then sftpport="-P $(echo $remote | @AWK@ -F ':' '{print $2}') " remote=$(echo $remote | @AWK@ -F ':' '{print $1}') fi debug "echo | sftp -o PasswordAuthentication=no ${sftpport}${remote}" output=$(echo | sftp -o PasswordAuthentication=no ${sftpport}${remote}) ret=$? if [ $ret -eq 0 ]; then debug "Connection test to SFTP backend ${remote} succeeded." else debug $output fatal "Failed connecting to SFTP backend at ${remote}." fi } function test_rest_connection() { local remote local output local ret local remote=$1 if [ ! -x $(which wget) ]; then error "Unable to test REST connection, wget executable not found!" return fi debug "wget --tries=1 --method=HEAD \"${remote}/config\"" output=$(wget --tries=1 --method=HEAD "${remote}/config") ret=$? if [ $ret -eq 0 ]; then debug "Connection test to REST backend at ${remote} succeeded." elif [ $ret -eq 8 ]; then debug $output fatal "REST backend at ${remote} returned an error, repository may not be created." else debug $output fatal "Failed connecting to REST backend at ${remote}." fi } ### PRE-COMMANDS ############################################################## [ -n "$nicelevel" ] && \ precmd+="nice -n $nicelevel " [ -n "$ionicelevel" ] && \ precmd+="ionice -c2 -n $ionicelevel " ### GLOBAL OPTIONS ############################################################ [ -z "$repository" ] && \ fatal "The repository option must be set." [ -z "$password" ] && [ -z "$password_file" ] && \ fatal "The password must be set by option 'password' or 'password_file'." [ -n "$repository" ] && \ cmd_global_options+="--repo $repository " [ -n "$password" ] && \ export_debug RESTIC_PASSWORD "$password" [ -n "$password_file" ] && \ cmd_global_options+="--password-file $password_file " [ -n "$cacert" ] && \ cmd_global_options+="--cacert $cacert " [ -n "$cache_dir" ] && \ cmd_global_options+="--cache-dir $cache_dir " [ -n "$cleanup_cache" ] && \ cmd_global_options+="--cleanup-cache " [ -n "$json" ] && \ cmd_global_options+="--json " [ -n "$limit_download" ] && \ cmd_global_options+="--limit-download $limit_download " [ -n "$limit_upload" ] && \ cmd_global_options+="--limit-upload $limit_upload " [ -n "$no_cache" ] && \ cmd_global_options+="--no-cache " [ -n "$no_lock" ] && \ cmd_global_options+="--no-lock " [ -n "$option" ] && \ cmd_global_options+="$(for i in "${option[@]}"; do echo "--option $i "; done)" [ -n "$quiet" ] && \ cmd_global_options+="--quiet " [ -n "$tls_client_cert" ] && \ cmd_global_options+="--tls-client-cert $tls_client_cert " ### REPOSITORY ################################################################ # SFTP repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "sftp" ]; then # connection test if [ "$testconnect" = "yes" ] || [ "$test" -eq 1 ]; then if echo "$repository" | grep -q '^sftp://'; then remote=$(echo "$repository" | @AWK@ -F '//' '{print $2}') elif echo "$repository" | grep -q '^sftp:'; then remote=$(echo "$repository" | @AWK@ -F ':' '{print $2}') fi [ -n "$remote" ] || fatal "Unable to parse SFTP repository URL ${repository}." test_sftp_connection $remote fi fi # REST Server repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "rest" ]; then # connection test if [ "$testconnect" = "yes" ] || [ "$test" -eq 1 ]; then remote="${repository#rest:}" test_rest_connection $remote fi fi # Amazon S3 repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "s3" ]; then ( [ -z "$aws_access_key_id" ] || [ -z "$aws_secret_access_key" ] ) && \ fatal "Missing some S3 credentials." export_debug AWS_ACCESS_KEY_ID "$aws_access_key_id" export_debug AWS_SECRET_ACCESS_KEY "$aws_secret_access_key" [ -n "$aws_session_token" ] && \ export_debug AWS_SESSION_TOKEN "$aws_session_token" fi # OpenStack Swift repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "swift" ]; then ( [ -z "$os_auth_url" ] || [ -z "$os_tenant_id" ] || [ -z "$os_tenant_name" ] || \ [ -z "$os_username" ] || [ -z "$os_password" ] || [ -z "$os_region_name" ] ) && \ fatal "Missing some Swift credentials." export_debug OS_AUTH_URL "$os_auth_url" export_debug OS_TENANT_ID "$os_tenant_id" export_debug OS_TENANT_NAME "$os_tenant_name" export_debug OS_USERNAME "$os_username" export_debug OS_PASSWORD "$os_password" export_debug OS_REGION_NAME "$os_region_name" fi # Backblaze B2 repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "b2" ]; then ( [ -z "$b2_account_id" ] || [ -z "$b2_account_key" ] ) && \ fatal "Missing some B2 credentials." export_debug B2_ACCOUNT_ID "$b2_account_id" export_debug B2_ACCOUNT_KEY "$b2_account_key" fi # Microsoft Azure Blob Storage repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "azure" ]; then ( [ -z "$azure_account_name" ] || [ -z "$azure_account_key" ] ) && \ fatal "Missing some Azure credentials." export_debug AZURE_ACCOUNT_NAME "$azure_account_name" export_debug AZURE_ACCOUNT_KEY "$azure_account_key" fi # Google Cloud Storage repository if [ "$(echo "$repository" | @AWK@ -F ':' '{print $1}')" == "gs" ]; then ( [ -z "$google_project_id" ] || [ -z "$google_application_credentials" ] ) && \ fatal "Missing some Google credentials." export_debug GOOGLE_PROJECT_ID "$google_project_id" export_debug GOOGLE_APPLICATION_CREDENTIALS "$google_application_credentials" fi ### BACKUP ##################################################################### if [ "$run_backup" == "yes" ]; then setsection backup getconf include "/" getconf exclude "/dev /lost+found /media /mnt /proc /run /sys /tmp /var/cache /var/lock /var/spool /var/run /var/tmp" getconf exclude_caches getconf exclude_file getconf exclude_if_present getconf files_from getconf force getconf hostname getconf one_file_system getconf parent getconf tag getconf time getconf with_atime # prevent bash from expanding glob set -f [ -z "$include" ] && [ -z "$files_from" ] && \ fatal "No files or directories specified for backup." [ -n "$include" ] && \ cmd_options+="$(for i in $include; do echo "$i "; done)" [ -n "$files_from" ] && \ cmd_options+="$(for i in $files_from; do echo "--files-from $i "; done)" [ -d "$repository" ] && \ cmd_options+="--exclude $repository " [ -n "$exclude" ] && \ cmd_options+="$(for i in $exclude; do echo "--exclude $i "; done)" [ "$exclude_caches" == "yes" ] && \ cmd_options+="--exclude-caches " [ -n "$exclude_file" ] && \ cmd_options+="$(for i in $exclude_file; do echo "--exclude-file $i "; done)" [ -n "$exclude_if_present" ] && \ cmd_options+="$(for i in $exclude_if_present; do echo "--exclude-if-present $i "; done)" [ "$force" == "yes" ] && \ cmd_options+="--force " [ -n "$hostname" ] && \ cmd_options+="--hostname $hostname " [ -n "$one_file_system" ] && \ cmd_options+="--one-file-system " [ -n "$parent" ] && \ cmd_options+="--parent $parent " [ -n "$tag" ] && \ cmd_options+="$(for i in $tag; do echo "--tag=$i "; done)" [ -n "$time" ] && \ cmd_options+="--time $time " [ "$with_atime" == "yes" ] && \ cmd_options+="--with_atime " # format command cmd="restic backup" execstr="${precmd}${cmd} ${cmd_global_options//$'\n'}${cmd_options//$'\n'}" # debug debug "executing restic backup" debug "$execstr" # execute if [ $test -eq 1 ]; then info "Test mode enabled, skipping restic backup." else info "Creating new backup snapshot." output=$($execstr 2>&1) ret=$? if [ $ret -eq 0 ]; then debug $output info "Restic backup successful." else error $output fatal "Restic backup failed." fi fi # set bash orginal globbing behavior set +f debug "Unsetting variables" unset cmd_options unset execstr unset output unset ret fi ### FORGET ##################################################################### if [[ "$run_forget" == "yes" ]]; then setsection forget getconf keep_last "7" getconf keep_hourly getconf keep_daily getconf keep_weekly getconf keep_monthly getconf keep_yearly getconf keep_within getconf keep_tag getconf host getconf tag getconf path getconf compact getconf group_by getconf dry_run getconf prune [ -n "$keep_last" ] && \ cmd_options+="--keep-last $keep_last " [ -n "$keep_hourly" ] && \ cmd_options+="--keep-hourly $keep_hourly " [ -n "$keep_daily" ] && \ cmd_options+="--keep-daily $keep_daily " [ -n "$keep_weekly" ] && \ cmd_options+="--keep-weekly $keep_weekly " [ -n "$keep_monthly" ] && \ cmd_options+="--keep-monthly $keep_monthly " [ -n "$keep_yearly" ] && \ cmd_options+="--keep-yearly $keep_yearly " [ -n "$keep_within" ] && \ cmd_options+="--keep-within $keep_within " [ -n "$keep_tag" ] && \ cmd_options+="$(for i in $keep_tag; do echo "--keep-tag=$i "; done)" [ -n "$host" ] && \ cmd_options+="--host $host " [ -n "$tag" ] && \ cmd_options+="$(for i in $tag; do echo "--tag=$i "; done)" [ -n "$path" ] && \ cmd_options+="$(for i in $path; do echo "--path=$i "; done)" [ -n "$compact" ] && \ cmd_options+="--compact " [ -n "$group_by" ] && \ cmd_options+="--group-by $group_by " [ -n "$dry_run" ] && \ cmd_options+="--dry-run " [ -n "$prune" ] && \ cmd_options+="--prune " # format command cmd="restic forget" execstr="${precmd}${cmd} ${cmd_global_options//$'\n'}${cmd_options//$'\n'}" # debug debug "executing restic forget" debug "$execstr" # execute if [ $test -eq 1 ]; then info "Test mode enabled, skipping restic forget." else info "Removing old snapshots based on defined retention policy." output=$($execstr 2>&1) ret=$? if [ $ret -eq 0 ]; then debug $output info "Restic forget successful." else error $output fatal "Restic forget failed." fi fi debug "Unsetting variables" unset cmd_options unset execstr unset output unset ret fi ### CHECK ###################################################################### if [ "$run_check" == "yes" ]; then setsection check getconf check_unused getconf read_data getconf read_data_subset getconf with_cache [ -n "$check_unused" ] && \ cmd_options+="--check-unused " [ -n "$read_data" ] && \ cmd_options+="--read-data " [ -n "$read_data_subset" ] && \ cmd_options+="--read-data-subset $read_data_subset " [ -n "$with_cache" ] && \ cmd_options+="--with-cache " # format command cmd="restic check" execstr="${precmd}${cmd} ${cmd_global_options//$'\n'}${cmd_options//$'\n'}" # debug debug "executing restic check" debug "$execstr" # execute if [ $test -eq 1 ]; then info "Test mode enabled, skipping restic check." else info "Checking repository integrity and consistency." output=$($execstr 2>&1) ret=$? if [ $ret -eq 0 ]; then debug $output info "Restic check successful." else error $output fatal "Restic check failed." fi fi debug "Unsetting variables" unset cmd_options unset execstr unset output unset ret fi ### PRUNE ###################################################################### if [ "$run_prune" == "yes" ]; then # format command cmd="restic prune" execstr="${precmd}${cmd} ${cmd_global_options//$'\n'}" # debug debug "executing restic prune" debug "$execstr" # execute if [ $test -eq 1 ]; then info "Test mode enabled, skipping restic prune." else info "Removing unreferenced data from repository." output=$($execstr 2>&1) ret=$? if [ $ret -eq 0 ]; then debug $output info "Restic prune successful." else error $output fatal "Restic prune failed." fi fi debug "Unsetting variables" unset execstr unset output unset ret fi ### CLEAN UP ################################################################### debug "Unsetting environment variables" unset RESTIC_PASSWORD unset AWS_ACCESS_KEY_ID unset AWS_SECRET_ACCESS_KEY unset AWS_SESSION_TOKEN unset OS_AUTH_URL unset OS_TENANT_ID unset OS_TENANT_NAME unset OS_USERNAME unset OS_PASSWORD unset OS_REGION_NAME unset B2_ACCOUNT_ID unset B2_ACCOUNT_KEY unset AZURE_ACCOUNT_NAME unset AZURE_ACCOUNT_KEY unset GOOGLE_PROJECT_ID unset GOOGLE_APPLICATION_CREDENTIALS unset cmd_global_options return 0