/
/
/
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# Every D&D image comes from Harbor. Shared by the agent, the post-processor
143# and the table server so there is one place to change the registry.
144dnd_registry: "{{ vault_runner.harbor_hostname | default('ys-registry.com') }}"
145dnd_registry_project: "docker"
146# Ansible runs as root, which has its own docker credential store, so a login
147# is needed even though this host serves the registry.
148dnd_registry_username: "{{ (vault_dnd_table | default({})).registry_username | default('admin') }}"
149dnd_registry_password: >-
150 {{ (vault_dnd_table | default({})).registry_password
151 | default(vault_runner.harbor_admin_password) }}
152
153# Ambience, effects, recordings, transcripts and the campaign knowledge base
154# all live on the NAS under one mount.
155dnd_media_dir: "{{ runner_nfs_mount_dir }}/dnd"
156
157# Files written here are read by people and by the storage host, so the
158# container runs as the storage owner rather than root.
159dnd_uid: 1000
160dnd_gid: 100
161
162# The LiveKit room the agent joins. A stable name, not the random id Meet's
163# "Start Meeting" button generates.
164dnd_room: "dnd"
165dnd_avatar_url: "{{ 'https://' ~ livekit_domain ~ '/' ~ livekit_agent_avatar_filename }}"
166
167# Pulled from Harbor. CI builds on every push to main and tags by commit SHA
168# plus `latest`; there are no release versions to maintain.
169#
170# Deploys follow `latest`. To pin a known-good set instead, set dnd_image_tag
171# to the parent repository's SHA - its pin workflow re-tags every component's
172# image with that one tag, so a single value reverts the whole system.
173dnd_image_tag: "latest"
174dnd_agent_image: >-
175 {{ dnd_registry }}/{{ dnd_registry_project }}/dnd-agent:{{ dnd_image_tag }}
176dnd_post_image: >-
177 {{ dnd_registry }}/{{ dnd_registry_project }}/dnd-post:{{ dnd_image_tag }}
178
179# Post-session processing
180dnd_chronicle_model: "qwen3:14b"
181dnd_model_dir: "{{ dnd_config_dir }}/models"
182# cuda when the driver matches torch's build; cpu still runs above realtime.
183dnd_diarize_device: "cuda"
184
185# Speech to text (Speaches / faster-whisper). Generic and OpenAI-compatible,
186# so anything else on the runner can use it too.
187dnd_stt_model_dir: "{{ dnd_config_dir }}/stt-models"
188dnd_stt_host_port: 9010
189
190# distil-large-v3 is near large-v3 accuracy for English at a fraction of the
191# VRAM, which matters because STT shares the 12 GB card with the cue-selection
192# model during a live session.
193dnd_stt_model: "Systran/faster-distil-whisper-large-v3"
194dnd_stt_compute_type: "int8_float16"
195
196# -1 keeps the model resident forever. Sessions have long silent gaps and a
197# cold reload would add seconds to the live path.
198dnd_stt_model_ttl: -1
199dnd_stt_start_period: "300s"
200dnd_stt_model_download_timeout: 900
201dnd_stt_log_level: "info"
202
203# --- D&D table server -------------------------------------------------------
204# Renders the kiosk page with headless Chromium and streams it to the table Pi,
205# which only hardware-decodes it. This is why the Pi can be a thin appliance:
206# all the rendering happens here, where there is a GPU and no thermal limit.
207#
208# Unlike dnd-agent, this image is pulled from Harbor rather than built here --
209# it is also deployed to the Pi, so one build serves both.
210dnd_table_enabled: false
211# Registry settings and the image tag are shared with the agent above, so one
212# tag reverts every component together.
213dnd_table_runner_image: >-
214 {{ dnd_registry }}/{{ dnd_registry_project }}/dnd-tabletop-server:{{ dnd_image_tag }}
215
216# Where the kiosk stream is sent. Must match the Pi's TABLE_RTP_PORT.
217dnd_table_pi_host: "dnd-table.home"
218dnd_table_rtp_port: 5600
219
220# 720p to match the projector's native panel; sending 1080p would add a
221# scaling pass on a display that cannot show it.
222dnd_table_width: 1280
223dnd_table_height: 720
224dnd_table_fps: 30
225dnd_table_bitrate_kbps: 4000
226
227# The DM control surface.
228dnd_table_http_port: 8081
229
230# Maps and handouts. Lives on the NAS beside the rest of the campaign media.
231dnd_table_media_dir: "{{ dnd_media_dir }}/table"
232
233# Frigate - AI NVR System
234frigate_enabled: true
235frigate_port: 5000
236frigate_rtmp_port: 1935
237frigate_rtsp_port: 8554
238frigate_go2rtc_port: 1984
239frigate_config_dir: "/docker/frigate"
240frigate_data_dir: "{{ runner_nfs_mount_dir }}/frigate"
241frigate_mqtt_enabled: true
242mqtt_host: "homeassistant.home"
243mqtt_port: 1883
244frigate_hardware_acceleration: "vaapi" # vaapi, nvdec, nvenc, qsv, or none
245
246# Frigate ONVIF default settings
247frigate_onvif_autotracking: false
248
249# Frigate cameras configuration (from vault)
250frigate_cameras:
251 - name: "dining-room"
252 host: "{{ vault_runner.dining_room_camera_host | default('') }}"
253 username: "{{ vault_runner.dining_room_camera_user | default('') }}"
254 password: "{{ vault_runner.dining_room_camera_pass | default('') }}"
255 path: "/stream1"
256 port: 554
257 onvif_port: 2020
258 enabled: true
259
260 - name: "living-room"
261 host: "{{ vault_runner.living_room_camera_host | default('') }}"
262 username: "{{ vault_runner.living_room_camera_user | default('') }}"
263 password: "{{ vault_runner.living_room_camera_pass | default('') }}"
264 path: "/stream1"
265 port: 554
266 onvif_port: 2020
267 enabled: true
268
269 - name: "bed-room"
270 host: "{{ vault_runner.bed_room_camera_host | default('') }}"
271 username: "{{ vault_runner.bed_room_camera_user | default('') }}"
272 password: "{{ vault_runner.bed_room_camera_pass | default('') }}"
273 path: "/stream1"
274 port: 554
275 onvif_port: 2020
276 enabled: true
277
278 - name: "alina-office"
279 host: "{{ vault_runner.alina_office_camera_host | default('') }}"
280 username: "{{ vault_runner.alina_office_camera_user | default('') }}"
281 password: "{{ vault_runner.alina_office_camera_pass | default('') }}"
282 path: "/stream1"
283 port: 554
284 onvif_port: 2020
285 enabled: true
286
287 - name: "street-cam"
288 host: "{{ vault_runner.street_cam_host | default('') }}"
289 username: "{{ vault_runner.street_cam_user | default('') }}"
290 password: "{{ vault_runner.street_cam_pass | default('') }}"
291 path: "/stream1"
292 port: 554
293 onvif_port: 2020
294 enabled: true
295
296 - name: "pi-cam"
297 host: "{{ vault_runner.pi_cam_host | default('') }}"
298 username: "{{ vault_runner.pi_cam_user | default('') }}"
299 password: "{{ vault_runner.pi_cam_pass | default('') }}"
300 path: "{{ vault_runner.pi_cam_path | default('/doorbell') }}"
301 port: 8554
302 enabled: true
303
304# Cameras with ONVIF support (for PTZ control)
305frigate_onvif_cameras:
306 - name: "dining-room"
307 host: "{{ vault_runner.dining_room_camera_host | default('') }}"
308 port: 2020
309 username: "{{ vault_runner.dining_room_camera_user | default('') }}"
310 password: "{{ vault_runner.dining_room_camera_pass | default('') }}"
311 - name: "living-room"
312 host: "{{ vault_runner.living_room_camera_host | default('') }}"
313 port: 2020
314 username: "{{ vault_runner.living_room_camera_user | default('') }}"
315 password: "{{ vault_runner.living_room_camera_pass | default('') }}"
316 - name: "bed-room"
317 host: "{{ vault_runner.bed_room_camera_host | default('') }}"
318 port: 2020
319 username: "{{ vault_runner.bed_room_camera_user | default('') }}"
320 password: "{{ vault_runner.bed_room_camera_pass | default('') }}"
321 - name: "alina-office"
322 host: "{{ vault_runner.alina_office_camera_host | default('') }}"
323 port: 2020
324 username: "{{ vault_runner.alina_office_camera_user | default('') }}"
325 password: "{{ vault_runner.alina_office_camera_pass | default('') }}"
326 - name: "street-cam"
327 host: "{{ vault_runner.street_cam_host | default('') }}"
328 port: 2020
329 username: "{{ vault_runner.street_cam_user | default('') }}"
330 password: "{{ vault_runner.street_cam_pass | default('') }}"
331
332# Cameras for snapshot capture
333frigate_snapshot_cameras:
334 - "living-room"
335 - "dining-room"
336 - "bed-room"
337 - "alina-office"
338 - "street-cam"
339 - "pi-cam"
340
341# Per-camera record override (cameras listed here have recording disabled)
342frigate_cameras_record_disabled:
343 - "bed-room"
344
345# Frigate profile system (home=record-only, away=full detection)
346frigate_profiles:
347 home:
348 detect_enabled: false
349 detectors_enabled: false
350 cameras:
351 - street-cam
352 - pi-cam
353 hwaccel_override_cameras:
354 - street-cam
355 - pi-cam
356 default_profile: false
357 away:
358 detect_enabled: true
359 detectors_enabled: true
360 cameras:
361 - living-room
362 - dining-room
363 - bed-room
364 - alina-office
365 - street-cam
366 - pi-cam
367 default_profile: true
368
369# Frigate manager service
370frigate_manager_enabled: true
371frigate_snapshot_interval_minutes: 30
372frigate_profile_switch_timeout: 120
373
374# Immich - Photo Management
375immich_enabled: true
376immich_server_port: 2283
377immich_ml_port: 3003
378immich_config_dir: "/docker/immich"
379immich_data_dir: "/docker/immich"
380immich_upload_dir: "{{ runner_nfs_mount_dir }}/immich/library"
381immich_db_name: "{{ vault_runner.postgres_db | default('') }}"
382immich_db_user: "{{ vault_runner.postgres_user | default('') }}"
383immich_redis_enabled: true
384immich_ml_enabled: true
385immich_facial_recognition: true
386immich_hardware_acceleration: "none" # none, vaapi, nvdec, nvenc, or qsv
387
388# External library mounts (read-only photo libraries from NFS)
389immich_external_libraries:
390 - name: "yannick"
391 host_path: "{{ runner_nfs_mount_dir }}/immich/yannick"
392 container_path: "/yannick"
393 - name: "alina"
394 host_path: "{{ runner_nfs_mount_dir }}/immich/alina"
395 container_path: "/alina"
396
397# Forgejo - Git Server
398forgejo_enabled: true
399forgejo_http_port: 3010
400forgejo_ssh_port: 2222
401forgejo_config_dir: "/docker/forgejo"
402forgejo_data_dir: "{{ runner_nfs_mount_dir }}/forgejo"
403forgejo_db_type: "sqlite3"
404forgejo_app_name: "Forgejo Git Service"
405forgejo_domain: "forgejo.home"
406forgejo_ssh_domain: "{{ ansible_default_ipv4.address }}"
407
408# Forgejo Runner Configuration
409forgejo_runner_enabled: true
410
411# Cross-architecture builds. The Pi images (display, ceiling wash) are arm64
412# while the runner is x86_64; QEMU binfmt handlers let them build here. The
413# runners mount the host's docker socket, so this is host state rather than
414# anything in the runner image.
415forgejo_runner_cross_build_enabled: true
416forgejo_runner_cross_platforms: "arm64"
417forgejo_runner_binfmt_image: "tonistiigi/binfmt:latest"
418forgejo_runner_token: "{{ vault_runner.forgejo_runner_token | default('changeme') }}"
419forgejo_runner_name: "default-runner"
420forgejo_runner_capacity: 2
421forgejo_runner_loglevel: info
422forgejo_runner_base_image: ubuntu:22.04
423forgejo_runner_user: runner
424forgejo_runner_uid: 1000
425forgejo_runner_version: 9.1.1
426
427# Forgejo Ansible Runner (for running Ansible playbooks in CI)
428forgejo_ansible_runner_enabled: true
429forgejo_ansible_runner_token: "{{ vault_runner.forgejo_ansible_runner_token | default('changeme') }}"
430forgejo_ansible_runner_name: "ansible-runner"
431forgejo_ansible_runner_capacity: 1
432forgejo_ansible_runner_version: 9.1.1
433
434# Web Tools - PDF Processing, File Conversion, Data Analysis, Developer Utilities
435web_tools_enabled: true
436web_tools_config_dir: "/docker/web-tools"
437web_tools_data_dir: "/docker/web-tools"
438
439# Stirling-PDF
440web_tools_stirling_port: 8090
441web_tools_stirling_max_file_size: 100
442
443# ConvertX
444web_tools_convertx_port: 8091
445web_tools_convertx_jwt_secret: "{{ vault_runner.web_tools_convertx_jwt_secret | default('changeme-generate-a-real-secret') }}"
446web_tools_convertx_auto_delete_hours: 24
447
448# CyberChef
449web_tools_cyberchef_port: 8092
450
451# IT-Tools
452web_tools_it_tools_port: 8093
453
454# Tandoor - Recipe Manager
455tandoor_enabled: true
456tandoor_port: 8010
457tandoor_config_dir: "/docker/tandoor"
458tandoor_data_dir: "/docker/tandoor"
459tandoor_media_dir: "{{ tandoor_data_dir }}/media"
460tandoor_static_dir: "{{ tandoor_data_dir }}/static"
461tandoor_db_engine: "django.db.backends.postgresql"
462
463# Ghost CMS - Headless CMS
464ghost_enabled: true
465ghost_port: 2368
466ghost_config_dir: "/docker/ghost"
467ghost_data_dir: "/docker/ghost"
468ghost_content_dir: "{{ ghost_data_dir }}/content"
469ghost_db_client: "mysql"
470ghost_db_host: "ghost-mysql"
471ghost_db_name: "ghost"
472ghost_db_user: "ghost"
473ghost_url: "http://ghost.home"
474
475# ==============================================================================
476# LIVEKIT - WebRTC Conferencing (SFU) + Meet frontend
477# ==============================================================================
478# LiveKit is a WebRTC SFU. Unlike the other runner services it runs on
479# network_mode: host because Docker's userland proxy rewrites UDP source
480# addresses, which breaks ICE candidate resolution and silently kills media.
481livekit_enabled: true
482livekit_config_dir: "/docker/livekit"
483livekit_data_dir: "{{ livekit_config_dir }}/data"
484
485# Signaling / API (HTTP + WebSocket)
486livekit_port: 7880
487# WebRTC over TCP (ICE/TCP fallback for restrictive networks)
488livekit_tcp_port: 7881
489# WebRTC over UDP - single-port mux, avoids the 50000-60000 range entirely
490livekit_udp_port: 7882
491# Meet frontend (bridge network, normal published port)
492livekit_meet_port: 7883
493
494# Public hostnames handed to browser clients. When fronted by a reverse proxy
495# these are the proxy's names; otherwise they resolve straight to this host.
496livekit_domain: "livekit.home"
497livekit_sfu_domain: "{{ livekit_domain }}"
498
499# Set true when a reverse proxy terminates TLS in front of both hostnames.
500# Browsers require a secure context for getUserMedia, and a page served over
501# HTTPS may not open a ws:// socket, so this must be on for any non-localhost
502# browser access.
503livekit_tls_enabled: false
504
505# Behind a proxy the public URLs carry no port (443 is implied). Direct access
506# needs the explicit container ports.
507livekit_public_url: >-
508 {{ 'https://' ~ livekit_domain
509 if livekit_tls_enabled
510 else 'http://' ~ livekit_domain ~ ':' ~ livekit_meet_port }}
511livekit_public_ws_url: >-
512 {{ 'wss://' ~ livekit_sfu_domain
513 if livekit_tls_enabled
514 else 'ws://' ~ livekit_sfu_domain ~ ':' ~ livekit_port }}
515
516# API credentials (from vault). Generate with:
517# docker run --rm livekit/livekit-server generate-keys
518livekit_api_key: "{{ vault_runner.livekit_api_key | default('') }}"
519livekit_api_secret: "{{ vault_runner.livekit_api_secret | default('') }}"
520
521livekit_log_level: "info"
522
523# Avatar for the session agent, served by Meet at the site root so it can be
524# shown in place of a video tile (the agent publishes no camera).
525livekit_agent_avatar_enabled: true
526livekit_agent_avatar_filename: "thaumaturgeon.png"
527livekit_agent_avatar_src: "files/thaumaturgeon.png"
528
529# Room behaviour
530livekit_room_auto_create: true
531livekit_room_empty_timeout: 300 # seconds before an empty room closes
532livekit_room_departure_timeout: 20
533
534# Meet frontend build. Upstream publishes no release tags, so this is pinned to
535# a specific commit SHA rather than tracking the main branch. Bump deliberately.
536livekit_meet_repo_url: "https://github.com/livekit-examples/meet.git"
537livekit_meet_repo_version: "665e1cb7841ab872de0d8e5c310744009a763b08"
538# Matches the packageManager field in Meet's package.json
539livekit_meet_pnpm_version: "10.18.2"
540livekit_meet_node_version: "20-alpine"
541
542# Resource limits
543livekit_memory_limit: "2g"
544livekit_cpu_limit: "2"
545livekit_meet_memory_limit: "1g"
546livekit_meet_cpu_limit: "1"
547
548cvat_config_dir: "/docker/cvat"
549# CVAT - Data labeling
550cvat_enabled: true
551
552cvat_repo_url: "https://github.com/cvat-ai/cvat.git"
553cvat_repo_version: "{{ cvat_image_tag }}"
554
555cvat_admin_username: admin
556cvat_admin_password: "{{ vault_runner.cvat_admin_password | default('change-me') }}"
557cvat_admin_email: "{{ vault_runner.cvat_admin_email | default('change-me') }}"
558
559# Networking / access
560cvat_domain: "cvat.home" # used by Traefik routing in CVAT compose
561cvat_http_port: 8990 # Traefik "web" entrypoint in the default compose
562cvat_https_enabled: false # add CVAT's https overlay when true
563
564cvat_share_dir: >-
565 {{ (runner_nfs_mounts
566 | selectattr('name','equalto','cvat')
567 | map(attribute='local_path')
568 | first)
569 | default(runner_nfs_mount_dir ~ '/cvat', true) }}
570
571# Versioning / images
572cvat_image_tag: "v2.44.3" # pulled via CVAT_VERSION; align with the git tag you run
573
574# Optional: serverless auto-annotation overlay (Nuclio/SAM/YOLO assist in CVAT)
575cvat_serverless_enabled: false
576
577# Optional: use an external Postgres instead of the bundled one
578cvat_external_db_enabled: false
579cvat_db_host: "postgres.internal"
580cvat_db_port: 5432
581cvat_db_name: "cvat"
582cvat_db_user: "cvat_user"
583cvat_db_password: "{{ vault_runner.cvat_db_password | default('change-me') }}"
584
585# Optional: expose Traefik dashboard (binds host port below)
586cvat_traefik_dashboard_enabled: false
587cvat_dashboard_port: 8899
588
589# Optional: GPU reservation for CVAT server container (you must have host GPU runtime ready)
590cvat_gpu_enabled: false
591cvat_gpu_driver: "nvidia"
592cvat_gpu_count: "all" # or a number like "1"
593
594# ==============================================================================
595# DATABASE CONFIGURATIONS
596# ==============================================================================
597
598# PostgreSQL (Immich)
599postgres_enabled: "{{ immich_enabled }}"
600postgres_config_dir: "/docker/immich/postgres"
601postgres_db: "{{ vault_runner.postgres_db | default('') }}"
602postgres_user: "{{ vault_runner.postgres_user | default('') }}"
603postgres_version: "14"
604
605# Redis (Immich)
606redis_enabled: "{{ immich_redis_enabled }}"
607redis_config_dir: "/docker/immich/redis"
608redis_port: 6379
609
610# MySQL (Ghost CMS)
611mysql_enabled: "{{ ghost_enabled }}"
612mysql_config_dir: "/docker/ghost/mysql"
613mysql_db: "{{ ghost_db_name }}"
614mysql_user: "{{ ghost_db_user }}"
615mysql_version: "8.0"
616
617# ==============================================================================
618# SECURITY SETTINGS (FROM VAULT)
619# ==============================================================================
620
621# Database passwords
622postgres_password: "{{ vault_runner.postgres_password | default('') }}"
623mysql_password: "{{ vault_runner.mysql_password | default('') }}"
624mysql_root_password: "{{ vault_runner.mysql_root_password | default('') }}"
625
626# Service secrets
627immich_jwt_secret: "{{ vault_runner.immich_jwt_secret | default('') }}"
628ghost_database_password: "{{ vault_runner.ghost_database_password | default('') }}"
629tandoor_secret_key: "{{ vault_runner.tandoor_secret_key | default('') }}"
630
631# MQTT credentials (from vault - top-level, shared across all roles)
632mqtt_username: ""
633mqtt_password: ""
634
635# ==============================================================================
636# DIRECTORY STRUCTURE
637# ==============================================================================
638
639# Local configuration directories
640runner_config_directories:
641 - "{{ llm_stack_config_dir }}"
642 - "{{ llm_stack_ollama_data_dir }}"
643 - "{{ llm_stack_openwebui_data_dir }}"
644 - "{{ llm_stack_litellm_data_dir }}"
645 - "{{ frigate_config_dir }}"
646 - "{{ immich_config_dir }}"
647 - "{{ immich_config_dir }}/postgres"
648 - "{{ immich_config_dir }}/redis"
649 - "{{ immich_config_dir }}/library"
650 - "{{ immich_config_dir }}/cache"
651 - "{{ immich_config_dir }}/model-cache"
652 - "{{ immich_config_dir }}/postgres-init"
653 - "{{ forgejo_config_dir }}"
654 - "{{ forgejo_config_dir }}/forgejo-runner-data"
655 - "{{ forgejo_config_dir }}/forgejo-ansible-runner-data"
656 - "{{ web_tools_config_dir }}"
657 - "{{ tandoor_config_dir }}"
658 - "{{ tandoor_data_dir }}/db"
659 - "{{ tandoor_data_dir }}/media"
660 - "{{ tandoor_data_dir }}/static"
661 - "{{ ghost_config_dir }}/config"
662 - "{{ ghost_config_dir }}/content"
663 - "{{ ghost_config_dir }}/mysql"
664
665# NFS mount directories
666runner_nfs_directories:
667 - "{{ runner_nfs_mount_dir }}"
668 - "{{ runner_nfs_mount_dir }}/frigate"
669 - "{{ runner_nfs_mount_dir }}/immich"
670 - "{{ runner_nfs_mount_dir }}/forgejo"
671 - "{{ runner_nfs_mount_dir }}/harbor"
672 - "{{ runner_nfs_mount_dir }}/cvat"
673
674# ==============================================================================
675# PERFORMANCE SETTINGS
676# ==============================================================================
677
678# Network performance tuning for NFS
679runner_performance_tuning_enabled: true
680runner_sysctl_settings:
681 # Extreme performance network buffers for Ryzen 7 + 32GB RAM
682 net.core.rmem_max: 268435456 # 256MB socket receive buffer
683 net.core.wmem_max: 268435456 # 256MB socket send buffer
684 net.core.rmem_default: 33554432 # 32MB default receive buffer
685 net.core.wmem_default: 33554432 # 32MB default send buffer
686 net.ipv4.tcp_rmem: "4096 131072 268435456" # TCP receive: 4KB min, 128KB default, 256MB max
687 net.ipv4.tcp_wmem: "4096 131072 268435456" # TCP send: 4KB min, 128KB default, 256MB max
688 net.core.netdev_max_backlog: 30000 # Handle high connection burst (32 connections)
689 net.ipv4.tcp_congestion_control: "bbr" # BBR congestion control
690 net.ipv4.tcp_window_scaling: 1 # Enable TCP window scaling
691 net.ipv4.tcp_timestamps: 1 # Enable TCP timestamps for RTT calculation
692 net.ipv4.tcp_sack: 1 # Enable selective acknowledgments
693 # NFS client cache tuning for 32GB RAM
694 vm.dirty_background_ratio: 3 # Start writeback at 3% (more aggressive)
695 vm.dirty_ratio: 8 # Force writeback at 8% (more aggressive)
696 vm.vfs_cache_pressure: 25 # Keep even more file cache (25% vs 50%)
697 vm.min_free_kbytes: 131072 # Keep 128MB free for network buffers
698
699# Harbor Configuration (external deployment)
700harbor_enabled: true
701harbor_config_dir: "/docker/harbor"
702harbor_version: "2.13.2"
703harbor_hostname: "{{ vault_runner.harbor_hostname | default('registry.local') }}"
704harbor_http_port: 8080
705harbor_registry_port: 5000
706harbor_admin_password: "{{ vault_runner.harbor_admin_password | default('changeme') }}"
707harbor_db_password: "{{ vault_runner.harbor_db_password | default('changeme') }}"
708harbor_data_volume: "{{ runner_nfs_mount_dir }}/harbor"
709
710# Docker resource limits
711default_memory_limit: "1g"
712default_cpu_limit: "1"
713
714# Health check configuration
715health_check_interval: "30s"
716health_check_timeout: "30s"
717health_check_retries: 5
718health_check_start_period: "60s"
719
720# ==============================================================================
721# SERVICE HEALTH ENDPOINTS
722# ==============================================================================
723service_endpoints:
724 ollama: "http://localhost:{{ llm_stack_ollama_port }}/api/tags"
725 openwebui: "http://localhost:{{ llm_stack_openwebui_port }}/api/health"
726 litellm: "http://localhost:{{ llm_stack_litellm_port }}/"
727 frigate: "http://localhost:{{ frigate_port }}/api/config"
728 immich: "http://localhost:{{ immich_server_port }}/api/server-info/ping"
729 forgejo: "http://localhost:{{ forgejo_http_port }}/api/v1/version"
730 stirling_pdf: "http://localhost:{{ web_tools_stirling_port }}/api/v1/info/status"
731 convertx: "http://localhost:{{ web_tools_convertx_port }}/"
732 cyberchef: "http://localhost:{{ web_tools_cyberchef_port }}/"
733 it_tools: "http://localhost:{{ web_tools_it_tools_port }}/"
734 tandoor: "http://localhost:{{ tandoor_port }}/accounts/login/"
735 ghost: "http://localhost:{{ ghost_port }}/ghost/api/admin/site/"
736 livekit: "http://localhost:{{ livekit_port }}/"
737 livekit_meet: "http://localhost:{{ livekit_meet_port }}/"
738
739# ==============================================================================
740# LOGGING CONFIGURATION
741# ==============================================================================
742logging_driver: "json-file"
743logging_max_size: "10m"
744logging_max_file: "3"
745
746# Service-specific logging levels (unified LLM stack uses llm_stack_* variables)
747# ollama_log_level: "info" # Now uses llm_stack_ollama_log_level
748# openwebui_log_level: "info" # Now uses llm_stack_openwebui_log_level
749# litellm_log_level: "info" # Now uses llm_stack_litellm_log_level
750frigate_log_level: "info"
751immich_log_level: "log"
752forgejo_log_level: "Info"
753ghost_logging: "info"
754
755