Self-Hosting the Terminal Broker
The terminal feature lets you run CLI agents (Claude, Codex, Gemini) directly from Telegram. By default it uses the shared hosted broker at terminal.aidaemon.ai. Self-hosting gives you full control over the relay infrastructure.
Architecture
The terminal system has three components:
- aidaemon daemon (Rust) — runs on your machine, spawns PTY sessions, bridges to the broker via WebSocket
- Broker (Cloudflare Worker) — WebSocket relay between the Mini App and the daemon. Handles auth, rate limiting, and session management
- Mini App (embedded in broker) — Telegram Mini App frontend with xterm.js terminal emulator
Telegram โโ> Mini App (browser) <โโWSSโโ> Broker (CF Worker) <โโWSSโโ> aidaemon daemon โโ> local PTY
โโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ Cloudflare โ โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโ
โ Telegram โโโโ>โ Mini App โ<โ>โ Worker (Broker) โ<โ>โ aidaemon โโโ>โ local PTY โ
โ โ โ (xterm.js) โ โ - auth โ โ (terminal โ โ (bash, โ
โ โ โ โ โ - rate limiting โ โ bridge) โ โ zsh...) โ
โโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ - session mgmt โ โโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโ
WSS โโโโโโโโโโโโโโโโโโโโโ WSSPrerequisites
- Cloudflare account — free tier is sufficient for personal use
- A Telegram bot — created via @BotFather
- aidaemon installed and running — with the Telegram channel configured (Telegram setup guide)
- Node.js 18+ and npm — for the wrangler CLI
Step 1 — Clone the Broker
The broker is a standalone Cloudflare Worker project. Clone it and install dependencies:
git clone https://github.com/davo20019/terminal.aidaemon.ai.git
cd terminal.aidaemon.ai
npm installStep 2 — Configure Deployment
Open wrangler.toml and choose how you want to deploy:
Option A: workers.dev Subdomain (Simplest)
If you don't have a custom domain, use Cloudflare's free *.workers.dev subdomain. Comment out or remove the routes block:
name = "terminal-broker"
main = "src/index.js"
compatibility_date = "2024-12-05"
compatibility_flags = ["nodejs_compat"]
# No routes block = uses your-name.workers.dev
# Your broker URL will be: https://terminal-broker.<your-subdomain>.workers.dev
[durable_objects]
bindings = [
{ name = "TERMINAL_SESSION", class_name = "TerminalSession" }
]
[[migrations]]
tag = "v1"
new_classes = ["TerminalSession"]Option B: Custom Domain
If you have a domain on Cloudflare DNS, set up a route to it:
name = "terminal-broker"
main = "src/index.js"
compatibility_date = "2024-12-05"
compatibility_flags = ["nodejs_compat"]
routes = [
{ pattern = "terminal.yourdomain.com", zone_name = "yourdomain.com" }
]
[durable_objects]
bindings = [
{ name = "TERMINAL_SESSION", class_name = "TerminalSession" }
]
[[migrations]]
tag = "v1"
new_classes = ["TerminalSession"]Step 3 — Set Secrets
The broker needs a few secrets to operate. Set them using the wrangler CLI:
# Required: signs all authentication tokens
npx wrangler secret put SESSION_SIGNING_KEY
# paste output of: openssl rand -base64 32
# Required: validates Telegram user identity
npx wrangler secret put TELEGRAM_BOT_TOKEN
# paste your bot token from @BotFather
# Optional: protects admin API endpoints
npx wrangler secret put ADMIN_API_KEY
# paste a strong random stringSESSION_SIGNING_KEY signs all authentication tokens. If it is compromised, an attacker could forge sessions. Generate a strong random value and store it securely. Rotate it immediately if you suspect a leak.Full list of available secrets:
| Key | Type | Default | Description |
|---|---|---|---|
SESSION_SIGNING_KEY | string | — | Required. Signs auth tokens. Generate with openssl rand -base64 32 |
TELEGRAM_BOT_TOKEN | string | — | Required. Bot token from @BotFather. Must match the token in your aidaemon config |
ADMIN_API_KEY | string | — | Optional. Protects admin endpoints (user management, quota overrides) |
BILLING_WEBHOOK_SECRET | string | — | Optional. HMAC secret for billing webhook signature verification |
Step 4 — Deploy
Deploy the broker to Cloudflare Workers:
npx wrangler deployYou should see output like this:
Uploaded terminal-broker (3.45 sec)
Published terminal-broker (0.35 sec)
https://terminal-broker.your-subdomain.workers.dev
Current Deployment ID: abc12345-...TerminalSession. No manual migration steps are needed.Step 5 — Configure aidaemon
Point your aidaemon daemon at your self-hosted broker. Update your config.toml:
[terminal]
web_app_url = "https://your-broker.your-domain.com"
daemon_ws_url = "wss://your-broker.your-domain.com/v1/ws/daemon"Or use environment variables:
export AIDAEMON_TERMINAL_WEB_APP_URL="https://your-broker.your-domain.com"
export AIDAEMON_TERMINAL_BROKER_URL="wss://your-broker.your-domain.com/v1/ws/daemon"Restart aidaemon after changing the configuration for the new values to take effect.
Step 6 — Configure Telegram
How you configure Telegram depends on how you launch the terminal:
Via /terminal Command (Inline Buttons)
If you use the /terminal command in your bot chat, the Mini App URL comes from web_app_url in your config.toml. No BotFather changes are needed — the inline button URL is set by aidaemon automatically.
Via Menu Button
If you want the terminal accessible as a permanent menu button in your bot:
- Open @BotFather on Telegram
- Send
/mybotsand select your bot - Go to Bot Settings → Menu Button
- Set the URL to your broker (e.g.
https://terminal-broker.your-subdomain.workers.dev)
workers.dev subdomains and Cloudflare-proxied custom domains provide HTTPS automatically.Verification
Confirm everything is working:
1. Health Check
curl https://your-broker.your-domain.com/healthExpected response:
{
"ok": true,
"service": "your-broker.your-domain.com",
"version": "1.0.0",
"timestamp": "2026-03-01T12:00:00.000Z"
}2. Test the Mini App
Send /terminal to your bot in Telegram. The Mini App should open and display the terminal interface.
3. Check aidaemon Logs
Look for the terminal bridge connection in your aidaemon logs:
[INFO] Connecting terminal bridge to broker: wss://your-broker.your-domain.com/v1/ws/daemon"ok": true, the Mini App opens in Telegram, and aidaemon logs show the broker connection — your self-hosted terminal is fully operational.Quota Configuration
The broker enforces per-user monthly quotas to prevent abuse. It tracks session starts, active minutes, bytes transferred, and WebSocket frames. The default "free" plan limits are generous and more than sufficient for personal use.
To customize the plan limits, set the PLAN_LIMITS_JSON environment variable in your wrangler.toml:
[vars]
PLAN_LIMITS_JSON = '{"free":{"maxSessionsPerMonth":500,"maxMinutesPerMonth":3000,"maxBytesPerMonth":536870912,"maxFramesPerMonth":500000}}'To set a specific user to the "pro" plan (or any custom plan), use the admin API:
curl -X POST https://your-broker.your-domain.com/admin/users/TELEGRAM_USER_ID/plan \
-H "Authorization: Bearer YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"plan": "pro"}'Available plan limit keys:
| Key | Type | Default | Description |
|---|---|---|---|
maxSessionsPerMonth | integer | 100 | Maximum terminal sessions a user can start per month |
maxMinutesPerMonth | integer | 600 | Maximum active terminal minutes per month |
maxBytesPerMonth | integer | 104857600 | Maximum bytes transferred per month (default ~100 MB) |
maxFramesPerMonth | integer | 100000 | Maximum WebSocket frames per month |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| "Terminal bridge disabled" in aidaemon logs | daemon_ws_url is misconfigured | Must use wss:// (not ws://). Cloudflare Workers require TLS. |
| Mini App shows "invalid session" | Token mismatch | TELEGRAM_BOT_TOKEN in the broker must exactly match the bot token in your aidaemon config.toml |
| "Token signature mismatch" | Missing or wrong signing key | Ensure SESSION_SIGNING_KEY is set via npx wrangler secret put |
| WebSocket connection drops | Worker error or timeout | Check the Cloudflare dashboard → Workers & Pages → your worker → Logs for errors |
| "daemon_auth_rate_limited" | Too many failed connection attempts | Wait 60 seconds, then fix the underlying auth issue and reconnect |
Security Notes
- The broker is a transparent relay — terminal content is end-to-end encrypted between the Mini App and your daemon
- The broker sees: user IDs, session metadata, and encrypted frame sizes — but never the actual terminal content
- The
SESSION_SIGNING_KEYis the most critical secret — rotate it immediately if compromised - All connections use TLS (WSS) — Cloudflare Workers enforce HTTPS by default