/
/
/
Ansible role that can run restic backups and push it to a remote storage server.
1---
2# Backup Workflow
3# Performs backup operation for a single service
4
5- name: Display backup start for {{ item.name }}
6 debug:
7 msg: |
8 Starting backup for {{ item.name }}
9 - Path: {{ item.path }}
10 - Tags: {{ item.tags | join(', ') }}
11 tags: [backup, info]
12
13- name: Check if service directory exists
14 stat:
15 path: "{{ item.path }}"
16 register: service_dir_stat
17 tags: [backup, validation]
18
19- name: Fail if service directory doesn't exist
20 fail:
21 msg: "Service directory {{ item.path }} does not exist for {{ item.name }}"
22 when: not service_dir_stat.stat.exists
23 tags: [backup, validation]
24
25- name: Pre-backup health check
26 community.docker.docker_host_info:
27 containers: true
28 containers_filters:
29 label: "com.docker.compose.project={{ item.name }}"
30 register: _pre_containers
31 when: backup_health_check | bool
32 tags: [backup, health-check]
33
34- name: Assert project healthy before backup
35 assert:
36 that:
37 - >-
38 (_pre_containers.containers | default([])
39 | selectattr('Health','defined')
40 | map(attribute='Health') | map(attribute='Status') | list
41 ) | difference(['healthy']) | length == 0
42 success_msg: "All containers with healthchecks are healthy (pre-backup)."
43 fail_msg: "Some containers are not healthy before backup for {{ item.name }}."
44 when:
45 - backup_health_check | bool
46 - (_pre_containers.containers | default([]) | selectattr('Health','defined') | list | length) > 0
47 tags: [backup, health-check]
48
49- name: Stop compose project for backup
50 community.docker.docker_compose_v2:
51 project_src: "{{ item.path }}"
52 project_name: "{{ item.name }}"
53 state: stopped
54 register: _stop_result
55 failed_when: false
56 tags: [backup, stop]
57
58- name: Run restic backup
59 command: >
60 restic backup
61 {{ restic_backup_args | default('--verbose') }}
62 --tag {{ item.name }}
63 --host {{ inventory_hostname }}
64 --one-file-system
65 {{ item.path }}
66 environment:
67 RESTIC_REPOSITORY: "{{ restic_repo }}"
68 RESTIC_PASSWORD: "{{ restic_password }}"
69 register: _restic_backup
70 changed_when: "'snapshot' in _restic_backup.stdout or 'created new' in _restic_backup.stdout"
71 become: yes
72 tags: [backup, restic]
73
74- name: Display backup result
75 debug:
76 msg: |
77 Backup completed for {{ item.name }}
78 - Snapshot: {{ _restic_backup.stdout | regex_search('snapshot [a-f0-9]+ saved') | default('unknown') }}
79 tags: [backup, info]
80
81- name: Enforce retention policy
82 command: >
83 restic forget --prune {{ restic_retention_args | default('--keep-last 10') }} --tag {{ item.name }}
84 environment:
85 RESTIC_REPOSITORY: "{{ restic_repo }}"
86 RESTIC_PASSWORD: "{{ restic_password }}"
87 when: restic_retention_enable | bool
88 become: yes
89 tags: [backup, retention]
90
91- name: Start compose project after backup
92 community.docker.docker_compose_v2:
93 project_src: "{{ item.path }}"
94 project_name: "{{ item.name }}"
95 state: present
96 register: _start_result
97 tags: [backup, start]
98
99- name: Wait for containers to be healthy after backup
100 vars:
101 _deadline: "{{ (backup_health_timeout_sec | int) // (backup_health_interval_sec | int) }}"
102 community.docker.docker_host_info:
103 containers: true
104 containers_filters:
105 label: "com.docker.compose.project={{ item.name }}"
106 register: _post_containers
107 until: >
108 (
109 (_post_containers.containers | default([])) | selectattr('Health','defined')
110 | map(attribute='Health') | map(attribute='Status') | list
111 ) | difference(['healthy']) | length == 0
112 or
113 (
114 (_post_containers.containers | default([])) | selectattr('Health','defined') | list | length == 0
115 )
116 retries: "{{ [_deadline | int, 1] | max }}"
117 delay: "{{ backup_health_interval_sec | int }}"
118 when: backup_health_check | bool
119 changed_when: false
120 tags: [backup, health-check]
121
122- name: Display backup completion for {{ item.name }}
123 debug:
124 msg: |
125 Backup workflow completed for {{ item.name }}
126 - Status: SUCCESS
127 - Service restarted: {{ _start_result.changed | default(false) }}
128 - Health check: {{ 'PASSED' if backup_health_check | bool else 'SKIPPED' }}
129 tags: [backup, summary]
130