From 8d96529d8f20aaa74809b03237453304e128358b Mon Sep 17 00:00:00 2001 From: alessandrovitali Date: Thu, 9 Jul 2026 15:51:45 +0200 Subject: [PATCH 1/4] feat: add backup role using restic --- roles/backup/tasks/main.yml | 31 +++++++++++++++++++ .../backup/templates/restic-backup.service.j2 | 7 +++++ roles/backup/templates/restic-backup.sh.j2 | 23 ++++++++++++++ roles/backup/templates/restic-backup.timer.j2 | 10 ++++++ 4 files changed, 71 insertions(+) create mode 100644 roles/backup/tasks/main.yml create mode 100644 roles/backup/templates/restic-backup.service.j2 create mode 100644 roles/backup/templates/restic-backup.sh.j2 create mode 100644 roles/backup/templates/restic-backup.timer.j2 diff --git a/roles/backup/tasks/main.yml b/roles/backup/tasks/main.yml new file mode 100644 index 0000000..e2954dc --- /dev/null +++ b/roles/backup/tasks/main.yml @@ -0,0 +1,31 @@ +--- +- name: Install Restic + ansible.builtin.apt: + name: restic + state: present + become: true + +- name: Deploy restic backup script + ansible.builtin.template: + src: restic-backup.sh.j2 + dest: /usr/local/bin/restic-backup.sh + mode: "0700" # Only root can read/execute this because it contains secrets + +- name: Deploy restic systemd service + ansible.builtin.template: + src: restic-backup.service.j2 + dest: /etc/systemd/system/restic-backup.service + mode: "0644" + +- name: Deploy restic systemd timer + ansible.builtin.template: + src: restic-backup.timer.j2 + dest: /etc/systemd/system/restic-backup.timer + mode: "0644" + +- name: Enable and start backup timer + ansible.builtin.systemd: + name: restic-backup.timer + enabled: yes + state: started + daemon_reload: yes diff --git a/roles/backup/templates/restic-backup.service.j2 b/roles/backup/templates/restic-backup.service.j2 new file mode 100644 index 0000000..c0c0020 --- /dev/null +++ b/roles/backup/templates/restic-backup.service.j2 @@ -0,0 +1,7 @@ +[Unit] +Description=Run Restic Backup +After=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/restic-backup.sh diff --git a/roles/backup/templates/restic-backup.sh.j2 b/roles/backup/templates/restic-backup.sh.j2 new file mode 100644 index 0000000..1f33c22 --- /dev/null +++ b/roles/backup/templates/restic-backup.sh.j2 @@ -0,0 +1,23 @@ +#!/bin/bash +## Ansible managed + +## Secrets are passed via Ansible Vault variables +export RESTIC_REPOSITORY="{{ vault_restic_repo_url }}" +export RESTIC_PASSWORD="{{ vault_restic_password }}" +export AWS_ACCESS_KEY_ID="{{ vault_aws_access_key }}" +export AWS_SECRET_ACCESS_KEY="{{ vault_aws_secret_key }}" + +set -e + +echo "Starting Restic Backup at $(date)" + +## Run the backup (docker volumes & config files) +restic backup \ + /etc \ + /opt/docker-data \ + --exclude /opt/docker-data/tmp \ + --cleanup-cache + +restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 + +echo "Restic backup completed at $(date)" diff --git a/roles/backup/templates/restic-backup.timer.j2 b/roles/backup/templates/restic-backup.timer.j2 new file mode 100644 index 0000000..b9ca4c1 --- /dev/null +++ b/roles/backup/templates/restic-backup.timer.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=Run Restic Backup Daily at 4:00 AM + +[Timer] +OnCalendar=*-*-* 04:00:00 +Persistent=true +RandomizedDelaySec=1800 + +[Install] +WantedBy=timers.target -- 2.54.0 From c4c8d6de7f83714781164cd0e5b134f9babed517 Mon Sep 17 00:00:00 2001 From: alessandrovitali Date: Thu, 9 Jul 2026 16:42:10 +0200 Subject: [PATCH 2/4] feat: make backup role universal by passing paths as variables --- roles/backup/defaults/main.yml | 14 ++++++++++++++ roles/backup/templates/restic-backup.sh.j2 | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 roles/backup/defaults/main.yml diff --git a/roles/backup/defaults/main.yml b/roles/backup/defaults/main.yml new file mode 100644 index 0000000..25bf917 --- /dev/null +++ b/roles/backup/defaults/main.yml @@ -0,0 +1,14 @@ +# ansible/roles/backup/defaults/main.yml +--- +## Directories to include in the backup +backup_include_paths: + - /etc + - /opt/docker + +## Directories to exclude from the backup +backup_exclude_paths: [] ## Nothing to exclude + +## Restic retention policy +backup_retention_daily: 7 +backup_retention_weekly: 4 +backup_retention_monthly: 6 diff --git a/roles/backup/templates/restic-backup.sh.j2 b/roles/backup/templates/restic-backup.sh.j2 index 1f33c22..8921f25 100644 --- a/roles/backup/templates/restic-backup.sh.j2 +++ b/roles/backup/templates/restic-backup.sh.j2 @@ -11,13 +11,20 @@ set -e echo "Starting Restic Backup at $(date)" -## Run the backup (docker volumes & config files) +## Run the backup using paths and exclusions defined in group_vars restic backup \ - /etc \ - /opt/docker-data \ - --exclude /opt/docker-data/tmp \ +{% for path in backup_include_paths %} + "{{ path }}" \ +{% endfor %} +{% for path in backup_exclude_paths %} + --exclude "{{ path }}" \ +{% endfor %} --cleanup-cache -restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6 +restic forget \ + --prune \ + --keep-daily {{ backup_retention_daily }} \ + --keep-weekly {{ backup_retention_weekly }} \ + --keep-monthly {{ backup_retention_monthly }} echo "Restic backup completed at $(date)" -- 2.54.0 From 71a02e0f8651178c3d8797b62b847d2c198fdcae Mon Sep 17 00:00:00 2001 From: alessandrovitali Date: Sun, 12 Jul 2026 23:46:29 +0200 Subject: [PATCH 3/4] refactor: harden restic configuration move secrets to .env file so they work on their own, initialize restic repo on fresh deployments --- roles/backup/tasks/main.yml | 36 +++++++++++++++++-- roles/backup/templates/restic-backup.env.j2 | 5 +++ .../backup/templates/restic-backup.service.j2 | 1 + roles/backup/templates/restic-backup.sh.j2 | 14 ++++---- roles/backup/templates/restic-backup.timer.j2 | 2 +- 5 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 roles/backup/templates/restic-backup.env.j2 diff --git a/roles/backup/tasks/main.yml b/roles/backup/tasks/main.yml index e2954dc..db8beb1 100644 --- a/roles/backup/tasks/main.yml +++ b/roles/backup/tasks/main.yml @@ -1,26 +1,56 @@ --- -- name: Install Restic +- name: Install restic ansible.builtin.apt: name: restic state: present - become: true + +- name: Initialize restic repository + environment: + RESTIC_REPOSITORY: "{{ vault_restic_repo_url }}" + RESTIC_PASSWORD: "{{ vault_restic_password }}" + AWS_ACCESS_KEY_ID: "{{ vault_aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ vault_aws_secret_key }}" + block: + - name: Check if already initialized + ansible.builtin.command: restic snapshots + register: restic_check + failed_when: false + changed_when: false + + - name: Initialize repository + ansible.builtin.command: restic init + when: restic_check.rc != 0 + +- name: Deploy restic environment file + ansible.builtin.template: + src: restic-backup.env.j2 + dest: /etc/restic-backup.env + owner: root + group: root + mode: "0600" # Only root can read this file because it contains sensitive credentials - name: Deploy restic backup script ansible.builtin.template: src: restic-backup.sh.j2 dest: /usr/local/bin/restic-backup.sh - mode: "0700" # Only root can read/execute this because it contains secrets + owner: root + group: root + mode: "0700" # Only root can execute this backup script - name: Deploy restic systemd service ansible.builtin.template: src: restic-backup.service.j2 dest: /etc/systemd/system/restic-backup.service + owner: root + group: root mode: "0644" - name: Deploy restic systemd timer ansible.builtin.template: src: restic-backup.timer.j2 dest: /etc/systemd/system/restic-backup.timer + owner: root + group: root mode: "0644" - name: Enable and start backup timer diff --git a/roles/backup/templates/restic-backup.env.j2 b/roles/backup/templates/restic-backup.env.j2 new file mode 100644 index 0000000..d1b6db1 --- /dev/null +++ b/roles/backup/templates/restic-backup.env.j2 @@ -0,0 +1,5 @@ +# Ansible managed +RESTIC_REPOSITORY="{{ vault_restic_repo_url }}" +RESTIC_PASSWORD="{{ vault_restic_password }}" +AWS_ACCESS_KEY_ID="{{ vault_aws_access_key }}" +AWS_SECRET_ACCESS_KEY="{{ vault_aws_secret_key }}" diff --git a/roles/backup/templates/restic-backup.service.j2 b/roles/backup/templates/restic-backup.service.j2 index c0c0020..027d216 100644 --- a/roles/backup/templates/restic-backup.service.j2 +++ b/roles/backup/templates/restic-backup.service.j2 @@ -4,4 +4,5 @@ After=network-online.target [Service] Type=oneshot +EnvironmentFile=/etc/restic-backup.env ExecStart=/usr/local/bin/restic-backup.sh diff --git a/roles/backup/templates/restic-backup.sh.j2 b/roles/backup/templates/restic-backup.sh.j2 index 8921f25..77b76d2 100644 --- a/roles/backup/templates/restic-backup.sh.j2 +++ b/roles/backup/templates/restic-backup.sh.j2 @@ -1,17 +1,17 @@ #!/bin/bash ## Ansible managed -## Secrets are passed via Ansible Vault variables -export RESTIC_REPOSITORY="{{ vault_restic_repo_url }}" -export RESTIC_PASSWORD="{{ vault_restic_password }}" -export AWS_ACCESS_KEY_ID="{{ vault_aws_access_key }}" -export AWS_SECRET_ACCESS_KEY="{{ vault_aws_secret_key }}" - set -e +# Source environment variables if running manually/outside Systemd +if [ -f /etc/restic-backup.env ]; then + # shellcheck source=/dev/null + . /etc/restic-backup.env +fi + echo "Starting Restic Backup at $(date)" -## Run the backup using paths and exclusions defined in group_vars +## Run the backup using paths and exclusions defined as group_vars restic backup \ {% for path in backup_include_paths %} "{{ path }}" \ diff --git a/roles/backup/templates/restic-backup.timer.j2 b/roles/backup/templates/restic-backup.timer.j2 index b9ca4c1..e68ad85 100644 --- a/roles/backup/templates/restic-backup.timer.j2 +++ b/roles/backup/templates/restic-backup.timer.j2 @@ -1,5 +1,5 @@ [Unit] -Description=Run Restic Backup Daily at 4:00 AM +Description=Run Restic Backup [Timer] OnCalendar=*-*-* 04:00:00 -- 2.54.0 From 617883a9a7b29627cb0c99b4e9a71da847b9f562 Mon Sep 17 00:00:00 2001 From: alessandrovitali Date: Sun, 12 Jul 2026 23:56:06 +0200 Subject: [PATCH 4/4] style: rewrite comments --- roles/backup/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/backup/tasks/main.yml b/roles/backup/tasks/main.yml index db8beb1..22f4bc7 100644 --- a/roles/backup/tasks/main.yml +++ b/roles/backup/tasks/main.yml @@ -27,7 +27,7 @@ dest: /etc/restic-backup.env owner: root group: root - mode: "0600" # Only root can read this file because it contains sensitive credentials + mode: "0600" ## only root can read this file - name: Deploy restic backup script ansible.builtin.template: @@ -35,7 +35,7 @@ dest: /usr/local/bin/restic-backup.sh owner: root group: root - mode: "0700" # Only root can execute this backup script + mode: "0700" ## only root can execute the script - name: Deploy restic systemd service ansible.builtin.template: -- 2.54.0