-
-
Notifications
You must be signed in to change notification settings - Fork 110
Expand file tree
/
Copy pathdocker-compose.dev.yml
More file actions
226 lines (218 loc) · 9.84 KB
/
Copy pathdocker-compose.dev.yml
File metadata and controls
226 lines (218 loc) · 9.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# SUB/WAVE — dev compose (local smoke test): Broadcast + Controller only.
# The Next.js UI runs separately via `cd web && npm run dev` for hot reload.
# State + sounds + radio.liq are bind-mounted so dev cycles need no rebuilds.
x-state: &state-mount ${STATE_DIR:-./state}:/var/sub-wave
# Cap container log growth (10m × 3 ≈ 30MB/service).
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
# -------------------------------------------------------------------------
# BROADCAST — icecast2 + liquidsoap in one container
# -------------------------------------------------------------------------
# Icecast exposed on :7702 for direct dev access; liquidsoap stays internal.
broadcast:
image: ghcr.io/perminder-klair/subwave-broadcast:${SUBWAVE_VERSION:-latest}
build:
context: .
dockerfile: docker/Dockerfile.broadcast
# No `platform:` pin. On Apple Silicon, forcing linux/amd64 routes
# liquidsoap through emulation where clock_nanosleep fails
# (`Unix.Unix_error(EUNKNOWNERR 0)`) — the clock loop dies on every boot
# and Icecast gets no source. The base is multi-arch; build natively.
container_name: sub-wave-broadcast
restart: unless-stopped
logging: *default-logging
ports:
- "7702:7702"
environment:
# Optional — blank means random values generated on first boot and
# persisted to state/icecast-secrets.env.
- ICECAST_SOURCE_PASSWORD=${ICECAST_SOURCE_PASSWORD:-}
- ICECAST_ADMIN_PASSWORD=${ICECAST_ADMIN_PASSWORD:-}
- ICECAST_RELAY_PASSWORD=${ICECAST_RELAY_PASSWORD:-}
# Concurrent-listener ceiling (<limits><clients>). Empty → 100.
- ICECAST_MAX_CLIENTS=${ICECAST_MAX_CLIENTS:-}
# Dev has no edge in front of icecast, so these stay empty and listener
# rows already show the real peer.
- ICECAST_TRUSTED_PROXY_IPS=${ICECAST_TRUSTED_PROXY_IPS:-}
- ICECAST_TRUSTED_PROXY_HOSTS=${ICECAST_TRUSTED_PROXY_HOSTS:-}
- TZ=${TZ:-Europe/London}
extra_hosts:
# Liquidsoap fetching Subsonic URLs that point at host services.
- "host.docker.internal:host-gateway"
volumes:
# Bind-mounts override the baked-in copies — radio.liq edits and new
# sounds land on the next restart, no rebuild.
- ./liquidsoap/radio.liq:/etc/liquidsoap/radio.liq:ro
- ./sounds:/sounds:ro
- *state-mount
- ${STATE_DIR:-./state}/logs:/var/log/liquidsoap
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:7702/status-json.xsl > /dev/null"]
interval: 5s
timeout: 3s
retries: 12
start_period: 15s
# -------------------------------------------------------------------------
# CONTROLLER — the AI DJ brain
# -------------------------------------------------------------------------
controller:
image: ghcr.io/perminder-klair/subwave-controller:${SUBWAVE_VERSION:-latest}
build:
context: .
dockerfile: docker/Dockerfile.controller
args:
# Version reported by the controller; unset → controller/package.json.
# The dev web UI resolves its own via `git describe` (next.config.js).
- SUBWAVE_BUILD_VERSION=${SUBWAVE_BUILD_VERSION:-}
platform: linux/amd64
container_name: sub-wave-controller
restart: unless-stopped
logging: *default-logging
depends_on:
broadcast:
condition: service_healthy
# So a selective `up -d controller` also brings the socket-proxy up.
# Remove this entry too if you drop the proxy below.
docker-socket-proxy:
condition: service_started
environment:
- TZ=${TZ:-Europe/London}
- STATE_DIR=/var/sub-wave
- SOUNDS_DIR=/sounds
# Optional Chatterbox/PocketTTS sidecar (--profile tts-heavy);
# unreachable URL → fall back to Piper.
- TTS_HEAVY_URL=${TTS_HEAVY_URL:-http://tts-heavy:8080}
# Acoustic-analysis sidecar (default-on below). Probed, then local venv.
- ANALYZE_URL=${ANALYZE_URL:-http://analyzer:8080}
# Admin Stats panel via the socket-proxy — the controller never touches
# the raw Docker socket. Unset to disable.
- DOCKER_HOST=tcp://docker-socket-proxy:2375
ports:
- "7701:7701"
env_file:
# All controller config (Navidrome creds, LLM keys, ADMIN_*, …). Unset
# vars are simply absent — settings.json covers what the wizard manages.
- ./.env
extra_hosts:
- "host.docker.internal:host-gateway"
# Dev runs `tsx watch` against bind-mounted source so controller/src/**
# edits restart in-place. Prod uses the image's default CMD (no watch).
command: ["node_modules/.bin/tsx", "watch", "src/server.ts"]
volumes:
- *state-mount
- ./sounds:/sounds:ro
# Mount src/ and scripts/ only — mounting the whole controller/ dir
# would shadow /app/node_modules with the (possibly empty) host one.
- ./controller/src:/app/src
- ./controller/scripts:/app/scripts
# start_period is generous: `tsx watch` is slower to first boot.
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:7701/health > /dev/null"]
interval: 10s
timeout: 5s
retries: 6
start_period: 20s
# -------------------------------------------------------------------------
# DOCKER-SOCKET-PROXY — locked-down Docker API for the Stats system panel
# -------------------------------------------------------------------------
# Read-only, GET-only, CONTAINERS-section-only slice of the Docker API over
# internal TCP. Optional: remove it (plus the controller's DOCKER_HOST +
# depends_on entry) to drop the Stats panel.
docker-socket-proxy:
image: ghcr.io/tecnativa/docker-socket-proxy:0.3.0
container_name: sub-wave-docker-proxy
restart: unless-stopped
logging: *default-logging
environment:
- CONTAINERS=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
# -------------------------------------------------------------------------
# TTS-HEAVY (optional) — sidecar for Chatterbox + PocketTTS
# -------------------------------------------------------------------------
# NOT started by default:
# docker compose -f docker-compose.dev.yml --profile tts-heavy up -d
# First boot downloads ~3-4GB of model weights.
tts-heavy:
image: ghcr.io/perminder-klair/subwave-tts-heavy:${SUBWAVE_VERSION:-latest}
build:
context: .
dockerfile: docker/Dockerfile.tts-heavy
args:
# GPU opt-in (source build only) — see docs/gpu-tts.md.
CHATTERBOX_TORCH_INDEX_URL: ${CHATTERBOX_TORCH_INDEX_URL:-https://download.pytorch.org/whl/cpu}
# RTX 50-series only: override chatterbox's torch==2.6.0 pin.
CHATTERBOX_TORCH_SPEC: ${CHATTERBOX_TORCH_SPEC:-}
platform: linux/amd64
container_name: sub-wave-tts-heavy
restart: unless-stopped
logging: *default-logging
# OOM containment: a runaway model load dies here, not via the host
# OOM-killer taking broadcast/controller with it.
mem_limit: ${TTS_HEAVY_MEM_LIMIT:-10g}
profiles: ["tts-heavy"]
environment:
- TTS_HEAVY_DEVICE=${TTS_HEAVY_DEVICE:-cpu}
- POCKET_TTS_VOICE=${POCKET_TTS_VOICE:-alba}
# Which engines to load (comma-separated); each costs RAM + weights.
- TTS_HEAVY_ENGINES=${TTS_HEAVY_ENGINES:-chatterbox,pocket-tts}
# Optional — PocketTTS voice cloning (#238); weights gated on HF.
- HF_TOKEN=${HF_TOKEN:-}
volumes:
- *state-mount
# Persist HF caches across recreates, or the multi-GB weight fetch
# repeats every time.
- tts-heavy-chatterbox-cache:/opt/chatterbox/hf-cache
- tts-heavy-pocket-cache:/opt/pocket-tts/hf-cache
# -------------------------------------------------------------------------
# ANALYZER — acoustic-analysis sidecar (bpm/key/intro/loudness; optional
# CLAP "sounds-like" embeddings + Demucs vocal ranges)
# -------------------------------------------------------------------------
# Starts by default; only the tts-heavy voices stay opt-in.
analyzer:
# Default: LEAN multi-arch. ANALYZER_HEAVY=1 → CLAP + Demucs heavy image.
image: ghcr.io/perminder-klair/subwave-analyzer${ANALYZER_HEAVY:+-heavy}:${SUBWAVE_VERSION:-latest}
build:
context: .
dockerfile: docker/Dockerfile.analyzer
args:
# Local build mirrors the pulled image: lean unless ANALYZER_HEAVY set.
WITH_CLAP: ${ANALYZER_HEAVY:+1}
WITH_DEMUCS: ${ANALYZER_HEAVY:+1}
container_name: sub-wave-analyzer
restart: unless-stopped
logging: *default-logging
# OOM containment: a runaway analysis dies here, not via the host
# OOM-killer taking broadcast/controller with it.
mem_limit: ${ANALYZER_MEM_LIMIT:-6g}
environment:
- ANALYZE_AUDIO_EMBEDDING=${ANALYZE_AUDIO_EMBEDDING:-}
- ANALYZE_VOCAL_ACTIVITY=${ANALYZE_VOCAL_ACTIVITY:-}
# Torch device for CLAP/Demucs: auto (default) / cpu / cuda.
- ANALYZE_DEVICE=${ANALYZE_DEVICE:-}
# Idle seconds before the worker drops CLAP/Demucs models (0 = never;
# default 300 cuda / 1800 cpu — see #1204).
- ANALYZE_IDLE_UNLOAD_S=${ANALYZE_IDLE_UNLOAD_S:-}
# Idle seconds before the sidecar recycles the whole worker process
# (default 3600; 0 = never).
- ANALYZE_RECYCLE_IDLE_S=${ANALYZE_RECYCLE_IDLE_S:-}
- CLAP_MODEL=${CLAP_MODEL:-}
- CLAP_MODEL_PATH=${CLAP_MODEL_PATH:-}
# Demucs model + analysis-window overrides (see .env.example).
- DEMUCS_MODEL=${DEMUCS_MODEL:-}
- ANALYZE_SECONDS=${ANALYZE_SECONDS:-}
- ANALYZE_CLAP_WINDOWS=${ANALYZE_CLAP_WINDOWS:-}
- ANALYZE_OUTRO_SECONDS=${ANALYZE_OUTRO_SECONDS:-}
# Anonymous HF downloads are rate-limited; same var as tts-heavy.
- HF_TOKEN=${HF_TOKEN:-}
volumes:
- *state-mount
- analyzer-cache:/opt/analyzer/hf-cache
volumes:
tts-heavy-chatterbox-cache:
tts-heavy-pocket-cache:
analyzer-cache: