initial commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
75891c3271
129 changed files with 8046 additions and 0 deletions
29
roles/restic/defaults/main.yml
Normal file
29
roles/restic/defaults/main.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
restic_backend_type: "sftp"
|
||||
restic_password: ""
|
||||
# restic_repo: set explicitly when restic_backend_type is not 'sftp'
|
||||
|
||||
restic_backup_paths: >-
|
||||
{{
|
||||
['/etc/letsencrypt', '/srv/caddy']
|
||||
+ (['/etc/nebula'] if (enable_nebula | default(false)) else [])
|
||||
+ (['/srv/forgejo'] if (enable_forgejo | default(false)) else [])
|
||||
+ (['/srv/goaccess'] if (enable_goaccess | default(false)) else [])
|
||||
+ (['/srv/mail'] if (enable_mail | default(false)) else [])
|
||||
+ (['/srv/monitoring'] if (enable_monitoring | default(false)) else [])
|
||||
+ (['/srv/tuwunel'] if (enable_tuwunel | default(false)) else [])
|
||||
+ (['/srv/radicale'] if (enable_radicale | default(false)) else [])
|
||||
+ (['/srv/diun'] if (enable_diun | default(false)) else [])
|
||||
}}
|
||||
|
||||
restic_exclude_patterns:
|
||||
- "**/tmp"
|
||||
- "**/cache"
|
||||
- "**/*.gz"
|
||||
|
||||
restic_backup_time: "02:00:00"
|
||||
restic_prune_time: "04:00:00"
|
||||
|
||||
restic_retention:
|
||||
daily: 7
|
||||
weekly: 4
|
||||
monthly: 6
|
||||
1
roles/restic/files/restic_backup
Symbolic link
1
roles/restic/files/restic_backup
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/home/matthias/.ssh/island_restic_backup
|
||||
4
roles/restic/handlers/main.yml
Normal file
4
roles/restic/handlers/main.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
19
roles/restic/tasks/backend.yml
Normal file
19
roles/restic/tasks/backend.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
- name: Ensure restic local repo directory exists
|
||||
file:
|
||||
path: "{{ restic_repo }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
when: restic_repo is defined and restic_repo.startswith('/') # only local path
|
||||
|
||||
- name: Ensure restic repo is initialized
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
source /etc/restic/restic.env
|
||||
restic snapshots > /dev/null 2>&1 || restic init
|
||||
touch /etc/restic/.initialized
|
||||
args:
|
||||
creates: /etc/restic/.initialized
|
||||
|
||||
30
roles/restic/tasks/backend_sftp.yml
Normal file
30
roles/restic/tasks/backend_sftp.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
- name: Deploy Restic SSH key
|
||||
ansible.builtin.copy:
|
||||
src: restic_backup # local path in your playbook repo
|
||||
dest: "{{ restic_ssh_key }}" # e.g. /root/.ssh/restic_backup
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
|
||||
- name: Ensure restic repo directory exists on Storage Box
|
||||
ansible.builtin.shell: |
|
||||
ssh -i {{ restic_ssh_key }} -o BatchMode=yes -o StrictHostKeyChecking=no -p {{ restic_ssh_port }} {{ restic_user }}@{{ restic_host }} \
|
||||
"mkdir -p {{ restic_remote_path }} && chmod 700 {{ restic_remote_path }}" < /dev/null
|
||||
changed_when: false
|
||||
|
||||
- name: Write the ssh config for the root user
|
||||
# TODO: this replaces roots config and should be much smarter, safe for me currently
|
||||
template:
|
||||
src: restic-ssh-config.j2
|
||||
dest: /root/.ssh/config
|
||||
mode: "0644"
|
||||
|
||||
- name: Initialize restic repo on Storage Box (if needed)
|
||||
ansible.builtin.shell: |
|
||||
source /etc/restic/restic.env
|
||||
restic snapshots > /dev/null 2>&1 || restic init
|
||||
touch /etc/restic/.initialized
|
||||
args:
|
||||
creates: /etc/restic/.initialized
|
||||
|
||||
34
roles/restic/tasks/backup.yml
Normal file
34
roles/restic/tasks/backup.yml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
- name: Install restic backup service
|
||||
template:
|
||||
src: restic-backup.service.j2
|
||||
dest: /etc/systemd/system/restic-backup.service
|
||||
|
||||
- name: Install restic backup timer
|
||||
template:
|
||||
src: restic-backup.timer.j2
|
||||
dest: /etc/systemd/system/restic-backup.timer
|
||||
|
||||
- name: Enable and start restic backup timer
|
||||
systemd:
|
||||
name: restic-backup.timer
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
- name: Install restic prune service
|
||||
template:
|
||||
src: restic-prune.service.j2
|
||||
dest: /etc/systemd/system/restic-prune.service
|
||||
|
||||
- name: Install restic prune timer
|
||||
template:
|
||||
src: restic-prune.timer.j2
|
||||
dest: /etc/systemd/system/restic-prune.timer
|
||||
|
||||
- name: Enable and start restic prune timer
|
||||
systemd:
|
||||
name: restic-prune.timer
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
24
roles/restic/tasks/config.yml
Normal file
24
roles/restic/tasks/config.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
- name: Create restic config directory
|
||||
file:
|
||||
path: /etc/restic
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Write restic environment file
|
||||
template:
|
||||
src: restic.env.j2
|
||||
dest: /etc/restic/restic.env
|
||||
mode: "0600"
|
||||
|
||||
- name: Write restic backup script
|
||||
template:
|
||||
src: restic-backup.sh.j2
|
||||
dest: /usr/local/bin/restic-backup
|
||||
mode: "0750"
|
||||
|
||||
- name: Write restic prune script
|
||||
template:
|
||||
src: restic-prune.sh.j2
|
||||
dest: /usr/local/bin/restic-prune
|
||||
mode: "0750"
|
||||
|
||||
6
roles/restic/tasks/install.yml
Normal file
6
roles/restic/tasks/install.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
- name: Install restic
|
||||
apt:
|
||||
name: restic
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
15
roles/restic/tasks/main.yml
Normal file
15
roles/restic/tasks/main.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
- name: Install restic binary
|
||||
include_tasks: install.yml
|
||||
|
||||
- name: Configure restic environment
|
||||
include_tasks: config.yml
|
||||
|
||||
- name: Prepare backup repository
|
||||
include_tasks: "{{ backend_file }}"
|
||||
vars:
|
||||
backend_file: "{{ 'backend_sftp.yml' if restic_backend_type == 'sftp' else 'backend.yml' }}"
|
||||
|
||||
- name: Create systemd backup timer and service
|
||||
include_tasks: backup.yml
|
||||
|
||||
9
roles/restic/templates/restic-backup.service.j2
Normal file
9
roles/restic/templates/restic-backup.service.j2
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Restic Backup
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/restic-backup
|
||||
|
||||
59
roles/restic/templates/restic-backup.sh.j2
Normal file
59
roles/restic/templates/restic-backup.sh.j2
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source /etc/restic/restic.env
|
||||
|
||||
# Metrics file for node_exporter
|
||||
METRICS_DIR="/var/lib/node_exporter/textfile_collector"
|
||||
METRICS_FILE="${METRICS_DIR}/restic_backup.prom"
|
||||
mkdir -p "${METRICS_DIR}"
|
||||
|
||||
# Temporary file for atomic writes
|
||||
TEMP_FILE=$(mktemp)
|
||||
|
||||
# Start backup
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
if restic backup \
|
||||
{% for path in restic_backup_paths %}
|
||||
{{ path }} \
|
||||
{% endfor %}
|
||||
{% for pattern in restic_exclude_patterns %}
|
||||
--exclude '{{ pattern }}' \
|
||||
{% endfor %}
|
||||
--host {{ ansible_facts["hostname"] }}; then
|
||||
|
||||
# Backup succeeded
|
||||
STATUS=1
|
||||
echo "# HELP restic_backup_success Whether the last backup succeeded (1=success, 0=failure)" > "${TEMP_FILE}"
|
||||
echo "# TYPE restic_backup_success gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_backup_success ${STATUS}" >> "${TEMP_FILE}"
|
||||
|
||||
echo "# HELP restic_backup_timestamp_seconds Timestamp of last backup completion" >> "${TEMP_FILE}"
|
||||
echo "# TYPE restic_backup_timestamp_seconds gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_backup_timestamp_seconds $(date +%s)" >> "${TEMP_FILE}"
|
||||
|
||||
echo "# HELP restic_backup_duration_seconds Duration of last backup in seconds" >> "${TEMP_FILE}"
|
||||
echo "# TYPE restic_backup_duration_seconds gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_backup_duration_seconds $(($(date +%s) - START_TIME))" >> "${TEMP_FILE}"
|
||||
|
||||
# Move temp file to final location atomically
|
||||
mv "${TEMP_FILE}" "${METRICS_FILE}"
|
||||
|
||||
exit 0
|
||||
else
|
||||
# Backup failed
|
||||
STATUS=0
|
||||
echo "# HELP restic_backup_success Whether the last backup succeeded (1=success, 0=failure)" > "${TEMP_FILE}"
|
||||
echo "# TYPE restic_backup_success gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_backup_success ${STATUS}" >> "${TEMP_FILE}"
|
||||
|
||||
echo "# HELP restic_backup_timestamp_seconds Timestamp of last backup attempt" >> "${TEMP_FILE}"
|
||||
echo "# TYPE restic_backup_timestamp_seconds gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_backup_timestamp_seconds $(date +%s)" >> "${TEMP_FILE}"
|
||||
|
||||
# Move temp file to final location atomically
|
||||
mv "${TEMP_FILE}" "${METRICS_FILE}"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
10
roles/restic/templates/restic-backup.timer.j2
Normal file
10
roles/restic/templates/restic-backup.timer.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Daily Restic Backup
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* {{ restic_backup_time }}
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
9
roles/restic/templates/restic-prune.service.j2
Normal file
9
roles/restic/templates/restic-prune.service.j2
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Restic Prune
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/restic-prune
|
||||
|
||||
56
roles/restic/templates/restic-prune.sh.j2
Normal file
56
roles/restic/templates/restic-prune.sh.j2
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source /etc/restic/restic.env
|
||||
|
||||
# Metrics file for node_exporter
|
||||
METRICS_DIR="/var/lib/node_exporter/textfile_collector"
|
||||
METRICS_FILE="${METRICS_DIR}/restic_prune.prom"
|
||||
mkdir -p "${METRICS_DIR}"
|
||||
|
||||
# Temporary file for atomic writes
|
||||
TEMP_FILE=$(mktemp)
|
||||
|
||||
# Start prune
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
if restic forget \
|
||||
--keep-daily {{ restic_retention.daily }} \
|
||||
--keep-weekly {{ restic_retention.weekly }} \
|
||||
--keep-monthly {{ restic_retention.monthly }} \
|
||||
--prune; then
|
||||
|
||||
# Prune succeeded
|
||||
STATUS=1
|
||||
echo "# HELP restic_prune_success Whether the last prune succeeded (1=success, 0=failure)" > "${TEMP_FILE}"
|
||||
echo "# TYPE restic_prune_success gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_prune_success ${STATUS}" >> "${TEMP_FILE}"
|
||||
|
||||
echo "# HELP restic_prune_timestamp_seconds Timestamp of last prune completion" >> "${TEMP_FILE}"
|
||||
echo "# TYPE restic_prune_timestamp_seconds gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_prune_timestamp_seconds $(date +%s)" >> "${TEMP_FILE}"
|
||||
|
||||
echo "# HELP restic_prune_duration_seconds Duration of last prune in seconds" >> "${TEMP_FILE}"
|
||||
echo "# TYPE restic_prune_duration_seconds gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_prune_duration_seconds $(($(date +%s) - START_TIME))" >> "${TEMP_FILE}"
|
||||
|
||||
# Move temp file to final location atomically
|
||||
mv "${TEMP_FILE}" "${METRICS_FILE}"
|
||||
|
||||
exit 0
|
||||
else
|
||||
# Prune failed
|
||||
STATUS=0
|
||||
echo "# HELP restic_prune_success Whether the last prune succeeded (1=success, 0=failure)" > "${TEMP_FILE}"
|
||||
echo "# TYPE restic_prune_success gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_prune_success ${STATUS}" >> "${TEMP_FILE}"
|
||||
|
||||
echo "# HELP restic_prune_timestamp_seconds Timestamp of last prune attempt" >> "${TEMP_FILE}"
|
||||
echo "# TYPE restic_prune_timestamp_seconds gauge" >> "${TEMP_FILE}"
|
||||
echo "restic_prune_timestamp_seconds $(date +%s)" >> "${TEMP_FILE}"
|
||||
|
||||
# Move temp file to final location atomically
|
||||
mv "${TEMP_FILE}" "${METRICS_FILE}"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
10
roles/restic/templates/restic-prune.timer.j2
Normal file
10
roles/restic/templates/restic-prune.timer.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Daily Restic Prune
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* {{ restic_prune_time }}
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
4
roles/restic/templates/restic-ssh-config.j2
Normal file
4
roles/restic/templates/restic-ssh-config.j2
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
Host {{ restic_host }}
|
||||
IdentityFile {{ restic_ssh_key }}
|
||||
User {{ restic_user }}
|
||||
Port {{ restic_ssh_port }}
|
||||
7
roles/restic/templates/restic.env.j2
Normal file
7
roles/restic/templates/restic.env.j2
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% if restic_backend_type == 'sftp' %}
|
||||
export RESTIC_REPOSITORY="sftp:{{ restic_user }}@{{ restic_host }}:{{ restic_remote_path }}"
|
||||
{% else %}
|
||||
export RESTIC_REPOSITORY="{{ restic_repo }}"
|
||||
{% endif %}
|
||||
export RESTIC_PASSWORD="{{ restic_password }}"
|
||||
export RESTIC_CACHE_DIR=/var/cache/restic
|
||||
Loading…
Add table
Add a link
Reference in a new issue