/
/
/
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 - Health monitoring will be provided by netdata (to be deployed separately)
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 MDADM support for storage server
214 - role: monitoring
215 tags: ['monitoring', 'netdata', 'mdadm']
216 vars:
217 monitoring_enabled: true
218 netdata_client_enabled: true
219 netdata_mdadm_monitoring_enabled: true
220
221 # Backup role for system-level backups
222 - role: backup
223 tags: ['system-backup']
224 when: backup_enabled | default(true) and ('system-backup' in ansible_run_tags or ansible_run_tags | length == 0)
225
226 post_tasks:
227 - name: Verify core services are running
228 systemd:
229 name: "{{ item }}"
230 state: started
231 enabled: yes
232 loop:
233 - docker
234 - mdmonitor
235 - smartd
236 tags: ['verification']
237
238 - name: Verify RAID monitoring is active
239 command: systemctl is-active mdmonitor
240 register: mdmonitor_status
241 changed_when: false
242 ignore_errors: true
243 tags: ['verification', 'raid']
244
245 - name: Verify SMART monitoring is active
246 command: systemctl is-active smartd
247 register: smartd_status
248 changed_when: false
249 ignore_errors: true
250 tags: ['verification', 'smart']
251
252 - name: Generate storage access summary
253 template:
254 src: "{{ role_path }}/templates/storage-access-summary.txt.j2"
255 dest: "{{ storage_docker_dir }}/storage-access-info.txt"
256 owner: "{{ storage_user | default(ansible_user) }}"
257 group: "{{ storage_group | default(ansible_user) }}"
258 mode: '0644'
259 vars:
260 role_path: "roles/storage"
261 tags: ['summary']
262
263
264 - name: Wait for all storage services to be healthy
265 uri:
266 url: "http://{{ ansible_default_ipv4.address }}:{{ item.port }}{{ item.path | default('') }}"
267 method: GET
268 status_code: [200, 302, 401] # Some services redirect or require auth
269 loop:
270 - { port: "{{ jellyfin_host_port | default(8096) }}", path: "/health", service: "Jellyfin" }
271 - { port: "{{ sonarr_host_port | default(8989) }}", path: "/ping", service: "Sonarr" }
272 - { port: "{{ radarr_host_port | default(7878) }}", path: "/ping", service: "Radarr" }
273 - { port: "{{ prowlarr_host_port | default(9696) }}", path: "/ping", service: "Prowlarr" }
274 - { port: "{{ lazylibrarian_host_port | default(5299) }}", path: "/", service: "LazyLibrarian" }
275 - { port: "{{ jellyseer_host_port | default(5055) }}", path: "/", service: "Jellyseer" }
276 - { port: "{{ flaresolverr_host_port | default(8191) }}", path: "/health", service: "Flaresolverr" }
277 - { port: "{{ calibre_web_host_port | default(8084) }}", path: "/", service: "Calibre-Web" }
278 - { port: "{{ restic_backup_host_port | default(8000) }}", path: "/", service: "Restic Backup" }
279 retries: 15
280 delay: 10
281 ignore_errors: yes
282 tags: ['verification', 'health-check']
283
284 - name: Display deployment completion summary
285 debug:
286 msg: |
287 ð Storage Services Deployment Complete!
288
289 Server: {{ inventory_hostname }} ({{ ansible_default_ipv4.address }})
290
291 ð Monitoring Status:
292 - RAID Monitoring: {{ 'Active' if mdmonitor_status is defined and mdmonitor_status.rc == 0 else 'Pending' }}
293 - SMART Monitoring: {{ 'Active' if smartd_status is defined and smartd_status.rc == 0 else 'Pending' }}
294 - Docker: Active
295
296 ð Access Information:
297 {% if jellyfin_enabled | default(true) %}
298 - Jellyfin: http://{{ ansible_default_ipv4.address }}:{{ jellyfin_host_port | default(8096) }} (CPU Hardware Acceleration)
299 {% endif %}
300 {% if arr_stack_enabled | default(true) %}
301 - Sonarr: http://{{ ansible_default_ipv4.address }}:{{ sonarr_host_port | default(8989) }}
302 - Radarr: http://{{ ansible_default_ipv4.address }}:{{ radarr_host_port | default(7878) }}
303 - Prowlarr: http://{{ ansible_default_ipv4.address }}:{{ prowlarr_host_port | default(9696) }}
304 - LazyLibrarian: http://{{ ansible_default_ipv4.address }}:{{ lazylibrarian_host_port | default(5299) }}
305 - Jellyseer: http://{{ ansible_default_ipv4.address }}:{{ jellyseer_host_port | default(5055) }} (NEW!)
306 - Flaresolverr: http://{{ ansible_default_ipv4.address }}:{{ flaresolverr_host_port | default(8191) }} (Cloudflare bypass)
307 {% endif %}
308 {% if calibre_enabled | default(true) %}
309 - Calibre-Web: http://{{ ansible_default_ipv4.address }}:{{ calibre_web_host_port | default(8084) }}
310 {% endif %}
311 {% if restic_backup_server_enabled | default(true) %}
312 - Restic Backup: http://{{ ansible_default_ipv4.address }}:{{ restic_backup_host_port | default(8000) }}
313 {% endif %}
314
315 ð File Locations:
316 - Docker Configs: {{ storage_docker_dir }}
317 - Media Storage: {{ storage_base_path }}/media
318 - Downloads: {{ storage_base_path }}/downloads
319 - Backups: {{ storage_base_path }}/backups
320 - Access Info: {{ storage_docker_dir }}/storage-access-info.txt
321 - SMART Monitoring: /etc/smartd.conf
322
323 ð¡ï¸ Monitoring Commands:
324 - RAID Status: cat /proc/mdstat
325 - SMART Status: smartctl -a /dev/sdX
326 - Service Health: Will be monitored by netdata (to be deployed separately)
327
328 ð§ Next Steps:
329 1. Configure VPN credentials in vault for Gluetun
330 2. Set up indexers in Prowlarr
331 3. Configure quality profiles in Arr applications
332 4. Add media libraries in Jellyfin
333 5. Import books to Calibre library
334 6. Configure Jellyseer with Jellyfin and Arr app integration
335 7. Set up backup clients using: {{ storage_docker_dir }}/restic-server/client-setup-example.sh
336
337 â¡ Management Commands:
338 - Restart All: docker restart $(docker ps -q)
339 - View Logs: docker compose logs -f (in service directories)
340 - Monitor RAID: watch cat /proc/mdstat
341
342 ð¡ Important Notes:
343 - RAID arrays monitored every 5 minutes
344 - SMART monitoring active for drive health
345 - All services configured for CPU-only operation (no GPU)
346 - Individual service directories for ARR stack components
347 tags: ['always']