docs/operations/public-https.md
Public HTTPS Entrypoint
Purpose
The public HTTPS entrypoint exposes the local dashboard/API (localhost:8787) over public HTTPS via Cloudflare Quick Tunnel, enabling remote access to share links and stream-frame ingest endpoints.
Scope
- Add optional runtime-managed public HTTPS entrypoint via Cloudflare Quick Tunnel (
cloudflared). - Expose tunnel state in API runtime responses.
- Provide operational knobs through environment variables.
- Keep frame ingest routes authenticated via either private admin token, public API token, or short-lived share-session token.
Runtime Behavior
1. Optional Public HTTPS Runtime
When PUBLIC_HTTPS_ENABLED=true:
- Bun process starts a child process:
cloudflared tunnel --url <target> --no-autoupdate
- The system watches child output and extracts a
https://*.trycloudflare.comURL. - On URL discovery, runtime state switches to
ready.
When disabled:
- Runtime state remains
disabled. - No child process is created.
2. Health and State Exposure
API surfaces:
GET /api/public-httpsGET /api/statsincludesruntime.publicHttps
Local discovery:
- The dashboard advertises
_clanky._tcpover Bonjour for the iOS app. - The dashboard does not advertise the Bonjour service until a usable tunnel URL exists.
- The Bonjour TXT record includes
tunnelUrlonce the public HTTPS entrypoint is ready. - The iOS setup flow keeps Bonjour browsing and TXT monitoring alive while the setup screen is visible, even after it finds a usable tunnel URL.
- The setup form auto-fills the tunnel URL when discovery first resolves, and replaces that value on later Bonjour updates only if the field still matches the last auto-filled value.
- After setup succeeds, the iOS Pulse tab opens
/api/activity/eventsand treats the SSE transport opening as connected immediately; it does not wait for the first activity payload before leaving the loading state. - If the activity SSE transport drops and starts retrying, the iOS Pulse tab surfaces that as a disconnected/reconnecting state instead of staying on an indefinite initial spinner.
State shape:
{
"enabled": true,
"provider": "cloudflared",
"status": "ready",
"targetUrl": "http://127.0.0.1:8787",
"publicUrl": "https://example.trycloudflare.com",
"pid": 12345,
"startedAt": "2026-02-27T12:34:56.789Z",
"lastError": ""
}
status values:
disabledidlestartingreadyerrorstopped
3. Public/Private Route Gating
- Tunnel-host requests are treated as public ingress traffic.
- Public ingress allowlist is intentionally narrow:
POST /api/voice/stream-ingest/frame- tokenized
POST /api/voice/share-session/:token/frame - tokenized
POST /api/voice/share-session/:token/stop GET /share/:token
- Non-allowlisted API routes on tunnel host (without a valid token) return
404. - Dashboard UI/static routes on tunnel host return
404unless they are tokenized share pages. - Local dashboard browser login uses
POST /api/auth/sessionto mint an HTTP-only signed session cookie. - Dashboard auth-session routes are not exposed on the tunnel host.
- Public header-token routes (
/api/voice/stream-ingest/frame) accept either:x-dashboard-tokenmatchingDASHBOARD_TOKEN, orx-public-api-tokenmatchingPUBLIC_API_TOKEN.
- If neither token is configured, header-token routes return
503. - Private/local admin routes require a dashboard session cookie or
x-dashboard-tokenwhen public HTTPS is enabled. - Remote admin API access over the tunnel continues to use
x-dashboard-tokendirectly; the browser-login flow stays local-only. - Dashboard/API listener defaults to loopback host (
127.0.0.1) unless explicitly overridden. - Frame ingress applies fixed-window rate limiting and declared payload-size checks on public paths.
4. Failure Handling
- If
cloudflaredis missing or exits unexpectedly:- state becomes
error - action log records
bot_error - automatic retry runs after a short delay for runtime exits
- spawn
ENOENT(missing binary) blocks automatic retry until operator fixes config/binary and restarts
- state becomes
5. Shutdown Behavior
- On process shutdown:
- bot disconnects
- public HTTPS child process receives termination signal
- dashboard server closes
Configuration
Environment variables:
PUBLIC_HTTPS_ENABLED(true|false, defaultfalse)PUBLIC_HTTPS_TARGET_URL(optional, defaulthttp://127.0.0.1:${DASHBOARD_PORT})PUBLIC_HTTPS_CLOUDFLARED_BIN(optional, defaultcloudflared)PUBLIC_API_TOKEN(optional; enables public-header auth path for allowlisted ingest route)DASHBOARD_HOST(optional bind host, default127.0.0.1)DASHBOARD_TOKEN(required for private/admin APIs whenPUBLIC_HTTPS_ENABLED=true)
Security Model
- Public HTTPS entrypoint requires full API auth.
- Browser dashboard auth uses an HTTP-only signed cookie so the admin token is not stored in browser localStorage or replayed on every request.
- Stream ingest endpoint requires either private admin auth (
DASHBOARD_TOKEN) or public ingress auth (PUBLIC_API_TOKEN). - The tunnel URL should be treated as an untrusted public entrypoint; tokens must be kept secret.
Observability
- Action stream includes:
public_https_entrypoint_starting(bot_runtime)public_https_entrypoint_ready(bot_runtime)- spawn/exit/log failures (
bot_error)
- Dashboard metrics include a
Public HTTPScard showing current state/URL host.
Setup
- Install
cloudflared. - Set
.env:PUBLIC_HTTPS_ENABLED=trueDASHBOARD_TOKEN=<strong secret>(required for private/admin APIs)- optionally
PUBLIC_API_TOKEN=<strong secret>(for public-header ingress auth)
- Start the bot:
bun run start. - Confirm tunnel:
- Dashboard
Public HTTPSmetric, or GET /api/public-https.
- Dashboard
- The returned HTTPS origin is used for remote share workflows.
