Files
Clément Désiles ceca06ff2e 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.
2026-09-01 22:20:27 +02:00

65 lines
2.0 KiB
Bash

# 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 %}
)