yt-dlp calls save_cookies() on exit and rewrites the --cookies file. A read-only
mount makes it crash with "OSError: [Errno 30] Read-only file system", which surfaced
as 500s on the app's YouTube search/download endpoints when YT_COOKIE_FILE was set.
YouTube playback relied on manual changes inside the running container
(deno install + /etc/yt-dlp.conf) that would be lost on any recreate.
Make it permanent and reproducible:
- Dockerfile.backend: install pinned deno 2.8.3 as yt-dlp's JS runtime and
write a system-wide /etc/yt-dlp.conf enabling the EJS challenge solver
(--js-runtimes deno, --remote-components ejs:github, --force-ipv4).
Replaces the unreliable node-based config.
- .gitea/workflows/build-backend.yml: build Dockerfile.backend and push
git.qomar.pw/omar/ts6-manager/backend:latest (amd64). Needs REGISTRY_TOKEN.
- docker-compose.yml: pull backend from the Gitea registry; frontend/sidecar
stay on upstream clusterzx images. Mount ./secrets read-only for optional
YouTube cookies via YT_COOKIE_FILE.
- gitignore/dockerignore: never commit cookies (live Google session).
- DEPLOY.md: deployment + CI notes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clear pending RTP packets and reset per-peer stream start state whenever
the active source is stopped or replaced.
This prevents stale queued packets and old stream gate state from leaking
into the next source, which could previously leave the sidecar stuck in a
degraded state until the container was restarted.
The change applies the same cleanup during both source stop and source
restart/switch, making repeated source changes much more robust.
Restore working output resolution presets for the sidecar service path and
extend the video streaming UI/backend with separate user-controlled FPS
and bitrate settings.
This change:
- wires resolution presets back through the sidecar service mode
- adds a dedicated framerate selection independent from resolution
- adds a dedicated video bitrate input independent from resolution
- passes resolution, FPS, and bitrate through frontend, API, backend,
and sidecar paths
- makes local mode and service mode use the same explicit stream settings
- exposes active framerate and bitrate in stream status
- keeps preset values only as fallback defaults where appropriate
Tested successfully across multiple combinations, including:
- 480p / 720p / 1080p
- 24 FPS / 30 FPS / 60 FPS
- local files and YouTube sources
- high bitrate cases up to 1080p@60fps with 6000k video bitrate
fix(sidecar): stabilize stream sync and peer join handling
Rework sidecar RTP handling to improve A/V sync and make stream joins
robust under heavy repeated client requests.
This change:
- decouples UDP RTP ingestion from paced WebRTC output via queued
audio/video processors
- adds adaptive stream pacing with configurable playout buffer and
optional video bias for fine sync adjustment
- keeps peer output gated until a valid VP8 keyframe is available
- adds configurable queue sizes for better burst tolerance
- hardens peer creation with in-flight deduplication and safe offer reuse
- serializes answer handling per peer and ignores invalid duplicate
answers during noisy join retries
- reduces noisy runtime logs behind a debug flag
Tested successfully with:
- YouTube sources at 30 fps and 60 fps
- local MP4 playback
- 720p and 1080p sources
- browser preview and TeamSpeak client joining in parallel
Prevents axios from auto-parsing JSON responses, so plain text
responses (like BBCode widget output) are stored correctly in
temp variables via storeAs.
Backend:
- Expose PlayQueue.index getter for current track position
- Add PlayQueue.move(from, to) for reordering
- Add POST /:id/queue/:index/play endpoint (play from queue position)
- Add PUT /:id/queue/move endpoint (reorder queue items)
- Fix currentIndex in state endpoint (was hardcoded -1)
Frontend:
- Add dedicated Queue tab with full track list
- Highlight currently playing track
- Click-to-play any track in queue
- Remove individual tracks from queue
- Move tracks up/down with buttons
- Clear queue button
- Bot selector for multi-bot setups
- Add playFromQueue and moveQueueItem API + hooks
Chat commands:
- !queue show — display queue with track numbers and durations
- !queue play <n> — jump to track at position N
- !queue remove <n> — remove track at position N
- !queue clear — clear entire queue
#26 - Permissions → Client: multiple bug fixes
- Use cldbid instead of clid for client permission operations
- Add PUT/DELETE routes for clientaddperm/clientdelperm with permid resolution
- Handle error 1281 (empty result) gracefully for clients with no permissions
- Add missing case 'client' branches in frontend save mutation
- Add addClientPerm/delClientPerm to frontend API client
#27 - Music bot duplicate client on reconnect
- Add forceClose() to TS3 client for immediate socket teardown
- Add ensureDisconnected() to VoiceBot
- Add 5s grace period before reconnect attempt to let old session expire
#12 - HTTP Request node: add headers input
- Add JSON headers input field to BotEditor HTTP Request node config
- Backend already supported headers, only the UI was missing
#29 - MusicBot PlayerWidget
- Add public /api/widget/player/:botId/data endpoint (JSON)
- Add public /api/widget/player/:botId/bbcode endpoint (BBCode for channel descriptions)
- HMAC token-based auth (no DB migration needed)
- Admin endpoint to retrieve widget token and URLs
- Shows now playing, progress, and next 5 queue items
- Add GenerateCodeActionData to ActionNodeData union in bot.ts
- Fix channel flag logic: only set semi_permanent when explicitly configured,
preserving the ability to create permanent channels (TS3 default)
- Add settings routes for uploading/deleting yt-dlp cookie files (admin only)
- Add YouTube tab in Settings page with file upload, paste, and delete
- Enable --remote-components ejs:github for YouTube bot-detection bypass
- Pass cookies to resolveVideoUrl for video streaming yt-dlp calls
- Fix SidecarClient to accept full URL string (Docker networking)
- Load saved cookie file from data dir on startup
- Switch docker-compose sidecar from build to prebuilt image
- Add docker-compose.dev.yml for dev deployment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Limit the WebRTC video preview player to max-w-xl (576px) to prevent
oversized preview in the WebUI. Sidecar binary rebuilt with the SSRC
resolution fix (moved to OnICEConnectionStateConnected callback).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous timestamp normalization used wallclock time (time.Now())
to calculate a cross-stream offset between video (90kHz) and audio
(48kHz) RTP timestamps. This caused progressive audio/video desync
because the offset was computed once at stream start from the arrival
time of each stream's first packet — any jitter in that measurement
became a permanent drift.
FFmpeg already guarantees A/V sync within a single invocation, so no
cross-stream correction is needed. Now each stream simply subtracts
its own first timestamp to normalize to zero-based, preserving
FFmpeg's native synchronization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend MusicBot to stream video (YouTube, URLs, local files) to TS6
channels via WebRTC P2P. Uses a Go sidecar (Pion WebRTC + FFmpeg) for
media relay and a new stream signaling layer over the TS3 UDP protocol.
- Go sidecar: WebRTC peer management, RTP forwarding, FFmpeg control
- Stream signaling: setupstream, respondjoinstreamrequest, streamsignaling
- YouTube URL resolution via yt-dlp before passing to FFmpeg
- Quality presets (480p/720p/1080p) with configurable bitrate/framerate
- WebUI: Video tab in MusicBots page with live WebRTC preview player
- Viewer list with kick capability, chat commands (!stream, !stopstream, !viewers)
- TS3 UDP command fragmentation for large SDP payloads
- Docker: multi-stage sidecar build, separate container with SIDECAR_URL
- Graceful sidecar error handling (no backend crash on missing binary)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
expanded nodes in BotEditor:
- added password field for create and edit channel node
- added Generate Code node for automatic pw creation for custom/free/temp channels + pw can be send then via message node with temp.<name>
- Voice audio improvements: stereo support + stable 20ms pacing (closes#25)
- Music request history with frontend page and API (closes#15)
- Channel-specific chat command triggers in flow automation (closes#23)
From PR #23 by @GingerFury6:
- Command triggers can target a specific channel via channelId
- Per-channel SSH query clients move into the channel to receive textchannel events
- Unique nicknames with random suffix to avoid conflicts
- Sync/cleanup of command listeners when flows change
- Frontend: Channel ID input field + Textarea for message actions
- Backward-compatible: triggers without channelId work as before
From PR #15 by @LemDog:
- New MusicRequest Prisma model with unique constraint per server+url
- Backend routes for listing/clearing music request history
- Frontend: History tab in Play Song dialog, dedicated MusicRequests page
- Sidebar link to Music Request History
- Server logs theme fix
- Adapted to work with existing !play/!queue changes
Deno was installed but never used in code — yt-dlp needs a JS runtime
for YouTube signature decryption and defaulted to Deno. Since Node.js 20
is already in the image, configure yt-dlp to use it via config file.
Also removes curl/unzip (only needed for Deno install), reducing image size.
Enables multi-arch (ARM64) builds by removing the arch-sensitive Deno binary.
- Fix#16: ScrollArea scroll bug — global CSS override for Radix display:table
- Fix#8: Music bot voice port no longer hardcoded to 9987, configurable per bot
- Fix#7: Long song titles no longer hide action buttons (CSS grid minmax fix)
- Fix#10: Self-signed certificate support via TS_ALLOW_SELF_SIGNED env var
- Fix#5: YouTube cookie file support via YT_COOKIE_FILE env var for yt-dlp
- Fix#18: Widget improvements — spacer channel rendering (SVG + HTML),
hide empty channels option, click-to-join ts3server:// links
- Fix#13: SSH reconnect hardened — TCP keepalive via ssh2, application-level
keepalive with failure detection, forceDisconnect, double-reconnect guard
- Fix#6: Add !queue/!add chat commands to enqueue songs without interrupting
current playback; !play now queues when already playing
- Replace hardcoded admin/admin with setup wizard (/setup) for initial account creation
- Add AES-256-GCM encryption for stored API keys, SSH passwords, and bot identity data
- Add SSRF protection (private IP blocking, DNS rebinding prevention) on all outbound URLs
- Add refresh token reuse detection with automatic family revocation
- Add rate limiting on auth endpoints (15 req/15min)
- Add per-server access control middleware (UserServerAccess enforcement)
- Add WebQuery command whitelist blocking destructive commands in bot flows
- Add JWT startup guard (refuse to start in production with default secret)
- Add WebSocket authentication via JWT query parameter
- Add mandatory webhook secrets with timing-safe comparison
- Add password complexity requirements (8+ chars, mixed case, digit)
- Add ENCRYPTION_KEY env var to docker-compose files
- Restrict JWT verification to HS256 algorithm
- Restrict client IP visibility to admin role
- Add parameter whitelists for instanceedit and serveredit
- Bound widget cache size with periodic cleanup
- Redact server version/platform in widget API
- Rewrite README with updated features, setup instructions, and env vars