/
/
/
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 {% if jellyfin_enabled | default(true) %}
142 - Jellyfin Media Server (Port {{ jellyfin_host_port | default(8096) }}) - CPU Hardware Acceleration
143 {% endif %}
144 {% if arr_stack_enabled | default(true) %}
145 - Arr Stack with Gluetun VPN:
146 * Sonarr TV (Port {{ sonarr_host_port | default(8989) }})
147 * Radarr Movies (Port {{ radarr_host_port | default(7878) }})
148 * Prowlarr Indexers (Port {{ prowlarr_host_port | default(9696) }})
149 * LazyLibrarian Books (Port {{ lazylibrarian_host_port | default(5299) }})
150 * Jellyseer (Port {{ jellyseer_host_port | default(5055) }}) - Media Requests
151 * Flaresolverr (Port {{ flaresolverr_host_port | default(8191) }}) - Cloudflare bypass
152 {% endif %}
153 {% if calibre_enabled | default(true) %}
154 - Calibre Stack:
155 * Calibre Server (Port {{ calibre_server_host_port | default(8083) }})
156 * Calibre-Web (Port {{ calibre_web_host_port | default(8084) }})
157 {% endif %}
158 {% if restic_backup_server_enabled | default(true) %}
159 - Restic Backup Server (Port {{ restic_backup_host_port | default(8000) }})
160 {% endif %}
161
162 ð¡ï¸ Monitoring:
163 - RAID Health Monitoring: Enabled (mdadm)
164 - SMART Monitoring: Enabled
165 - Service Health Checks: Comprehensive
166 - Storage Health: Active monitoring
167
168 ð Directory Structure:
169 - Service configs: {{ storage_docker_dir }}/[service-name]
170 - Environment files: {{ storage_docker_dir }}/[service-name]/.env
171 - Individual service directories for ARR stack
172 - Media Storage: {{ storage_base_path }}/media
173 - Downloads: {{ storage_base_path }}/downloads
174 - Backups: {{ storage_base_path }}/backups
175
176 ð§ Management Tools:
177 - Glances Monitoring: http://{{ ansible_default_ipv4.address }}:61208
178
179 roles:
180 # Core prerequisites
181 - role: user
182 tags: ['core', 'user']
183
184 - role: system
185 tags: ['core', 'system']
186
187 - role: geerlingguy.docker
188 tags: ['core', 'docker']
189
190 - role: geerlingguy.security
191 tags: ['core', 'security']
192
193 - role: docker-framework
194 tags: ['docker-framework', 'core', 'docker']
195
196 # Storage-specific services
197 - role: storage
198 tags: ['storage', 'media', 'backup']
199
200 # RAID monitoring for storage server
201 - role: nas
202 tags: ['storage', 'raid']
203 vars:
204 # Only run monitoring tasks from NAS role
205 nas_nfs_enabled: false
206 nas_network_bonding_enabled: false
207 nas_performance_tuning_enabled: false
208 nas_backup_integration: false
209 nas_enable_runner_exports: false
210 nas_raid_monitoring: true
211 nas_smartmontools_enabled: true
212
213 # Monitoring with RAID support for storage server
214 - role: monitoring
215 tags: ['monitoring', 'glances']
216 vars:
217 monitoring_enabled: true
218 glances_storage_monitoring: true
219 # RAID5 array drives - update if drives change
220 glances_storage_drives:
221 - { device: "sda", label: "Disk 1" }
222 - { device: "sdb", label: "Disk 2" }
223 - { device: "sdc", label: "Disk 3" }
224 - { device: "sdd", label: "Disk 4" }
225 - { device: "sde", label: "Disk 5" }
226 - { device: "sdf", label: "Disk 6" }
227 glances_monitored_directories:
228 - path: "/mnt/rstorage"
229 description: "RAID storage array"
230 - path: "/docker"
231 description: "Docker data"
232
233 # Backup role for system-level backups
234 - role: backup
235 tags: ['system-backup']
236 when: backup_enabled | default(true) and ('system-backup' in ansible_run_tags or ansible_run_tags | length == 0)
237
238 post_tasks:
239 - name: Verify core services are running
240 systemd:
241 name: "{{ item }}"
242 state: started
243 enabled: yes
244 loop:
245 - docker
246 - mdmonitor
247 - smartd
248 tags: ['verification']
249
250 - name: Verify RAID monitoring is active
251 command: systemctl is-active mdmonitor
252 register: mdmonitor_status
253 changed_when: false
254 ignore_errors: true
255 tags: ['verification', 'raid']
256
257 - name: Verify SMART monitoring is active
258 command: systemctl is-active smartd
259 register: smartd_status
260 changed_when: false
261 ignore_errors: true
262 tags: ['verification', 'smart']
263
264 - name: Generate storage access summary
265 template:
266 src: "{{ role_path }}/templates/storage-access-summary.txt.j2"
267 dest: "{{ storage_docker_dir }}/storage-access-info.txt"
268 owner: "{{ storage_user | default(ansible_user) }}"
269 group: "{{ storage_group | default(ansible_user) }}"
270 mode: '0644'
271 vars:
272 role_path: "roles/storage"
273 tags: ['summary']
274
275
276 - name: Wait for all storage services to be healthy
277 uri:
278 url: "http://{{ ansible_default_ipv4.address }}:{{ item.port }}{{ item.path | default('') }}"
279 method: GET
280 status_code: [200, 302, 401] # Some services redirect or require auth
281 loop:
282 - { port: "{{ jellyfin_host_port | default(8096) }}", path: "/health", service: "Jellyfin" }
283 - { port: "{{ sonarr_host_port | default(8989) }}", path: "/ping", service: "Sonarr" }
284 - { port: "{{ radarr_host_port | default(7878) }}", path: "/ping", service: "Radarr" }
285 - { port: "{{ prowlarr_host_port | default(9696) }}", path: "/ping", service: "Prowlarr" }
286 - { port: "{{ lazylibrarian_host_port | default(5299) }}", path: "/", service: "LazyLibrarian" }
287 - { port: "{{ jellyseer_host_port | default(5055) }}", path: "/", service: "Jellyseer" }
288 - { port: "{{ flaresolverr_host_port | default(8191) }}", path: "/health", service: "Flaresolverr" }
289 - { port: "{{ calibre_web_host_port | default(8084) }}", path: "/", service: "Calibre-Web" }
290 - { port: "{{ restic_backup_host_port | default(8000) }}", path: "/", service: "Restic Backup" }
291 retries: 15
292 delay: 10
293 ignore_errors: yes
294 tags: ['verification', 'health-check']
295
296 - name: Display deployment completion summary
297 debug:
298 msg: |
299 ð Storage Services Deployment Complete!
300
301 Server: {{ inventory_hostname }} ({{ ansible_default_ipv4.address }})
302
303 ð Monitoring Status:
304 - RAID Monitoring: {{ 'Active' if mdmonitor_status is defined and mdmonitor_status.rc == 0 else 'Pending' }}
305 - SMART Monitoring: {{ 'Active' if smartd_status is defined and smartd_status.rc == 0 else 'Pending' }}
306 - Docker: Active
307
308 ð Access Information:
309 {% if jellyfin_enabled | default(true) %}
310 - Jellyfin: http://{{ ansible_default_ipv4.address }}:{{ jellyfin_host_port | default(8096) }} (CPU Hardware Acceleration)
311 {% endif %}
312 {% if arr_stack_enabled | default(true) %}
313 - Sonarr: http://{{ ansible_default_ipv4.address }}:{{ sonarr_host_port | default(8989) }}
314 - Radarr: http://{{ ansible_default_ipv4.address }}:{{ radarr_host_port | default(7878) }}
315 - Prowlarr: http://{{ ansible_default_ipv4.address }}:{{ prowlarr_host_port | default(9696) }}
316 - LazyLibrarian: http://{{ ansible_default_ipv4.address }}:{{ lazylibrarian_host_port | default(5299) }}
317 - Jellyseer: http://{{ ansible_default_ipv4.address }}:{{ jellyseer_host_port | default(5055) }} (NEW!)
318 - Flaresolverr: http://{{ ansible_default_ipv4.address }}:{{ flaresolverr_host_port | default(8191) }} (Cloudflare bypass)
319 {% endif %}
320 {% if calibre_enabled | default(true) %}
321 - Calibre-Web: http://{{ ansible_default_ipv4.address }}:{{ calibre_web_host_port | default(8084) }}
322 {% endif %}
323 {% if restic_backup_server_enabled | default(true) %}
324 - Restic Backup: http://{{ ansible_default_ipv4.address }}:{{ restic_backup_host_port | default(8000) }}
325 {% endif %}
326
327 ð File Locations:
328 - Docker Configs: {{ storage_docker_dir }}
329 - Media Storage: {{ storage_base_path }}/media
330 - Downloads: {{ storage_base_path }}/downloads
331 - Backups: {{ storage_base_path }}/backups
332 - Access Info: {{ storage_docker_dir }}/storage-access-info.txt
333 - SMART Monitoring: /etc/smartd.conf
334
335 ð¡ï¸ Monitoring Commands:
336 - RAID Status: cat /proc/mdstat
337 - SMART Status: smartctl -a /dev/sdX
338 - Glances: http://{{ ansible_default_ipv4.address }}:61208
339
340 ð§ Next Steps:
341 1. Configure VPN credentials in vault for Gluetun
342 2. Set up indexers in Prowlarr
343 3. Configure quality profiles in Arr applications
344 4. Add media libraries in Jellyfin
345 5. Import books to Calibre library
346 6. Configure Jellyseer with Jellyfin and Arr app integration
347 7. Set up backup clients using: {{ storage_docker_dir }}/restic-server/client-setup-example.sh
348
349 â¡ Management Commands:
350 - Restart All: docker restart $(docker ps -q)
351 - View Logs: docker compose logs -f (in service directories)
352 - Monitor RAID: watch cat /proc/mdstat
353
354 ð¡ Important Notes:
355 - RAID arrays monitored every 5 minutes
356 - SMART monitoring active for drive health
357 - All services configured for CPU-only operation (no GPU)
358 - Individual service directories for ARR stack components
359 tags: ['always']