/
/
/
Ansible role that deployes services on my runner machine
1---
2# Runner Services Role Defaults
3# Multi-service container deployment with NFS integration
4
5# ==============================================================================
6# GENERAL SETTINGS
7# ==============================================================================
8runner_enabled: true
9# Legacy variables - no longer used with consolidated structure
10# runner_docker_dir: "/docker/runner" # Now each service uses /docker/[service]
11# runner_data_dir: "/docker/runner-data" # Now consolidated into each service directory
12runner_nfs_mount_dir: "/mnt/docker"
13runner_snapshot_dir: "{{ runner_nfs_mount_dir }}/frigate/snapshots"
14runner_user: "{{ ansible_user }}"
15runner_group: "users"
16runner_uid: 1000
17runner_gid: 1000
18
19# Timezone configuration
20runner_timezone: "{{ system_timezone }}"
21
22# Docker network for runner services
23runner_docker_network: "runner-network"
24runner_network_subnet: "172.20.0.0/16"
25
26# NFS Configuration
27runner_nfs_enabled: true
28runner_nas_host: "{{ vault_storage.nas_host | default('storage.home') }}"
29runner_nfs_options: "nfsvers=4.1,proto=tcp,hard,timeo=600,retrans=2,rsize=16777216,wsize=16777216,nconnect=8,noatime,_netdev"
30
31# ==============================================================================
32# NFS MOUNT CONFIGURATION
33# ==============================================================================
34runner_nfs_mounts:
35 - name: "frigate"
36 local_path: "{{ runner_nfs_mount_dir }}/frigate"
37 nfs_path: "/mnt/rstorage/cctv-data"
38 host: "{{ runner_nas_host }}"
39 options: "{{ runner_nfs_options }}"
40
41 - name: "immich"
42 local_path: "{{ runner_nfs_mount_dir }}/immich"
43 nfs_path: "/mnt/rstorage/media/pictures"
44 host: "{{ runner_nas_host }}"
45 options: "{{ runner_nfs_options }}"
46
47 - name: "forgejo"
48 local_path: "{{ runner_nfs_mount_dir }}/forgejo"
49 nfs_path: "/mnt/rstorage/code-repo"
50 host: "{{ runner_nas_host }}"
51 options: "{{ runner_nfs_options }}"
52
53 - name: "harbor"
54 local_path: "{{ runner_nfs_mount_dir }}/harbor"
55 nfs_path: "/mnt/rstorage/registry-data"
56 host: "{{ runner_nas_host }}"
57 options: "{{ runner_nfs_options }}"
58
59 - name: "cvat"
60 local_path: "{{ runner_nfs_mount_dir }}/cvat"
61 nfs_path: "/mnt/rstorage/cvat-datasets"
62 host: "{{ runner_nas_host }}"
63 options: "{{ runner_nfs_options }}"
64
65 # D&D session agent: ambience, effects, recordings, transcripts and the
66 # campaign knowledge base all live under one mount.
67 - name: "dnd"
68 local_path: "{{ runner_nfs_mount_dir }}/dnd"
69 nfs_path: "/mnt/rstorage/media/dnd"
70 host: "{{ runner_nas_host }}"
71 options: "{{ runner_nfs_options }}"
72
73# ==============================================================================
74# SERVICE CONFIGURATIONS
75# ==============================================================================
76
77# LLM Stack - Unified Local AI Infrastructure
78llm_stack_enabled: true
79llm_stack_config_dir: "/docker/llm-stack"
80
81# Unified LLM Stack Configuration
82llm_stack_ollama_port: 9000
83llm_stack_openwebui_port: 9001
84llm_stack_litellm_port: 9002
85
86# Ollama - Local LLM Server
87llm_stack_ollama_data_dir: "{{ llm_stack_config_dir }}/ollama/data"
88# GPU configuration uses global gpu_enabled parameter
89llm_stack_ollama_default_model: "qwen3:14b"
90llm_stack_ollama_keep_alive: "1h"
91llm_stack_ollama_log_level: "info"
92
93# Pull declared models on deploy. Only models absent from Ollama are fetched,
94# so re-runs are cheap. Set false to manage the model set by hand.
95llm_stack_pull_models: true
96llm_stack_model_pull_timeout: 3600
97
98# Model lineup
99llm_stack_models:
100 - name: "qwen3:14b"
101 role: "default"
102 description: "General-purpose daily driver"
103 - name: "phi4:14b"
104 role: "coder"
105 description: "Coding and analytical tasks"
106 - name: "qwen2.5-coder:14b"
107 role: "coder"
108 description: "Coding specialist (Qwen 2.5 generation)"
109 - name: "deepseek-r1:14b"
110 role: "reasoning"
111 description: "Complex reasoning and chain-of-thought"
112 - name: "qwen3:8b"
113 role: "fast"
114 description: "Lightweight fast tasks"
115 - name: "qwen3:4b"
116 role: "minimal"
117 description: "Quick lightweight tasks; D&D live cue selection"
118 - name: "qwen2.5vl:7b"
119 role: "vision"
120 description: "Battlemap frame understanding for session chronicles"
121
122# OpenWebUI - Web Interface for Ollama
123llm_stack_openwebui_data_dir: "{{ llm_stack_config_dir }}/openwebui/data"
124llm_stack_openwebui_name: "Local AI Assistant"
125llm_stack_openwebui_description: "Self-hosted AI chat interface"
126llm_stack_openwebui_author: "Home Assistant"
127llm_stack_openwebui_default_models: "qwen3:14b"
128llm_stack_openwebui_log_level: "info"
129
130# LiteLLM - Unified LLM Proxy
131llm_stack_litellm_data_dir: "{{ llm_stack_config_dir }}/litellm/data"
132llm_stack_litellm_streaming_enabled: true
133llm_stack_litellm_log_level: "info"
134
135# =============================================================================
136# D&D Session Agent - The Thaumaturgeon
137# One compose project, one switch: dnd_enabled: false removes all of it.
138# =============================================================================
139dnd_enabled: false
140dnd_config_dir: "/docker/dnd"
141
142# Ambience, effects, recordings, transcripts and the campaign knowledge base
143# all live on the NAS under one mount.
144dnd_media_dir: "{{ runner_nfs_mount_dir }}/dnd"
145
146# Files written here are read by people and by the storage host, so the
147# container runs as the storage owner rather than root.
148dnd_uid: 1000
149dnd_gid: 100
150
151# The LiveKit room the agent joins. A stable name, not the random id Meet's
152# "Start Meeting" button generates.
153dnd_room: "dnd"
154dnd_avatar_url: "{{ 'https://' ~ livekit_domain ~ '/' ~ livekit_agent_avatar_filename }}"
155
156# Built on the runner from a checkout: the application changes far more often
157# than the infrastructure around it.
158dnd_build_from_source: true
159dnd_build_images: true
160dnd_source_dir: "/home/yannick/personal/dnd-agent"
161dnd_agent_version: "0.1.0"
162
163# Post-session processing
164dnd_chronicle_model: "qwen3:14b"
165dnd_model_dir: "{{ dnd_config_dir }}/models"
166# cuda when the driver matches torch's build; cpu still runs above realtime.
167dnd_diarize_device: "cuda"
168
169# Speech to text (Speaches / faster-whisper). Generic and OpenAI-compatible,
170# so anything else on the runner can use it too.
171dnd_stt_model_dir: "{{ dnd_config_dir }}/stt-models"
172dnd_stt_host_port: 9010
173
174# distil-large-v3 is near large-v3 accuracy for English at a fraction of the
175# VRAM, which matters because STT shares the 12 GB card with the cue-selection
176# model during a live session.
177dnd_stt_model: "Systran/faster-distil-whisper-large-v3"
178dnd_stt_compute_type: "int8_float16"
179
180# -1 keeps the model resident forever. Sessions have long silent gaps and a
181# cold reload would add seconds to the live path.
182dnd_stt_model_ttl: -1
183dnd_stt_start_period: "300s"
184dnd_stt_model_download_timeout: 900
185dnd_stt_log_level: "info"
186
187# Frigate - AI NVR System
188frigate_enabled: true
189frigate_port: 5000
190frigate_rtmp_port: 1935
191frigate_rtsp_port: 8554
192frigate_go2rtc_port: 1984
193frigate_config_dir: "/docker/frigate"
194frigate_data_dir: "{{ runner_nfs_mount_dir }}/frigate"
195frigate_mqtt_enabled: true
196mqtt_host: "homeassistant.home"
197mqtt_port: 1883
198frigate_hardware_acceleration: "vaapi" # vaapi, nvdec, nvenc, qsv, or none
199
200# Frigate ONVIF default settings
201frigate_onvif_autotracking: false
202
203# Frigate cameras configuration (from vault)
204frigate_cameras:
205 - name: "dining-room"
206 host: "{{ vault_runner.dining_room_camera_host | default('') }}"
207 username: "{{ vault_runner.dining_room_camera_user | default('') }}"
208 password: "{{ vault_runner.dining_room_camera_pass | default('') }}"
209 path: "/stream1"
210 port: 554
211 onvif_port: 2020
212 enabled: true
213
214 - name: "living-room"
215 host: "{{ vault_runner.living_room_camera_host | default('') }}"
216 username: "{{ vault_runner.living_room_camera_user | default('') }}"
217 password: "{{ vault_runner.living_room_camera_pass | default('') }}"
218 path: "/stream1"
219 port: 554
220 onvif_port: 2020
221 enabled: true
222
223 - name: "bed-room"
224 host: "{{ vault_runner.bed_room_camera_host | default('') }}"
225 username: "{{ vault_runner.bed_room_camera_user | default('') }}"
226 password: "{{ vault_runner.bed_room_camera_pass | default('') }}"
227 path: "/stream1"
228 port: 554
229 onvif_port: 2020
230 enabled: true
231
232 - name: "alina-office"
233 host: "{{ vault_runner.alina_office_camera_host | default('') }}"
234 username: "{{ vault_runner.alina_office_camera_user | default('') }}"
235 password: "{{ vault_runner.alina_office_camera_pass | default('') }}"
236 path: "/stream1"
237 port: 554
238 onvif_port: 2020
239 enabled: true
240
241 - name: "street-cam"
242 host: "{{ vault_runner.street_cam_host | default('') }}"
243 username: "{{ vault_runner.street_cam_user | default('') }}"
244 password: "{{ vault_runner.street_cam_pass | default('') }}"
245 path: "/stream1"
246 port: 554
247 onvif_port: 2020
248 enabled: true
249
250 - name: "pi-cam"
251 host: "{{ vault_runner.pi_cam_host | default('') }}"
252 username: "{{ vault_runner.pi_cam_user | default('') }}"
253 password: "{{ vault_runner.pi_cam_pass | default('') }}"
254 path: "{{ vault_runner.pi_cam_path | default('/doorbell') }}"
255 port: 8554
256 enabled: true
257
258# Cameras with ONVIF support (for PTZ control)
259frigate_onvif_cameras:
260 - name: "dining-room"
261 host: "{{ vault_runner.dining_room_camera_host | default('') }}"
262 port: 2020
263 username: "{{ vault_runner.dining_room_camera_user | default('') }}"
264 password: "{{ vault_runner.dining_room_camera_pass | default('') }}"
265 - name: "living-room"
266 host: "{{ vault_runner.living_room_camera_host | default('') }}"
267 port: 2020
268 username: "{{ vault_runner.living_room_camera_user | default('') }}"
269 password: "{{ vault_runner.living_room_camera_pass | default('') }}"
270 - name: "bed-room"
271 host: "{{ vault_runner.bed_room_camera_host | default('') }}"
272 port: 2020
273 username: "{{ vault_runner.bed_room_camera_user | default('') }}"
274 password: "{{ vault_runner.bed_room_camera_pass | default('') }}"
275 - name: "alina-office"
276 host: "{{ vault_runner.alina_office_camera_host | default('') }}"
277 port: 2020
278 username: "{{ vault_runner.alina_office_camera_user | default('') }}"
279 password: "{{ vault_runner.alina_office_camera_pass | default('') }}"
280 - name: "street-cam"
281 host: "{{ vault_runner.street_cam_host | default('') }}"
282 port: 2020
283 username: "{{ vault_runner.street_cam_user | default('') }}"
284 password: "{{ vault_runner.street_cam_pass | default('') }}"
285
286# Cameras for snapshot capture
287frigate_snapshot_cameras:
288 - "living-room"
289 - "dining-room"
290 - "bed-room"
291 - "alina-office"
292 - "street-cam"
293 - "pi-cam"
294
295# Per-camera record override (cameras listed here have recording disabled)
296frigate_cameras_record_disabled:
297 - "bed-room"
298
299# Frigate profile system (home=record-only, away=full detection)
300frigate_profiles:
301 home:
302 detect_enabled: false
303 detectors_enabled: false
304 cameras:
305 - street-cam
306 - pi-cam
307 hwaccel_override_cameras:
308 - street-cam
309 - pi-cam
310 default_profile: false
311 away:
312 detect_enabled: true
313 detectors_enabled: true
314 cameras:
315 - living-room
316 - dining-room
317 - bed-room
318 - alina-office
319 - street-cam
320 - pi-cam
321 default_profile: true
322
323# Frigate manager service
324frigate_manager_enabled: true
325frigate_snapshot_interval_minutes: 30
326frigate_profile_switch_timeout: 120
327
328# Immich - Photo Management
329immich_enabled: true
330immich_server_port: 2283
331immich_ml_port: 3003
332immich_config_dir: "/docker/immich"
333immich_data_dir: "/docker/immich"
334immich_upload_dir: "{{ runner_nfs_mount_dir }}/immich/library"
335immich_db_name: "{{ vault_runner.postgres_db | default('') }}"
336immich_db_user: "{{ vault_runner.postgres_user | default('') }}"
337immich_redis_enabled: true
338immich_ml_enabled: true
339immich_facial_recognition: true
340immich_hardware_acceleration: "none" # none, vaapi, nvdec, nvenc, or qsv
341
342# External library mounts (read-only photo libraries from NFS)
343immich_external_libraries:
344 - name: "yannick"
345 host_path: "{{ runner_nfs_mount_dir }}/immich/yannick"
346 container_path: "/yannick"
347 - name: "alina"
348 host_path: "{{ runner_nfs_mount_dir }}/immich/alina"
349 container_path: "/alina"
350
351# Forgejo - Git Server
352forgejo_enabled: true
353forgejo_http_port: 3010
354forgejo_ssh_port: 2222
355forgejo_config_dir: "/docker/forgejo"
356forgejo_data_dir: "{{ runner_nfs_mount_dir }}/forgejo"
357forgejo_db_type: "sqlite3"
358forgejo_app_name: "Forgejo Git Service"
359forgejo_domain: "forgejo.home"
360forgejo_ssh_domain: "{{ ansible_default_ipv4.address }}"
361
362# Forgejo Runner Configuration
363forgejo_runner_enabled: true
364forgejo_runner_token: "{{ vault_runner.forgejo_runner_token | default('changeme') }}"
365forgejo_runner_name: "default-runner"
366forgejo_runner_capacity: 2
367forgejo_runner_loglevel: info
368forgejo_runner_base_image: ubuntu:22.04
369forgejo_runner_user: runner
370forgejo_runner_uid: 1000
371forgejo_runner_version: 9.1.1
372
373# Forgejo Ansible Runner (for running Ansible playbooks in CI)
374forgejo_ansible_runner_enabled: true
375forgejo_ansible_runner_token: "{{ vault_runner.forgejo_ansible_runner_token | default('changeme') }}"
376forgejo_ansible_runner_name: "ansible-runner"
377forgejo_ansible_runner_capacity: 1
378forgejo_ansible_runner_version: 9.1.1
379
380# Web Tools - PDF Processing, File Conversion, Data Analysis, Developer Utilities
381web_tools_enabled: true
382web_tools_config_dir: "/docker/web-tools"
383web_tools_data_dir: "/docker/web-tools"
384
385# Stirling-PDF
386web_tools_stirling_port: 8090
387web_tools_stirling_max_file_size: 100
388
389# ConvertX
390web_tools_convertx_port: 8091
391web_tools_convertx_jwt_secret: "{{ vault_runner.web_tools_convertx_jwt_secret | default('changeme-generate-a-real-secret') }}"
392web_tools_convertx_auto_delete_hours: 24
393
394# CyberChef
395web_tools_cyberchef_port: 8092
396
397# IT-Tools
398web_tools_it_tools_port: 8093
399
400# Tandoor - Recipe Manager
401tandoor_enabled: true
402tandoor_port: 8010
403tandoor_config_dir: "/docker/tandoor"
404tandoor_data_dir: "/docker/tandoor"
405tandoor_media_dir: "{{ tandoor_data_dir }}/media"
406tandoor_static_dir: "{{ tandoor_data_dir }}/static"
407tandoor_db_engine: "django.db.backends.postgresql"
408
409# Ghost CMS - Headless CMS
410ghost_enabled: true
411ghost_port: 2368
412ghost_config_dir: "/docker/ghost"
413ghost_data_dir: "/docker/ghost"
414ghost_content_dir: "{{ ghost_data_dir }}/content"
415ghost_db_client: "mysql"
416ghost_db_host: "ghost-mysql"
417ghost_db_name: "ghost"
418ghost_db_user: "ghost"
419ghost_url: "http://ghost.home"
420
421# ==============================================================================
422# LIVEKIT - WebRTC Conferencing (SFU) + Meet frontend
423# ==============================================================================
424# LiveKit is a WebRTC SFU. Unlike the other runner services it runs on
425# network_mode: host because Docker's userland proxy rewrites UDP source
426# addresses, which breaks ICE candidate resolution and silently kills media.
427livekit_enabled: true
428livekit_config_dir: "/docker/livekit"
429livekit_data_dir: "{{ livekit_config_dir }}/data"
430
431# Signaling / API (HTTP + WebSocket)
432livekit_port: 7880
433# WebRTC over TCP (ICE/TCP fallback for restrictive networks)
434livekit_tcp_port: 7881
435# WebRTC over UDP - single-port mux, avoids the 50000-60000 range entirely
436livekit_udp_port: 7882
437# Meet frontend (bridge network, normal published port)
438livekit_meet_port: 7883
439
440# Public hostnames handed to browser clients. When fronted by a reverse proxy
441# these are the proxy's names; otherwise they resolve straight to this host.
442livekit_domain: "livekit.home"
443livekit_sfu_domain: "{{ livekit_domain }}"
444
445# Set true when a reverse proxy terminates TLS in front of both hostnames.
446# Browsers require a secure context for getUserMedia, and a page served over
447# HTTPS may not open a ws:// socket, so this must be on for any non-localhost
448# browser access.
449livekit_tls_enabled: false
450
451# Behind a proxy the public URLs carry no port (443 is implied). Direct access
452# needs the explicit container ports.
453livekit_public_url: >-
454 {{ 'https://' ~ livekit_domain
455 if livekit_tls_enabled
456 else 'http://' ~ livekit_domain ~ ':' ~ livekit_meet_port }}
457livekit_public_ws_url: >-
458 {{ 'wss://' ~ livekit_sfu_domain
459 if livekit_tls_enabled
460 else 'ws://' ~ livekit_sfu_domain ~ ':' ~ livekit_port }}
461
462# API credentials (from vault). Generate with:
463# docker run --rm livekit/livekit-server generate-keys
464livekit_api_key: "{{ vault_runner.livekit_api_key | default('') }}"
465livekit_api_secret: "{{ vault_runner.livekit_api_secret | default('') }}"
466
467livekit_log_level: "info"
468
469# Avatar for the session agent, served by Meet at the site root so it can be
470# shown in place of a video tile (the agent publishes no camera).
471livekit_agent_avatar_enabled: true
472livekit_agent_avatar_filename: "thaumaturgeon.png"
473livekit_agent_avatar_src: "files/thaumaturgeon.png"
474
475# Room behaviour
476livekit_room_auto_create: true
477livekit_room_empty_timeout: 300 # seconds before an empty room closes
478livekit_room_departure_timeout: 20
479
480# Meet frontend build. Upstream publishes no release tags, so this is pinned to
481# a specific commit SHA rather than tracking the main branch. Bump deliberately.
482livekit_meet_repo_url: "https://github.com/livekit-examples/meet.git"
483livekit_meet_repo_version: "665e1cb7841ab872de0d8e5c310744009a763b08"
484# Matches the packageManager field in Meet's package.json
485livekit_meet_pnpm_version: "10.18.2"
486livekit_meet_node_version: "20-alpine"
487
488# Resource limits
489livekit_memory_limit: "2g"
490livekit_cpu_limit: "2"
491livekit_meet_memory_limit: "1g"
492livekit_meet_cpu_limit: "1"
493
494cvat_config_dir: "/docker/cvat"
495# CVAT - Data labeling
496cvat_enabled: true
497
498cvat_repo_url: "https://github.com/cvat-ai/cvat.git"
499cvat_repo_version: "{{ cvat_image_tag }}"
500
501cvat_admin_username: admin
502cvat_admin_password: "{{ vault_runner.cvat_admin_password | default('change-me') }}"
503cvat_admin_email: "{{ vault_runner.cvat_admin_email | default('change-me') }}"
504
505# Networking / access
506cvat_domain: "cvat.home" # used by Traefik routing in CVAT compose
507cvat_http_port: 8990 # Traefik "web" entrypoint in the default compose
508cvat_https_enabled: false # add CVAT's https overlay when true
509
510cvat_share_dir: >-
511 {{ (runner_nfs_mounts
512 | selectattr('name','equalto','cvat')
513 | map(attribute='local_path')
514 | first)
515 | default(runner_nfs_mount_dir ~ '/cvat', true) }}
516
517# Versioning / images
518cvat_image_tag: "v2.44.3" # pulled via CVAT_VERSION; align with the git tag you run
519
520# Optional: serverless auto-annotation overlay (Nuclio/SAM/YOLO assist in CVAT)
521cvat_serverless_enabled: false
522
523# Optional: use an external Postgres instead of the bundled one
524cvat_external_db_enabled: false
525cvat_db_host: "postgres.internal"
526cvat_db_port: 5432
527cvat_db_name: "cvat"
528cvat_db_user: "cvat_user"
529cvat_db_password: "{{ vault_runner.cvat_db_password | default('change-me') }}"
530
531# Optional: expose Traefik dashboard (binds host port below)
532cvat_traefik_dashboard_enabled: false
533cvat_dashboard_port: 8899
534
535# Optional: GPU reservation for CVAT server container (you must have host GPU runtime ready)
536cvat_gpu_enabled: false
537cvat_gpu_driver: "nvidia"
538cvat_gpu_count: "all" # or a number like "1"
539
540# ==============================================================================
541# DATABASE CONFIGURATIONS
542# ==============================================================================
543
544# PostgreSQL (Immich)
545postgres_enabled: "{{ immich_enabled }}"
546postgres_config_dir: "/docker/immich/postgres"
547postgres_db: "{{ vault_runner.postgres_db | default('') }}"
548postgres_user: "{{ vault_runner.postgres_user | default('') }}"
549postgres_version: "14"
550
551# Redis (Immich)
552redis_enabled: "{{ immich_redis_enabled }}"
553redis_config_dir: "/docker/immich/redis"
554redis_port: 6379
555
556# MySQL (Ghost CMS)
557mysql_enabled: "{{ ghost_enabled }}"
558mysql_config_dir: "/docker/ghost/mysql"
559mysql_db: "{{ ghost_db_name }}"
560mysql_user: "{{ ghost_db_user }}"
561mysql_version: "8.0"
562
563# ==============================================================================
564# SECURITY SETTINGS (FROM VAULT)
565# ==============================================================================
566
567# Database passwords
568postgres_password: "{{ vault_runner.postgres_password | default('') }}"
569mysql_password: "{{ vault_runner.mysql_password | default('') }}"
570mysql_root_password: "{{ vault_runner.mysql_root_password | default('') }}"
571
572# Service secrets
573immich_jwt_secret: "{{ vault_runner.immich_jwt_secret | default('') }}"
574ghost_database_password: "{{ vault_runner.ghost_database_password | default('') }}"
575tandoor_secret_key: "{{ vault_runner.tandoor_secret_key | default('') }}"
576
577# MQTT credentials (from vault - top-level, shared across all roles)
578mqtt_username: ""
579mqtt_password: ""
580
581# ==============================================================================
582# DIRECTORY STRUCTURE
583# ==============================================================================
584
585# Local configuration directories
586runner_config_directories:
587 - "{{ llm_stack_config_dir }}"
588 - "{{ llm_stack_ollama_data_dir }}"
589 - "{{ llm_stack_openwebui_data_dir }}"
590 - "{{ llm_stack_litellm_data_dir }}"
591 - "{{ frigate_config_dir }}"
592 - "{{ immich_config_dir }}"
593 - "{{ immich_config_dir }}/postgres"
594 - "{{ immich_config_dir }}/redis"
595 - "{{ immich_config_dir }}/library"
596 - "{{ immich_config_dir }}/cache"
597 - "{{ immich_config_dir }}/model-cache"
598 - "{{ immich_config_dir }}/postgres-init"
599 - "{{ forgejo_config_dir }}"
600 - "{{ forgejo_config_dir }}/forgejo-runner-data"
601 - "{{ forgejo_config_dir }}/forgejo-ansible-runner-data"
602 - "{{ web_tools_config_dir }}"
603 - "{{ tandoor_config_dir }}"
604 - "{{ tandoor_data_dir }}/db"
605 - "{{ tandoor_data_dir }}/media"
606 - "{{ tandoor_data_dir }}/static"
607 - "{{ ghost_config_dir }}/config"
608 - "{{ ghost_config_dir }}/content"
609 - "{{ ghost_config_dir }}/mysql"
610
611# NFS mount directories
612runner_nfs_directories:
613 - "{{ runner_nfs_mount_dir }}"
614 - "{{ runner_nfs_mount_dir }}/frigate"
615 - "{{ runner_nfs_mount_dir }}/immich"
616 - "{{ runner_nfs_mount_dir }}/forgejo"
617 - "{{ runner_nfs_mount_dir }}/harbor"
618 - "{{ runner_nfs_mount_dir }}/cvat"
619
620# ==============================================================================
621# PERFORMANCE SETTINGS
622# ==============================================================================
623
624# Network performance tuning for NFS
625runner_performance_tuning_enabled: true
626runner_sysctl_settings:
627 # Extreme performance network buffers for Ryzen 7 + 32GB RAM
628 net.core.rmem_max: 268435456 # 256MB socket receive buffer
629 net.core.wmem_max: 268435456 # 256MB socket send buffer
630 net.core.rmem_default: 33554432 # 32MB default receive buffer
631 net.core.wmem_default: 33554432 # 32MB default send buffer
632 net.ipv4.tcp_rmem: "4096 131072 268435456" # TCP receive: 4KB min, 128KB default, 256MB max
633 net.ipv4.tcp_wmem: "4096 131072 268435456" # TCP send: 4KB min, 128KB default, 256MB max
634 net.core.netdev_max_backlog: 30000 # Handle high connection burst (32 connections)
635 net.ipv4.tcp_congestion_control: "bbr" # BBR congestion control
636 net.ipv4.tcp_window_scaling: 1 # Enable TCP window scaling
637 net.ipv4.tcp_timestamps: 1 # Enable TCP timestamps for RTT calculation
638 net.ipv4.tcp_sack: 1 # Enable selective acknowledgments
639 # NFS client cache tuning for 32GB RAM
640 vm.dirty_background_ratio: 3 # Start writeback at 3% (more aggressive)
641 vm.dirty_ratio: 8 # Force writeback at 8% (more aggressive)
642 vm.vfs_cache_pressure: 25 # Keep even more file cache (25% vs 50%)
643 vm.min_free_kbytes: 131072 # Keep 128MB free for network buffers
644
645# Harbor Configuration (external deployment)
646harbor_enabled: true
647harbor_config_dir: "/docker/harbor"
648harbor_version: "2.13.2"
649harbor_hostname: "{{ vault_runner.harbor_hostname | default('registry.local') }}"
650harbor_http_port: 8080
651harbor_registry_port: 5000
652harbor_admin_password: "{{ vault_runner.harbor_admin_password | default('changeme') }}"
653harbor_db_password: "{{ vault_runner.harbor_db_password | default('changeme') }}"
654harbor_data_volume: "{{ runner_nfs_mount_dir }}/harbor"
655
656# Docker resource limits
657default_memory_limit: "1g"
658default_cpu_limit: "1"
659
660# Health check configuration
661health_check_interval: "30s"
662health_check_timeout: "30s"
663health_check_retries: 5
664health_check_start_period: "60s"
665
666# ==============================================================================
667# SERVICE HEALTH ENDPOINTS
668# ==============================================================================
669service_endpoints:
670 ollama: "http://localhost:{{ llm_stack_ollama_port }}/api/tags"
671 openwebui: "http://localhost:{{ llm_stack_openwebui_port }}/api/health"
672 litellm: "http://localhost:{{ llm_stack_litellm_port }}/"
673 frigate: "http://localhost:{{ frigate_port }}/api/config"
674 immich: "http://localhost:{{ immich_server_port }}/api/server-info/ping"
675 forgejo: "http://localhost:{{ forgejo_http_port }}/api/v1/version"
676 stirling_pdf: "http://localhost:{{ web_tools_stirling_port }}/api/v1/info/status"
677 convertx: "http://localhost:{{ web_tools_convertx_port }}/"
678 cyberchef: "http://localhost:{{ web_tools_cyberchef_port }}/"
679 it_tools: "http://localhost:{{ web_tools_it_tools_port }}/"
680 tandoor: "http://localhost:{{ tandoor_port }}/accounts/login/"
681 ghost: "http://localhost:{{ ghost_port }}/ghost/api/admin/site/"
682 livekit: "http://localhost:{{ livekit_port }}/"
683 livekit_meet: "http://localhost:{{ livekit_meet_port }}/"
684
685# ==============================================================================
686# LOGGING CONFIGURATION
687# ==============================================================================
688logging_driver: "json-file"
689logging_max_size: "10m"
690logging_max_file: "3"
691
692# Service-specific logging levels (unified LLM stack uses llm_stack_* variables)
693# ollama_log_level: "info" # Now uses llm_stack_ollama_log_level
694# openwebui_log_level: "info" # Now uses llm_stack_openwebui_log_level
695# litellm_log_level: "info" # Now uses llm_stack_litellm_log_level
696frigate_log_level: "info"
697immich_log_level: "log"
698forgejo_log_level: "Info"
699ghost_logging: "info"
700
701