/
/
/
This repo is destined for my server automations and setup.
1---
2# Storage Services Playbook
3# Comprehensive deployment for media automation and backup services
4
5# PHASE 1: Setup NAS exports for storage services (if needed for other hosts)
6# Note: Storage server typically hosts its own storage, so this phase may be empty
7- name: Storage Server Local Setup
8 hosts: storage_servers
9 become: yes
10 gather_facts: yes
11
12 vars:
13 # Enable basic system monitoring (mdadm, smartd)
14 nas_raid_monitoring: true
15 nas_smartmontools_enabled: true
16
17 pre_tasks:
18 - name: Verify storage server local requirements
19 assert:
20 that:
21 - storage_base_path is defined
22 - storage_raid_arrays is defined
23 - ansible_default_ipv4.address is defined
24 fail_msg: "Storage server local requirements not met. Check host variables."
25
26 - name: Display storage local setup information
27 debug:
28 msg: |
29 Setting up local storage services on: {{ inventory_hostname }}
30 Storage Base: {{ storage_base_path }}
31 RAID Arrays: {{ storage_raid_arrays | length }}
32
33 Local Services to Enable:
34 - RAID Health Monitoring
35 - SMART Drive Monitoring
36 - Storage Path Validation
37
38 roles:
39 # Only run monitoring tasks from NAS role for local storage
40 - role: nas
41 tags: ['storage', 'raid']
42 vars:
43 nas_nfs_enabled: false
44 nas_network_bonding_enabled: false
45 nas_performance_tuning_enabled: false
46 nas_backup_integration: false
47 nas_enable_runner_exports: false
48 nas_raid_monitoring: true
49 nas_smartmontools_enabled: true
50
51 post_tasks:
52 - name: Verify local monitoring services are active
53 systemd:
54 name: "{{ item }}"
55 state: started
56 enabled: yes
57 loop:
58 - mdmonitor
59 - smartd
60 tags: ['verification']
61
62# PHASE 2: Deploy storage services
63- name: Storage Services Deployment
64 hosts: storage_servers
65 become: yes
66 gather_facts: yes
67
68 vars:
69 # Override defaults for storage-specific deployment
70 storage_enabled: true
71 backup_enabled: true
72
73 pre_tasks:
74 - name: Verify storage server requirements
75 assert:
76 that:
77 - storage_base_path is defined
78 - storage_docker_dir is defined
79 - ansible_default_ipv4.address is defined
80 - storage_user is defined
81 - storage_group is defined
82 - storage_raid_arrays is defined
83 fail_msg: "Storage server requirements not met. Check host variables."
84
85 - name: Validate storage paths exist and are accessible
86 block:
87 - name: Check storage base path
88 stat:
89 path: "{{ storage_base_path }}"
90 register: storage_base_stat
91 failed_when: not storage_base_stat.stat.exists
92
93 - name: Check storage base path permissions
94 assert:
95 that:
96 - storage_base_stat.stat.readable
97 - storage_base_stat.stat.writable
98 fail_msg: "Storage base path {{ storage_base_path }} is not readable/writable"
99
100 - name: Check storage docker directory
101 stat:
102 path: "{{ storage_docker_dir }}"
103 register: storage_docker_stat
104 failed_when: not storage_docker_stat.stat.exists
105
106 - name: Check storage docker directory permissions
107 assert:
108 that:
109 - storage_docker_stat.stat.readable
110 - storage_docker_stat.stat.writable
111 fail_msg: "Storage docker directory {{ storage_docker_dir }} is not readable/writable"
112
113 rescue:
114 - name: Display storage path error
115 debug:
116 msg: |
117 ERROR: Storage path validation failed!
118 Path: {{ storage_base_path }}
119 Docker Directory: {{ storage_docker_dir }}
120 Ensure the storage array is mounted and accessible
121 Current mounts: {{ ansible_mounts | map(attribute='mount') | list }}
122 Available space: {{ ansible_mounts | selectattr('mount', 'equalto', storage_base_path) | map(attribute='size_available') | first | default('unknown') | filesizeformat }}
123
124 - name: Display storage deployment information
125 debug:
126 msg: |
127 ð Deploying Storage Services to: {{ inventory_hostname }}
128
129 ð Server Information:
130 - IP Address: {{ ansible_default_ipv4.address }}
131 - Storage Base: {{ storage_base_path }}
132 - Docker Directory: {{ storage_docker_dir }}
133 - User: {{ storage_user }}:{{ storage_group }}
134 - RAID Monitoring: {{ 'Active' if mdmonitor_status is defined and mdmonitor_status.rc == 0 else 'Pending' }}
135 - SMART Monitoring: {{ 'Active' if smartd_status is defined and smartd_status.rc == 0 else 'Pending' }}
136 - GPU Acceleration: Disabled (CPU only)
137 - Available Storage: {{ ansible_mounts | selectattr('mount', 'equalto', storage_base_path) | map(attribute='size_available') | first | default('unknown') | filesizeformat }}
138 - Total Storage: {{ ansible_mounts | selectattr('mount', 'equalto', storage_base_path) | map(attribute='size_total') | first | default('unknown') | filesizeformat }}
139
140 Services to Deploy:
141 - Jellyfin: {{ 'Enabled' if jellyfin_enabled | default(false) else 'Disabled' }}
142 - Arr Stack: {{ 'Enabled' if arr_stack_enabled | default(false) else 'Disabled' }}
143 - Calibre Stack: {{ 'Enabled' if calibre_enabled | default(false) else 'Disabled' }}
144 - Navidrome: {{ 'Enabled' if navidrome_enabled | default(false) else 'Disabled' }}
145 - Nextcloud: {{ 'Enabled' if nextcloud_enabled | default(false) else 'Disabled' }}
146 - gdu (Disk Usage): {{ 'Enabled' if gdu_enabled | default(false) else 'Disabled' }}
147 - Restic Backup: {{ 'Enabled' if restic_backup_server_enabled | default(false) else 'Disabled' }}
148
149 ð¡ï¸ Monitoring:
150 - RAID Health Monitoring: Enabled (mdadm)
151 - SMART Monitoring: Enabled
152 - Service Health Checks: Comprehensive
153 - Storage Health: Active monitoring
154
155 ð Directory Structure:
156 - Service configs: {{ storage_docker_dir }}/[service-name]
157 - Environment files: {{ storage_docker_dir }}/[service-name]/.env
158 - Individual service directories for ARR stack
159 - Media Storage: {{ storage_base_path }}/media
160 - Downloads: {{ storage_base_path }}/downloads
161 - Backups: {{ storage_base_path }}/backups
162
163 ð§ Management Tools:
164 - Glances Monitoring: http://{{ ansible_default_ipv4.address }}:61208
165 {% if gdu_enabled | default(false) %}
166 - gdu Disk Usage: http://{{ gdu_web_hostname }}:{{ gdu_host_port }}
167 {% endif %}
168
169 roles:
170 # Core prerequisites
171 - role: user
172 tags: ['core', 'user']
173
174 - role: system
175 tags: ['core', 'system']
176
177 - role: geerlingguy.docker
178 tags: ['core', 'docker']
179
180 - role: geerlingguy.security
181 tags: ['core', 'security']
182
183 - role: docker-framework
184 tags: ['docker-framework', 'core', 'docker']
185
186 # Storage-specific services
187 - role: storage
188 tags: ['storage', 'media', 'backup']
189
190 # RAID monitoring for storage server
191 - role: nas
192 tags: ['storage', 'raid']
193 vars:
194 # Only run monitoring tasks from NAS role
195 nas_nfs_enabled: false
196 nas_network_bonding_enabled: false
197 nas_performance_tuning_enabled: false
198 nas_backup_integration: false
199 nas_enable_runner_exports: false
200 nas_raid_monitoring: true
201 nas_smartmontools_enabled: true
202
203 # Monitoring with RAID support for storage server
204 - role: monitoring
205 tags: ['monitoring', 'glances']
206 vars:
207 monitoring_enabled: true
208 glances_storage_monitoring: true
209 # RAID5 array drives - update if drives change
210 glances_storage_drives:
211 - { device: "sda", label: "Disk 1" }
212 - { device: "sdb", label: "Disk 2" }
213 - { device: "sdc", label: "Disk 3" }
214 - { device: "sdd", label: "Disk 4" }
215 - { device: "sde", label: "Disk 5" }
216 - { device: "sdf", label: "Disk 6" }
217 glances_monitored_directories:
218 - path: "/mnt/rstorage"
219 description: "RAID storage array"
220 - path: "/docker"
221 description: "Docker data"
222
223 # Backup role for system-level backups
224 - role: backup
225 tags: ['system-backup']
226 when: backup_enabled | default(true) and ('system-backup' in ansible_run_tags or ansible_run_tags | length == 0)
227
228 post_tasks:
229 - name: Verify core services are running
230 systemd:
231 name: "{{ item }}"
232 state: started
233 enabled: yes
234 loop:
235 - docker
236 - mdmonitor
237 - smartd
238 tags: ['verification']
239
240 - name: Verify RAID monitoring is active
241 command: systemctl is-active mdmonitor
242 register: mdmonitor_status
243 changed_when: false
244 ignore_errors: true
245 tags: ['verification', 'raid']
246
247 - name: Verify SMART monitoring is active
248 command: systemctl is-active smartd
249 register: smartd_status
250 changed_when: false
251 ignore_errors: true
252 tags: ['verification', 'smart']
253
254 - name: Generate storage access summary
255 template:
256 src: "{{ role_path }}/templates/storage-access-summary.txt.j2"
257 dest: "{{ storage_docker_dir }}/storage-access-info.txt"
258 owner: "{{ storage_user | default(ansible_user) }}"
259 group: "{{ storage_group | default(ansible_user) }}"
260 mode: '0644'
261 vars:
262 role_path: "roles/storage"
263 tags: ['summary']
264
265
266 - name: Wait for all storage services to be healthy
267 uri:
268 url: "http://{{ ansible_default_ipv4.address }}:{{ item.port }}{{ item.path | default('/') }}"
269 method: GET
270 status_code: "{{ item.status_code | default([200, 302, 401]) }}"
271 loop:
272 - { port: "{{ jellyfin_host_port }}", path: "/health", service: "Jellyfin", enabled: "{{ jellyfin_enabled }}" }
273 - { port: "{{ sonarr_host_port }}", path: "/ping", service: "Sonarr", enabled: "{{ sonarr_enabled }}" }
274 - { port: "{{ radarr_host_port }}", path: "/ping", service: "Radarr", enabled: "{{ radarr_enabled }}" }
275 - { port: "{{ prowlarr_host_port }}", path: "/ping", service: "Prowlarr", enabled: "{{ prowlarr_enabled }}" }
276 - { port: "{{ lazylibrarian_host_port }}", path: "/", service: "LazyLibrarian", enabled: "{{ lazylibrarian_enabled }}" }
277 - { port: "{{ lidarr_host_port }}", path: "/ping", service: "Lidarr", enabled: "{{ lidarr_enabled }}" }
278 - { port: "{{ jellyseer_host_port }}", path: "/", service: "Jellyseer", enabled: "{{ jellyseer_enabled }}" }
279 - { port: "{{ bazarr_host_port }}", path: "/", service: "Bazarr", enabled: "{{ bazarr_enabled }}" }
280 - { port: "{{ flaresolverr_host_port }}", path: "/health", service: "Flaresolverr", enabled: "{{ flaresolverr_enabled }}" }
281 - { port: "{{ calibre_desktop_gui_host_port }}", path: "/", service: "Calibre Server", enabled: "{{ calibre_server_enabled }}" }
282 - { port: "{{ calibre_web_host_port }}", path: "/", service: "Calibre-Web", enabled: "{{ calibre_web_enabled }}" }
283 - { port: "{{ navidrome_host_port }}", path: "/", service: "Navidrome", enabled: "{{ navidrome_enabled }}" }
284 - { port: "{{ restic_backup_host_port }}", path: "/", service: "Restic Backup", enabled: "{{ restic_backup_server_enabled }}", status_code: [200, 302, 401, 405] }
285 - { port: "{{ nextcloud_host_port }}", path: "/status.php", service: "Nextcloud", enabled: "{{ nextcloud_enabled }}", status_code: [200, 302, 400] }
286 - { port: "{{ gdu_host_port }}", path: "/", service: "gdu", enabled: "{{ gdu_enabled }}" }
287 when: item.enabled | bool
288 retries: 15
289 delay: 10
290 ignore_errors: yes
291 tags: ['verification', 'health-check']
292
293 - name: Display deployment completion summary
294 debug:
295 msg: |
296 Storage Services Deployment Complete
297
298 Server: {{ inventory_hostname }} ({{ ansible_default_ipv4.address }})
299
300 Monitoring:
301 - RAID: {{ 'Active' if mdmonitor_status is defined and mdmonitor_status.rc == 0 else 'Pending' }}
302 - SMART: {{ 'Active' if smartd_status is defined and smartd_status.rc == 0 else 'Pending' }}
303 - Glances: http://{{ ansible_default_ipv4.address }}:61208
304 {% if gdu_enabled | default(false) %}
305 - gdu Disk Usage: http://{{ gdu_web_hostname }}:{{ gdu_host_port }} (scanning {{ gdu_scan_dir }})
306 {% endif %}
307
308 Services:
309 {% if jellyfin_enabled | default(false) %}
310 - Jellyfin: http://{{ ansible_default_ipv4.address }}:{{ jellyfin_host_port }}
311 {% endif %}
312 {% if arr_stack_enabled | default(false) %}
313 - Sonarr: http://{{ ansible_default_ipv4.address }}:{{ sonarr_host_port }}
314 - Radarr: http://{{ ansible_default_ipv4.address }}:{{ radarr_host_port }}
315 - Prowlarr: http://{{ ansible_default_ipv4.address }}:{{ prowlarr_host_port }}
316 - Lidarr: http://{{ ansible_default_ipv4.address }}:{{ lidarr_host_port }}
317 - Bazarr: http://{{ ansible_default_ipv4.address }}:{{ bazarr_host_port }}
318 - LazyLibrarian: http://{{ ansible_default_ipv4.address }}:{{ lazylibrarian_host_port }}
319 - Jellyseer: http://{{ ansible_default_ipv4.address }}:{{ jellyseer_host_port }}
320 - Flaresolverr: http://{{ ansible_default_ipv4.address }}:{{ flaresolverr_host_port }}
321 - qBittorrent: http://{{ ansible_default_ipv4.address }}:{{ qbittorrent_host_port }}
322 {% endif %}
323 {% if calibre_enabled | default(false) %}
324 - Calibre Server: http://{{ ansible_default_ipv4.address }}:{{ calibre_desktop_gui_host_port }}
325 - Calibre-Web: http://{{ ansible_default_ipv4.address }}:{{ calibre_web_host_port }}
326 {% endif %}
327 {% if navidrome_enabled | default(false) %}
328 - Navidrome: http://{{ ansible_default_ipv4.address }}:{{ navidrome_host_port }}
329 {% endif %}
330 {% if restic_backup_server_enabled | default(false) %}
331 - Restic Backup: http://{{ ansible_default_ipv4.address }}:{{ restic_backup_host_port }}
332 {% endif %}
333 {% if nextcloud_enabled | default(false) %}
334 - Nextcloud: http://{{ ansible_default_ipv4.address }}:{{ nextcloud_host_port }}
335 {% endif %}
336
337 Directories:
338 - Docker Configs: {{ storage_docker_dir }}
339 - Media: {{ storage_base_path }}/media
340 - Downloads: {{ storage_base_path }}/downloads
341 - Backups: {{ storage_base_path }}/backups
342 tags: ['always']