/
/
/
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 - Restic Backup: {{ 'Enabled' if restic_backup_server_enabled | default(false) else 'Disabled' }}
147
148 ð¡ï¸ Monitoring:
149 - RAID Health Monitoring: Enabled (mdadm)
150 - SMART Monitoring: Enabled
151 - Service Health Checks: Comprehensive
152 - Storage Health: Active monitoring
153
154 ð Directory Structure:
155 - Service configs: {{ storage_docker_dir }}/[service-name]
156 - Environment files: {{ storage_docker_dir }}/[service-name]/.env
157 - Individual service directories for ARR stack
158 - Media Storage: {{ storage_base_path }}/media
159 - Downloads: {{ storage_base_path }}/downloads
160 - Backups: {{ storage_base_path }}/backups
161
162 ð§ Management Tools:
163 - Glances Monitoring: http://{{ ansible_default_ipv4.address }}:61208
164
165 roles:
166 # Core prerequisites
167 - role: user
168 tags: ['core', 'user']
169
170 - role: system
171 tags: ['core', 'system']
172
173 - role: geerlingguy.docker
174 tags: ['core', 'docker']
175
176 - role: geerlingguy.security
177 tags: ['core', 'security']
178
179 - role: docker-framework
180 tags: ['docker-framework', 'core', 'docker']
181
182 # Storage-specific services
183 - role: storage
184 tags: ['storage', 'media', 'backup']
185
186 # RAID monitoring for storage server
187 - role: nas
188 tags: ['storage', 'raid']
189 vars:
190 # Only run monitoring tasks from NAS role
191 nas_nfs_enabled: false
192 nas_network_bonding_enabled: false
193 nas_performance_tuning_enabled: false
194 nas_backup_integration: false
195 nas_enable_runner_exports: false
196 nas_raid_monitoring: true
197 nas_smartmontools_enabled: true
198
199 # Monitoring with RAID support for storage server
200 - role: monitoring
201 tags: ['monitoring', 'glances']
202 vars:
203 monitoring_enabled: true
204 glances_storage_monitoring: true
205 # RAID5 array drives - update if drives change
206 glances_storage_drives:
207 - { device: "sda", label: "Disk 1" }
208 - { device: "sdb", label: "Disk 2" }
209 - { device: "sdc", label: "Disk 3" }
210 - { device: "sdd", label: "Disk 4" }
211 - { device: "sde", label: "Disk 5" }
212 - { device: "sdf", label: "Disk 6" }
213 glances_monitored_directories:
214 - path: "/mnt/rstorage"
215 description: "RAID storage array"
216 - path: "/docker"
217 description: "Docker data"
218
219 # Backup role for system-level backups
220 - role: backup
221 tags: ['system-backup']
222 when: backup_enabled | default(true) and ('system-backup' in ansible_run_tags or ansible_run_tags | length == 0)
223
224 post_tasks:
225 - name: Verify core services are running
226 systemd:
227 name: "{{ item }}"
228 state: started
229 enabled: yes
230 loop:
231 - docker
232 - mdmonitor
233 - smartd
234 tags: ['verification']
235
236 - name: Verify RAID monitoring is active
237 command: systemctl is-active mdmonitor
238 register: mdmonitor_status
239 changed_when: false
240 ignore_errors: true
241 tags: ['verification', 'raid']
242
243 - name: Verify SMART monitoring is active
244 command: systemctl is-active smartd
245 register: smartd_status
246 changed_when: false
247 ignore_errors: true
248 tags: ['verification', 'smart']
249
250 - name: Generate storage access summary
251 template:
252 src: "{{ role_path }}/templates/storage-access-summary.txt.j2"
253 dest: "{{ storage_docker_dir }}/storage-access-info.txt"
254 owner: "{{ storage_user | default(ansible_user) }}"
255 group: "{{ storage_group | default(ansible_user) }}"
256 mode: '0644'
257 vars:
258 role_path: "roles/storage"
259 tags: ['summary']
260
261
262 - name: Wait for all storage services to be healthy
263 uri:
264 url: "http://{{ ansible_default_ipv4.address }}:{{ item.port }}{{ item.path | default('/') }}"
265 method: GET
266 status_code: "{{ item.status_code | default([200, 302, 401]) }}"
267 loop:
268 - { port: "{{ jellyfin_host_port }}", path: "/health", service: "Jellyfin", enabled: "{{ jellyfin_enabled }}" }
269 - { port: "{{ sonarr_host_port }}", path: "/ping", service: "Sonarr", enabled: "{{ sonarr_enabled }}" }
270 - { port: "{{ radarr_host_port }}", path: "/ping", service: "Radarr", enabled: "{{ radarr_enabled }}" }
271 - { port: "{{ prowlarr_host_port }}", path: "/ping", service: "Prowlarr", enabled: "{{ prowlarr_enabled }}" }
272 - { port: "{{ lazylibrarian_host_port }}", path: "/", service: "LazyLibrarian", enabled: "{{ lazylibrarian_enabled }}" }
273 - { port: "{{ lidarr_host_port }}", path: "/ping", service: "Lidarr", enabled: "{{ lidarr_enabled }}" }
274 - { port: "{{ jellyseer_host_port }}", path: "/", service: "Jellyseer", enabled: "{{ jellyseer_enabled }}" }
275 - { port: "{{ bazarr_host_port }}", path: "/", service: "Bazarr", enabled: "{{ bazarr_enabled }}" }
276 - { port: "{{ flaresolverr_host_port }}", path: "/health", service: "Flaresolverr", enabled: "{{ flaresolverr_enabled }}" }
277 - { port: "{{ calibre_desktop_gui_host_port }}", path: "/", service: "Calibre Server", enabled: "{{ calibre_server_enabled }}" }
278 - { port: "{{ calibre_web_host_port }}", path: "/", service: "Calibre-Web", enabled: "{{ calibre_web_enabled }}" }
279 - { port: "{{ navidrome_host_port }}", path: "/", service: "Navidrome", enabled: "{{ navidrome_enabled }}" }
280 - { port: "{{ restic_backup_host_port }}", path: "/", service: "Restic Backup", enabled: "{{ restic_backup_server_enabled }}", status_code: [200, 302, 401, 405] }
281 - { port: "{{ nextcloud_host_port }}", path: "/status.php", service: "Nextcloud", enabled: "{{ nextcloud_enabled }}", status_code: [200, 302, 400] }
282 when: item.enabled | bool
283 retries: 15
284 delay: 10
285 ignore_errors: yes
286 tags: ['verification', 'health-check']
287
288 - name: Display deployment completion summary
289 debug:
290 msg: |
291 Storage Services Deployment Complete
292
293 Server: {{ inventory_hostname }} ({{ ansible_default_ipv4.address }})
294
295 Monitoring:
296 - RAID: {{ 'Active' if mdmonitor_status is defined and mdmonitor_status.rc == 0 else 'Pending' }}
297 - SMART: {{ 'Active' if smartd_status is defined and smartd_status.rc == 0 else 'Pending' }}
298 - Glances: http://{{ ansible_default_ipv4.address }}:61208
299
300 Services:
301 {% if jellyfin_enabled | default(false) %}
302 - Jellyfin: http://{{ ansible_default_ipv4.address }}:{{ jellyfin_host_port }}
303 {% endif %}
304 {% if arr_stack_enabled | default(false) %}
305 - Sonarr: http://{{ ansible_default_ipv4.address }}:{{ sonarr_host_port }}
306 - Radarr: http://{{ ansible_default_ipv4.address }}:{{ radarr_host_port }}
307 - Prowlarr: http://{{ ansible_default_ipv4.address }}:{{ prowlarr_host_port }}
308 - Lidarr: http://{{ ansible_default_ipv4.address }}:{{ lidarr_host_port }}
309 - Bazarr: http://{{ ansible_default_ipv4.address }}:{{ bazarr_host_port }}
310 - LazyLibrarian: http://{{ ansible_default_ipv4.address }}:{{ lazylibrarian_host_port }}
311 - Jellyseer: http://{{ ansible_default_ipv4.address }}:{{ jellyseer_host_port }}
312 - Flaresolverr: http://{{ ansible_default_ipv4.address }}:{{ flaresolverr_host_port }}
313 - qBittorrent: http://{{ ansible_default_ipv4.address }}:{{ qbittorrent_host_port }}
314 {% endif %}
315 {% if calibre_enabled | default(false) %}
316 - Calibre Server: http://{{ ansible_default_ipv4.address }}:{{ calibre_desktop_gui_host_port }}
317 - Calibre-Web: http://{{ ansible_default_ipv4.address }}:{{ calibre_web_host_port }}
318 {% endif %}
319 {% if navidrome_enabled | default(false) %}
320 - Navidrome: http://{{ ansible_default_ipv4.address }}:{{ navidrome_host_port }}
321 {% endif %}
322 {% if restic_backup_server_enabled | default(false) %}
323 - Restic Backup: http://{{ ansible_default_ipv4.address }}:{{ restic_backup_host_port }}
324 {% endif %}
325 {% if nextcloud_enabled | default(false) %}
326 - Nextcloud: http://{{ ansible_default_ipv4.address }}:{{ nextcloud_host_port }}
327 {% endif %}
328
329 Directories:
330 - Docker Configs: {{ storage_docker_dir }}
331 - Media: {{ storage_base_path }}/media
332 - Downloads: {{ storage_base_path }}/downloads
333 - Backups: {{ storage_base_path }}/backups
334 tags: ['always']