/
/
/
Ansible role that can run restic backups and push it to a remote storage server.
1---
2# Appliance Backup Workflow
3# Backs up non-Docker appliances (Home Assistant, OPNsense) via API/CLI
4# Runs on a collector host that has restic installed and network access
5
6# ==============================================================================
7# HOME ASSISTANT BACKUP
8# ==============================================================================
9
10- name: Home Assistant backup
11 when: backup_ha_enabled | bool
12 tags: [backup, appliance, homeassistant]
13 block:
14 - name: Create HA backup via CLI
15 ansible.builtin.command: ha backups new --name "restic-{{ ansible_date_time.date }}"
16 register: _ha_backup_result
17 changed_when: "'slug' in _ha_backup_result.stdout"
18 check_mode: false
19
20 - name: Wait for HA backup to appear on disk
21 ansible.builtin.find:
22 paths: "{{ backup_ha_backup_path }}"
23 patterns: "*.tar"
24 age: "-5m"
25 register: _ha_backup_files
26 until: _ha_backup_files.matched > 0
27 retries: 12
28 delay: 10
29 check_mode: false
30
31 - name: Run restic backup for Home Assistant
32 ansible.builtin.command: >
33 restic backup
34 {{ restic_backup_args | default('--verbose') }}
35 --tag homeassistant
36 --host {{ inventory_hostname }}
37 {{ backup_ha_backup_path }}
38 environment:
39 RESTIC_REPOSITORY: "{{ restic_repo }}"
40 RESTIC_PASSWORD: "{{ restic_password }}"
41 register: _ha_restic_result
42 changed_when: "'snapshot' in _ha_restic_result.stdout or 'created new' in _ha_restic_result.stdout"
43 become: true
44
45 - name: Enforce retention for Home Assistant backups
46 ansible.builtin.command: >
47 restic forget --prune {{ restic_retention_args | default('--keep-last 10') }} --tag homeassistant
48 environment:
49 RESTIC_REPOSITORY: "{{ restic_repo }}"
50 RESTIC_PASSWORD: "{{ restic_password }}"
51 when: restic_retention_enable | bool
52 become: true
53
54 - name: Prune old HA backup files (keep last 3)
55 block:
56 - name: List HA backup files by age
57 ansible.builtin.find:
58 paths: "{{ backup_ha_backup_path }}"
59 patterns: "*.tar"
60 register: _ha_all_backups
61
62 - name: Remove old HA backup files
63 ansible.builtin.file:
64 path: "{{ item.path }}"
65 state: absent
66 loop: "{{ (_ha_all_backups.files | sort(attribute='mtime') | reverse | list)[backup_ha_keep_local:] }}"
67 loop_control:
68 label: "{{ item.path | basename }}"
69 become: true
70 when: _ha_all_backups.matched > backup_ha_keep_local
71 when: backup_ha_prune_local | bool
72
73 - name: Display Home Assistant backup result
74 ansible.builtin.debug:
75 msg: |
76 Home Assistant backup completed
77 - Backup path: {{ backup_ha_backup_path }}
78 - Restic tag: homeassistant
79 - Snapshot: {{ _ha_restic_result.stdout | regex_search('snapshot [a-f0-9]+ saved') | default('unknown') }}
80
81# ==============================================================================
82# OPNSENSE BACKUP
83# ==============================================================================
84
85- name: OPNsense backup
86 when: backup_opnsense_enabled | bool
87 tags: [backup, appliance, opnsense]
88 block:
89 - name: Create OPNsense staging directory
90 ansible.builtin.file:
91 path: "{{ backup_appliance_staging_dir }}/opnsense"
92 state: directory
93 owner: "{{ ansible_user }}"
94 group: users
95 mode: '2775'
96 become: true
97
98 - name: Download OPNsense config backup via API
99 ansible.builtin.uri:
100 url: "https://{{ backup_opnsense_host }}/api/core/backup/download/this"
101 user: "{{ backup_opnsense_api_key }}"
102 password: "{{ backup_opnsense_api_secret }}"
103 force_basic_auth: true
104 validate_certs: "{{ backup_opnsense_validate_certs }}"
105 dest: "{{ backup_appliance_staging_dir }}/opnsense/config-{{ ansible_date_time.date }}.xml"
106 mode: '0640'
107 become: true
108 register: _opnsense_download
109
110 - name: Run restic backup for OPNsense
111 ansible.builtin.command: >
112 restic backup
113 {{ restic_backup_args | default('--verbose') }}
114 --tag opnsense
115 --host {{ inventory_hostname }}
116 {{ backup_appliance_staging_dir }}/opnsense
117 environment:
118 RESTIC_REPOSITORY: "{{ restic_repo }}"
119 RESTIC_PASSWORD: "{{ restic_password }}"
120 register: _opnsense_restic_result
121 changed_when: "'snapshot' in _opnsense_restic_result.stdout or 'created new' in _opnsense_restic_result.stdout"
122 become: true
123
124 - name: Enforce retention for OPNsense backups
125 ansible.builtin.command: >
126 restic forget --prune {{ restic_retention_args | default('--keep-last 10') }} --tag opnsense
127 environment:
128 RESTIC_REPOSITORY: "{{ restic_repo }}"
129 RESTIC_PASSWORD: "{{ restic_password }}"
130 when: restic_retention_enable | bool
131 become: true
132
133 - name: Prune old OPNsense config files (keep last 3)
134 block:
135 - name: List OPNsense config files by age
136 ansible.builtin.find:
137 paths: "{{ backup_appliance_staging_dir }}/opnsense"
138 patterns: "config-*.xml"
139 register: _opnsense_all_configs
140
141 - name: Remove old OPNsense config files
142 ansible.builtin.file:
143 path: "{{ item.path }}"
144 state: absent
145 loop: "{{ (_opnsense_all_configs.files | sort(attribute='mtime') | reverse | list)[backup_opnsense_keep_local:] }}"
146 loop_control:
147 label: "{{ item.path | basename }}"
148 become: true
149 when: _opnsense_all_configs.matched > backup_opnsense_keep_local
150 when: backup_opnsense_prune_local | bool
151
152 - name: Display OPNsense backup result
153 ansible.builtin.debug:
154 msg: |
155 OPNsense backup completed
156 - Config downloaded from: {{ backup_opnsense_host }}
157 - Staging: {{ backup_appliance_staging_dir }}/opnsense
158 - Restic tag: opnsense
159 - Snapshot: {{ _opnsense_restic_result.stdout | regex_search('snapshot [a-f0-9]+ saved') | default('unknown') }}
160