mirror of
https://github.com/roles-ansible/ansible_role_restic/
synced 2024-12-18 23:40:18 +01:00
c70e0d660a
Make sure backup source list is escaped
320 lines
12 KiB
Django/Jinja
320 lines
12 KiB
Django/Jinja
#!/usr/bin/env bash
|
|
# {{ ansible_managed }}
|
|
# Backup script for {{ item.src|default('stdin') }}
|
|
# Use this file to create a Backup and prune existing data with one execution.
|
|
|
|
pid="/var/run/restic_backup_{{ item.name | regex_replace('\'', '\'\\\'\'') }}.pid"
|
|
trap "rm -f $pid" SIGSEGV
|
|
trap "rm -f $pid" SIGINT
|
|
|
|
if [ -e $pid ]; then
|
|
echo "Another version of this restic backup script is already running!"
|
|
{% if item.mail_on_error is defined and item.mail_on_error == true %}
|
|
mail -s "starting restic backup failed on {{ ansible_hostname }}" {{ item.mail_address }} <<< "Another restic backup process is already running. We canceled starting a new restic backup script running at {{ ansible_hostname }} at $(date -u '+%Y-%m-%d %H:%M:%S').
|
|
{%- if item.src is defined -%}
|
|
{{ ' ' }}We tried to backup '{{ item.src }}'.
|
|
{%- endif -%}
|
|
{{ ' ' }}Please repair the restic-{{ item.name | replace(' ', '') }} job."
|
|
{% endif %}
|
|
exit # pid file exists, another instance is running, so now we politely exit
|
|
else
|
|
echo $$ > $pid # pid file doesn't exit, create one and go on
|
|
fi
|
|
# your normal workflow here...
|
|
|
|
|
|
{% if item.disable_logging is defined and item.disable_logging %}
|
|
{% set pre_backup_cmd_result_log, pre_backup_cmd_output_log = "/dev/null", "/dev/null" %}
|
|
{% set backup_result_log, backup_output_log = "/dev/null", "/dev/null" %}
|
|
{% set forget_result_log, forget_output_log = "/dev/null", "/dev/null" %}
|
|
{% else %}
|
|
{% if (item.log_to_journald is defined and item.log_to_journald) %}
|
|
{% set pre_backup_cmd_result_log, pre_backup_cmd_output_log = "| systemd-cat -t " + item.name, "2>&1 | systemd-cat -t " + item.name %}
|
|
{% set backup_result_log, backup_output_log = "| systemd-cat -t " + item.name, "2>&1 | systemd-cat -t " + item.name %}
|
|
{% set forget_result_log, forget_output_log = "| systemd-cat -t " + item.name, "2>&1 | systemd-cat -t " + item.name %}
|
|
{% else %}
|
|
{% set pre_backup_cmd_result_log, pre_backup_cmd_output_log = ">> " + restic_log_dir + "/" + item.name + "-pre_backup_cmd-result.log", "| tee " + restic_log_dir + "/" + item.name + "-pre_backup_cmd-output.log" %}
|
|
{% set backup_result_log, backup_output_log = ">> " + restic_log_dir + "/" + item.name + "-backup-result.log", "| tee " + restic_log_dir + "/" + item.name + "-backup-output.log" %}
|
|
{% set forget_result_log, forget_output_log = ">> " + restic_log_dir + "/" + item.name + "-forget-result.log", "| tee " + restic_log_dir + "/" + item.name + "-forget-output.log" %}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{% if restic__cache_config | bool -%}
|
|
export XDG_CACHE_HOME={{ restic__cache_dir }}
|
|
{% endif %}
|
|
|
|
{% if restic__limit_cpu_usage | bool -%}
|
|
# Maximum number of CPUs used by restic is limited to {{ restic__max_cpus }}
|
|
export GOMAXPROCS={{ restic__max_cpus }}
|
|
{% endif %}
|
|
|
|
export RESTIC_REPOSITORY={{ restic_repos[item.repo].location }}
|
|
export RESTIC_PASSWORD='{{ restic_repos[item.repo].password | regex_replace('\'', '\'\\\'\'') }}'
|
|
BACKUP_NAME={{ item.name }}
|
|
{% if restic_repos[item.repo].aws_access_key is defined %}
|
|
export AWS_ACCESS_KEY_ID={{ restic_repos[item.repo].aws_access_key }}
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].aws_secret_access_key is defined %}
|
|
export AWS_SECRET_ACCESS_KEY='{{ restic_repos[item.repo].aws_secret_access_key | regex_replace('\'', '\'\\\'\'') }}'
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].aws_default_region is defined %}
|
|
export AWS_DEFAULT_REGION={{ restic_repos[item.repo].aws_default_region }}
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].azure_account_name is defined %}
|
|
export AZURE_ACCOUNT_NAME={{ restic_repos[item.repo].azure_account_name }}
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].azure_account_key is defined %}
|
|
export AZURE_ACCOUNT_KEY={{ restic_repos[item.repo].azure_account_key }}
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].azure_account_sas is defined %}
|
|
export AZURE_ACCOUNT_SAS='{{ restic_repos[item.repo].azure_account_sas }}'
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].azure_endpoint_suffix is defined %}
|
|
export AZURE_ENDPOINT_SUFFIX={{ restic_repos[item.repo].azure_endpoint_suffix }}
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].b2_account_id is defined %}
|
|
export B2_ACCOUNT_ID={{ restic_repos[item.repo].b2_account_id }}
|
|
{% endif %}
|
|
{% if restic_repos[item.repo].b2_account_key is defined %}
|
|
export B2_ACCOUNT_KEY={{ restic_repos[item.repo].b2_account_key }}
|
|
{% endif %}
|
|
{% if item.src is defined and item.src is string %}
|
|
BACKUP_SOURCE={{ item.src }}
|
|
{% endif %}
|
|
{% if item.src is defined and item.src.__class__.__name__ =='list' %}
|
|
BACKUP_SOURCE='{{ item.src| join(' ') }}'
|
|
{% endif %}
|
|
|
|
{% if item.lvm is defined %}
|
|
# Set up functions for LVM.
|
|
|
|
function mount_opt_map {
|
|
mount_type="$1"
|
|
case "$mount_type" in
|
|
xfs)
|
|
echo "noatime,nouuid"
|
|
;;
|
|
ext4)
|
|
echo "noatime"
|
|
;;
|
|
*)
|
|
echo "noatime"
|
|
esac
|
|
}
|
|
|
|
function prepare_vol {
|
|
local path="$1"
|
|
[ -d "$path" ] || path="$(dirname "$path")"
|
|
if [ "$path" == '/' ] ; then
|
|
mkdir -p /rootfs;
|
|
newpath='/rootfs';
|
|
else
|
|
newpath="$path";
|
|
fi
|
|
|
|
{
|
|
local source="$(findmnt -J -T ${path} | jq -r '.filesystems[0].source')"
|
|
local target="$(findmnt -J -T ${path} | jq -r '.filesystems[0].target')"
|
|
subdir=${path##$target}
|
|
echo "Creating snapshot ..."
|
|
lvcreate -y -L "${size:-10G}" -s -n "${source}_snap" "${source}"
|
|
|
|
local tmpdir="$(mktemp -d)"
|
|
local fs="$(lsblk -J --fs "$source" | jq -r '.blockdevices[0]|.fstype')"
|
|
echo "Identified fstype: $fs; using opts $(mount_opt_map "$fs") ..."
|
|
mount -t "$fs" \
|
|
-o "$(mount_opt_map "$fs")" \
|
|
--make-private \
|
|
"${source}_snap" "${tmpdir}"
|
|
|
|
mount --bind --make-private "${tmpdir}/${subdir}" "${newpath}"
|
|
}
|
|
}
|
|
|
|
function cleanup_vol {
|
|
local path="$1"
|
|
[ -d "$path" ] || path="$(dirname "$path")"
|
|
if [ "$path" == '/' ] ; then
|
|
newpath='/rootfs';
|
|
else
|
|
newpath="$path";
|
|
fi
|
|
|
|
{
|
|
local source="$(findmnt -v -J -T "${newpath}" | jq -r '.filesystems[]|.source' | grep '_snap$')"
|
|
|
|
if ! grep -q '_snap$' <<< $source; then
|
|
echo "Snapshot for ${path} could not be found (found: ${source}). Exiting!" && return 1;
|
|
fi
|
|
|
|
echo "Cleaning up mount ..."
|
|
umount "${newpath}"
|
|
|
|
echo "Cleaning up snapshot ..."
|
|
umount "${source}"
|
|
lvremove -y "${source}";
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
set -uxo pipefail
|
|
{#
|
|
Define Tags
|
|
#}
|
|
{% macro tags(tags) -%}
|
|
{% if tags is defined and (tags|length>0) %}{% for tag in tags %} --tag {{ tag }}{% endfor %}{% endif %}
|
|
{%- endmacro %}
|
|
{#
|
|
Define Keeped Tags
|
|
#}
|
|
{% macro keep_tags(tags) -%}
|
|
{% if tags is defined and (tags|length>0) %}{% for tag in tags %} --keep-tag {{ tag }}{% endfor %}{% endif %}
|
|
{%- endmacro %}
|
|
{#
|
|
Define Hostname
|
|
#}
|
|
{% macro hostname(h) -%}
|
|
{% if h is defined %} --hostname {{ h }}{% endif %}
|
|
{%- endmacro %}
|
|
{#
|
|
Define stdin filename
|
|
#}
|
|
{% macro stdin_filename(n) -%}
|
|
{% if n is defined %} --stdin-filename {{ n }}{% endif %}
|
|
{%- endmacro %}
|
|
{#
|
|
Define paths
|
|
#}
|
|
{% macro paths(repo) -%}
|
|
{% if repo.src is defined and repo.src != None and (repo.src is not string) %}{%for path in repo.src %} --path {{ path }}{% endfor %}{%elif repo.src is string %}--path {{repo.src}} {% else %} --path {{ repo.stdin_filename }}{% endif %}
|
|
{%- endmacro %}
|
|
{#
|
|
Define retention pattern
|
|
#}
|
|
{% macro retention_pattern(repo) -%}
|
|
{% if repo.keep_last is defined and repo.keep_last != None %}--keep-last {{ item.keep_last }}{% endif %} \
|
|
{% if repo.keep_hourly is defined and repo.keep_hourly != None %}--keep-hourly {{ item.keep_hourly }}{% endif %} \
|
|
{% if repo.keep_daily is defined and repo.keep_daily != None %}--keep-daily {{ item.keep_daily }}{% endif %} \
|
|
{% if repo.keep_weekly is defined and repo.keep_weekly != None %}--keep-weekly {{ item.keep_weekly }}{% endif %} \
|
|
{% if repo.keep_monthly is defined and repo.keep_monthly != None %}--keep-monthly {{ item.keep_monthly }}{% endif %} \
|
|
{% if repo.keep_yearly is defined and repo.keep_yearly != None %}--keep-yearly {{ item.keep_yearly }}{% endif %} \
|
|
{% if repo.keep_within is defined and repo.keep_within != None %}--keep-within {{ item.keep_within }}{% endif %} \
|
|
{% if repo.keep_tag is defined and (repo.keep_tag|length>0) %}{{ keep_tags(repo.keep_tag) }}{% endif %}
|
|
{%- endmacro %}
|
|
|
|
{% macro exclude(exclude) %}
|
|
{% if exclude.exclude_caches is defined and exclude.exclude_caches == true %}
|
|
--exclude-caches \
|
|
{% endif %}
|
|
{% if exclude.exclude is defined %}
|
|
{% for path in exclude.exclude %}
|
|
--exclude {{ path }} \
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% if exclude.iexclude is defined %}
|
|
{% for path in exclude.iexclude %}
|
|
--iexclude {{ path }} \
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% if exclude.exclude_file is defined %}
|
|
{% for path in exclude.exclude_file %}
|
|
--exclude-file {{ path }} \
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% if exclude.exclude_if_present is defined %}
|
|
{% for path in exclude.exclude_if_present %}
|
|
--exclude-if-present {{ path }} \
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endmacro %}
|
|
|
|
|
|
{% if item.pre_backup_cmd is defined %}
|
|
{{ item.pre_backup_cmd }} {{ pre_backup_cmd_output_log }}
|
|
if [[ $? -eq 0 ]]
|
|
then
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') OK" {{ pre_backup_cmd_result_log }}
|
|
else
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') ERROR" {{ pre_backup_cmd_result_log }}
|
|
{% if item.mail_on_error is defined and item.mail_on_error == true %}
|
|
mail -s "restic backup failed on {{ ansible_hostname }}" {{ item.mail_address }} <<< "Something went wrong while running restic backup script running at {{ ansible_hostname }} at $(date -u '+%Y-%m-%d %H:%M:%S').
|
|
{%- if item.src is defined -%}
|
|
{{ ' ' }}We tried to backup '{{ item.src }}'.
|
|
{%- endif -%}
|
|
{{ ' ' }}Please repair the restic-{{ item.name | replace(' ', '') }} job."
|
|
{% endif %}
|
|
fi
|
|
{% endif %}
|
|
|
|
|
|
{#
|
|
Define backup commands
|
|
#}
|
|
if [[ -z ${CRON+x} ]]; then
|
|
MODE_TAG="--tag manual"
|
|
else
|
|
MODE_TAG="--tag cron"
|
|
fi
|
|
|
|
{% if item.stdin is defined and item.stdin == true %}
|
|
{{ item.stdin_cmd }} | {{ restic_install_path }}/restic backup \
|
|
--stdin $MODE_TAG \
|
|
{{ tags(item.tags) }} \
|
|
{{ stdin_filename(item.stdin_filename) }} \
|
|
{% if item.exclude is defined %}{{ exclude(item.exclude) }}{% endif %} \
|
|
$@ \
|
|
{% else %}
|
|
{
|
|
{% if item.lvm is defined %}prepare_vol $BACKUP_SOURCE &&{% endif %}
|
|
{{ restic_install_path }}/restic backup {% if item.lvm is defined and item.src == '/' %}/rootfs{% endif %}$BACKUP_SOURCE $MODE_TAG \
|
|
{{ tags(item.tags) }} \
|
|
{% if item.exclude is defined %}{{ exclude(item.exclude) }}{% endif %} \
|
|
$@
|
|
} \
|
|
{% endif %} {{ backup_output_log }}
|
|
|
|
case $? in
|
|
0)
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') OK" {{ backup_result_log }}
|
|
;;
|
|
3)
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') WARNING" {{ backup_result_log }}
|
|
;;
|
|
*)
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') ERROR" {{ backup_result_log }}
|
|
{% if item.mail_on_error is defined and item.mail_on_error == true %}
|
|
mail -s "restic backup failed on {{ ansible_hostname }}" {{ item.mail_address }} <<< "Something went wrong while running restic backup script running at {{ ansible_hostname }} at $(date -u '+%Y-%m-%d %H:%M:%S').
|
|
{%- if item.src is defined -%}
|
|
{{ ' ' }}We tried to backup '{{ item.src }}'.
|
|
{%- endif -%}
|
|
{{ ' ' }}Please repair the restic-{{ item.name | replace(' ', '') }} job."
|
|
{% endif %}
|
|
esac
|
|
|
|
{% if item.lvm is defined %}
|
|
cleanup_vol $BACKUP_SOURCE
|
|
{% endif %}
|
|
|
|
|
|
{#
|
|
Define stdin forget commands
|
|
#}
|
|
{{ restic_install_path }}/restic forget {{ paths(item) }} {{ retention_pattern(item) }} {% if item.prune is defined and item.prune == true %}--prune{% endif %} {{ forget_output_log }}
|
|
if [[ $? -eq 0 ]]
|
|
then
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') OK" {{ forget_result_log }}
|
|
{% if item.monitoring_call is defined %}
|
|
{{ item.monitoring_call }}
|
|
{% endif %}
|
|
else
|
|
echo "$(date -u '+%Y-%m-%d %H:%M:%S') ERROR" {{ forget_result_log }}
|
|
{% if item.mail_on_error is defined and item.mail_on_error == true %}
|
|
mail -s "restic backup failed on {{ ansible_hostname }}" {{ item.mail_address }} <<< "Something went wrong while running restic backup script running at {{ ansible_hostname }} at $(date -u '+%Y-%m-%d %H:%M:%S').
|
|
{%- if item.src is defined -%}
|
|
{{ ' ' }}We tried to backup '{{ item.src }}'.
|
|
{%- endif -%}
|
|
{{ ' ' }}Please repair the restic-{{ item.name | replace(' ', '') }} job."
|
|
{% endif %}
|
|
fi
|
|
rm -f $pid # remove pid file just before exiting
|
|
exit
|