/
/
/
Ansible role that can run restic backups and push it to a remote storage server.
1---
2# Helper vars
3- name: Derive project_name and targets
4 ansible.builtin.set_fact:
5 _project_name: "{{ (item.project_name | default((item.path | basename))) }}"
6 _targets: "{{ item.targets | default([ item.path ]) }}"
7
8# Optional pre-commands (host shell)
9- name: Run pre_cmd tasks (if any)
10 ansible.builtin.shell: "{{ cmd }}"
11 args:
12 chdir: "{{ item.path }}"
13 loop: "{{ item.pre_cmd | default([]) }}"
14 loop_control:
15 loop_var: cmd
16 when: (item.pre_cmd | default([])) | length > 0
17 become: true
18
19# Pre-backup health check (only if enabled)
20- name: Gather containers for project (pre)
21 community.docker.docker_container_info:
22 filters:
23 label: "com.docker.compose.project={{ _project_name }}"
24 register: _pre_containers
25 when: backup_health_check | bool
26
27- name: Determine if any container has a healthcheck (pre)
28 ansible.builtin.set_fact:
29 _has_healthchecks_pre: "{{ (_pre_containers.containers | default([])) | selectattr('Health','defined') | list | length > 0 }}"
30 when: backup_health_check | bool
31
32- name: Assert project healthy before backup (if healthchecks exist)
33 ansible.builtin.assert:
34 that:
35 - (_pre_containers.containers | selectattr('Health','defined') | map(attribute='Health') | map(attribute='Status') | list) | difference(['healthy']) | length == 0
36 success_msg: "All containers with healthchecks are healthy (pre-backup)."
37 fail_msg: "Some containers are not healthy before backup."
38 when: backup_health_check | bool and _has_healthchecks_pre
39
40# Stop the project
41- name: Stop compose project
42 community.docker.docker_compose_v2:
43 project_src: "{{ item.path }}"
44 project_name: "{{ _project_name }}"
45 files: "{{ item.compose_files | default(omit) }}"
46 state: stopped
47 register: _stop_result
48
49# Backup: run restic on targets
50- name: Run restic backup
51 ansible.builtin.command: >
52 restic backup
53 {{ restic_backup_args }}
54 --tag {{ restic_tag_format }}
55 {{ _targets | join(' ') }}
56 environment: "{{ {'RESTIC_REPOSITORY': restic_repo, 'RESTIC_PASSWORD': restic_password} | combine(restic_env_extra | default({})) }}"
57 register: _restic_backup
58 changed_when: "'snapshot' in _restic_backup.stdout or 'created new' in _restic_backup.stdout"
59
60# Housekeeping: retention/prune (optional)
61- name: Enforce retention policy
62 ansible.builtin.command: >
63 restic forget --prune {{ restic_retention_args }} --tag {{ restic_tag_format }}
64 environment: "{{ {'RESTIC_REPOSITORY': restic_repo, 'RESTIC_PASSWORD': restic_password} | combine(restic_env_extra | default({})) }}"
65 when: restic_retention_enable | bool
66
67# Start the project
68- name: Start compose project
69 community.docker.docker_compose_v2:
70 project_src: "{{ item.path }}"
71 project_name: "{{ _project_name }}"
72 files: "{{ item.compose_files | default(omit) }}"
73 state: present
74 register: _start_result
75
76# Post-backup: wait until healthy (if healthchecks exist)
77- name: Wait for containers to be healthy (post)
78 vars:
79 _deadline: "{{ (backup_health_timeout_sec | int) // (backup_health_interval_sec | int) }}"
80 community.docker.docker_container_info:
81 filters:
82 label: "com.docker.compose.project={{ _project_name }}"
83 register: _post_containers
84 until: >
85 (
86 (_post_containers.containers | default([])) | selectattr('Health','defined')
87 | map(attribute='Health') | map(attribute='Status') | list
88 ) | difference(['healthy']) | length == 0
89 or
90 (
91 (_post_containers.containers | default([])) | selectattr('Health','defined') | list | length == 0
92 )
93 retries: "{{ _deadline | int | max(1) }}"
94 delay: "{{ backup_health_interval_sec | int }}"
95 when: backup_health_check | bool
96 changed_when: false
97
98# Optional post-commands (host shell)
99- name: Run post_cmd tasks (if any)
100 ansible.builtin.shell: "{{ cmd }}"
101 args:
102 chdir: "{{ item.path }}"
103 loop: "{{ item.post_cmd | default([]) }}"
104 loop_control:
105 loop_var: cmd
106 when: (item.post_cmd | default([])) | length > 0
107 become: true
108
109