Self-hosted Lavalink audio node with YouTube anti-detection and web-based admin panel.
| Service | Purpose | Port |
|---|---|---|
| Lavalink | Audio sending node (Discord) | 2333 |
| yt-cipher | YouTube signature deciphering | 8001 |
| bgutil-pot | PoToken generation (BotGuard) | 4416 |
| Admin Panel | Config management + monitoring | 8080 |
# One-click setup — generates all secrets, creates .env, prepares directories
./setup.sh
# Start the stack
docker compose up --build -dVisit http://localhost:8080 and log in with the admin password printed by the setup script.
If you prefer to configure manually:
cp .env.example .env
# Edit .env with your own passwords/tokens
# Then sync the lavalink password and cipher token into config/application.yml
docker compose up --build -dThree independent mechanisms work together:
- OAuth — Authenticates as a real Google user (use a burner account!)
- PoToken — Proof-of-origin token from BotGuard attestation
- Remote Cipher — Offloads signature deciphering to yt-cipher
The admin panel manages the lifecycle of all three.
See docs/DESIGN.md for the full architecture document.
terraform/ has ready-to-go modules for deploying the stack to a single
cloud VM. AWS, UpCloud, and Vultr are supported. Each module provisions a
hardened instance with IPv6 enabled (useful for Lavalink's IP rotation) and
prints an SSH-tunnel command for reaching the admin panel.
The admin panel exposes JSON probes for external monitors (CloudWatch, Uptime Kuma, Pingdom, etc). These bypass the login cookie so a monitor can scrape them, but they only return service status — no credentials or config.
| Endpoint | Returns |
|---|---|
GET /healthz |
Aggregate. 200 if Lavalink is up; 503 otherwise. Body lists per-service state. |
GET /healthz/lavalink |
200/503 for Lavalink only. |
GET /healthz/cipher |
200/503 for yt-cipher. |
GET /healthz/pot |
200/503 for bgutil-pot. |
Example body:
{"status":"ok","services":{"lavalink":"ok","cipher":"ok","pot":"down"},"checked_at":"2026-04-23T02:30:00+00:00"}cipher and pot are reported but advisory — they can flap without flipping
the aggregate to 503, since Lavalink can serve non-YouTube sources without
them. If you need either to gate alerts, scrape its dedicated endpoint.
The HTML dashboard at /health is unchanged and still requires login.
Public exposure: these endpoints leak that the stack exists and which services run. Fine for the SSH-tunnel default (loopback only). If you put admin behind a public reverse proxy, scope
/healthz*to your monitor's IP at the proxy, otherwise you're broadcasting your topology.
- Never expose the admin panel to the public internet without a reverse proxy + auth
- The admin panel reaches Docker through
tecnativa/docker-socket-proxy, configured with a customhaproxy.cfg(inconfig/dockerproxy/) that allowlists exactly five endpoint+method combinations:GET /_ping,GET /version,GET /containers/{name}/json,GET /containers/{id}/logs, andPOST /containers/{id}/restart. Everything else (includingcontainers.create,start,kill,exec,prune, and the entire images/volumes/networks/swarm surface) returns 403, so an admin RCE cannot pivot to host root via the Docker API. - OAuth uses burner Google accounts — never your primary
- All secrets are auto-generated by
setup.sh— no default passwords in production
MIT (see LICENSE)