/
/
/
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_container_info:
27 filters:
28 label: "com.docker.compose.project={{ item.name }}"
29 register: _pre_containers
30 when: backup_health_check | bool
31 tags: [backup, health-check]
32
33- name: Assert project healthy before backup
34 assert:
35 that:
36 - >-
37 (_pre_containers.containers | default([])
38 | selectattr('Health','defined')
39 | map(attribute='Health') | map(attribute='Status') | list
40 ) | difference(['healthy']) | length == 0
41 success_msg: "All containers with healthchecks are healthy (pre-backup)."
42 fail_msg: "Some containers are not healthy before backup for {{ item.name }}."
43 when:
44 - backup_health_check | bool
45 - (_pre_containers.containers | default([]) | selectattr('Health','defined') | list | length) > 0
46 tags: [backup, health-check]
47
48- name: Stop compose project for backup
49 community.docker.docker_compose_v2:
50 project_src: "{{ item.path }}"
51 project_name: "{{ item.name }}"
52 state: stopped
53 register: _stop_result
54 failed_when: false
55 tags: [backup, stop]
56
57- name: Run restic backup
58 command: >
59 restic backup
60 {{ restic_backup_args | default('--verbose') }}
61 --tag {{ item.name }}
62 --host {{ inventory_hostname }}
63 --one-file-system
64 {{ item.path }}
65 environment:
66 RESTIC_REPOSITORY: "{{ restic_repo }}"
67 RESTIC_PASSWORD: "{{ restic_password }}"
68 register: _restic_backup
69 changed_when: "'snapshot' in _restic_backup.stdout or 'created new' in _restic_backup.stdout"
70 become: yes
71 tags: [backup, restic]
72
73- name: Display backup result
74 debug:
75 msg: |
76 Backup completed for {{ item.name }}
77 - Snapshot: {{ _restic_backup.stdout | regex_search('snapshot [a-f0-9]+ saved') | default('unknown') }}
78 tags: [backup, info]
79
80- name: Enforce retention policy
81 command: >
82 restic forget --prune {{ restic_retention_args | default('--keep-last 10') }} --tag {{ item.name }}
83 environment:
84 RESTIC_REPOSITORY: "{{ restic_repo }}"
85 RESTIC_PASSWORD: "{{ restic_password }}"
86 when: restic_retention_enable | bool
87 become: yes
88 tags: [backup, retention]
89
90- name: Start compose project after backup
91 community.docker.docker_compose_v2:
92 project_src: "{{ item.path }}"
93 project_name: "{{ item.name }}"
94 state: present
95 register: _start_result
96 tags: [backup, start]
97
98- name: Wait for containers to be healthy after backup
99 vars:
100 _deadline: "{{ (backup_health_timeout_sec | int) // (backup_health_interval_sec | int) }}"
101 community.docker.docker_container_info:
102 filters:
103 label: "com.docker.compose.project={{ item.name }}"
104 register: _post_containers
105 until: >
106 (
107 (_post_containers.containers | default([])) | selectattr('Health','defined')
108 | map(attribute='Health') | map(attribute='Status') | list
109 ) | difference(['healthy']) | length == 0
110 or
111 (
112 (_post_containers.containers | default([])) | selectattr('Health','defined') | list | length == 0
113 )
114 retries: "{{ _deadline | int | max(1) }}"
115 delay: "{{ backup_health_interval_sec | int }}"
116 when: backup_health_check | bool
117 changed_when: false
118 tags: [backup, health-check]
119
120- name: Display backup completion for {{ item.name }}
121 debug:
122 msg: |
123 Backup workflow completed for {{ item.name }}
124 - Status: SUCCESS
125 - Service restarted: {{ _start_result.changed | default(false) }}
126 - Health check: {{ 'PASSED' if backup_health_check | bool else 'SKIPPED' }}
127 tags: [backup, summary]
128