feat(backup): add restic backup role with encrypted incremental snapshots
Restic-backed backups using a many-to-many repository/job model. - repository = destination: restic repository URL verbatim (s3, local, removable device), backend credentials via env passthrough, retention policy, and an optional per-repository ntfy notify channel - job = source set: paths, sqlite and postgres dumps (consistent .backup / pg_dump instead of torn live-file copies), pushed to one or more repos - systemd timer per job, per-repo + per-job flock serialisation, restic check with read_data_subset for rotating integrity verification Service roles declare what to back up (ntfy_backup_*, uptime_kuma_backup_*); the inventory declares where.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Managed by Ansible (roles/backup). Do not edit on the host.
|
||||
# Contains the repository passphrase{% if repo.env is defined and repo.env | length > 0 %} and backend credentials{% endif %}. Mode 0600, root-owned.
|
||||
#
|
||||
# Source this to run restic against this repository by hand:
|
||||
# set -a; . /etc/backup/repos/{{ repo.name }}.env; set +a
|
||||
|
||||
# Clear credential variables a previously-sourced repository may have set, so
|
||||
# env vars never leak from one repository into another.
|
||||
{% for key in backup_env_keys %}
|
||||
unset {{ key }}
|
||||
{% endfor %}
|
||||
|
||||
REPO_NAME={{ repo.name | quote }}
|
||||
|
||||
export RESTIC_REPOSITORY={{ repo.repository | quote }}
|
||||
|
||||
{% for key, value in (repo.env | default({})).items() %}
|
||||
export {{ key }}={{ value | string | quote }}
|
||||
{% endfor %}
|
||||
|
||||
{% if repo.mountpoint is defined %}
|
||||
REPO_DEVICE_UUID={{ repo.uuid | quote }}
|
||||
REPO_MOUNTPOINT={{ repo.mountpoint | quote }}
|
||||
{% if repo.fstype | default('') | length > 0 %}
|
||||
REPO_FSTYPE={{ repo.fstype | quote }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
export RESTIC_PASSWORD={{ repo.password | quote }}
|
||||
export RESTIC_CACHE_DIR={{ backup_cache_dir | quote }}
|
||||
|
||||
# Notification channel for this repository. Absent notify config -> disabled.
|
||||
{% if repo.notify is defined %}
|
||||
NTFY_ENABLED=true
|
||||
NTFY_SERVER={{ (repo.notify.server | default('https://ntfy.sh')) | quote }}
|
||||
NTFY_TOPIC={{ repo.notify.topic | quote }}
|
||||
NTFY_TOKEN={{ (repo.notify.token | default('')) | quote }}
|
||||
NTFY_ON_SUCCESS={{ (repo.notify.notify_success | default(false)) | lower }}
|
||||
{% else %}
|
||||
NTFY_ENABLED=false
|
||||
NTFY_SERVER=""
|
||||
NTFY_TOPIC=""
|
||||
NTFY_TOKEN=""
|
||||
NTFY_ON_SUCCESS=false
|
||||
{% endif %}
|
||||
|
||||
# Retention policy, applied per job tag by `restic forget`.
|
||||
REPO_RETENTION=(
|
||||
{% for key, value in repo.retention.items() %}
|
||||
--{{ key | replace('_', '-') }} {{ value }}
|
||||
{% endfor %}
|
||||
)
|
||||
|
||||
{% if repo.read_data_subset | default('') | length > 0 %}
|
||||
REPO_READ_DATA_SUBSET={{ repo.read_data_subset | quote }}
|
||||
{% else %}
|
||||
REPO_READ_DATA_SUBSET=""
|
||||
{% endif %}
|
||||
|
||||
REPO_OPTIONS=(
|
||||
{% for option in repo.options | default([]) %}
|
||||
{{ option | quote }}
|
||||
{% endfor %}
|
||||
)
|
||||
Reference in New Issue
Block a user