Skip to content

feat(tunnel): multi-hostname support, headless cert reuse, and tunnel delete#670

Open
bussyjd wants to merge 1 commit into
mainfrom
feat/tunnel-multi-domain
Open

feat(tunnel): multi-hostname support, headless cert reuse, and tunnel delete#670
bussyjd wants to merge 1 commit into
mainfrom
feat/tunnel-multi-domain

Conversation

@bussyjd

@bussyjd bussyjd commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Rounds out the tunnel CLI on top of the new least-privilege Cloudflare auth (connector token / dashboard-managed; no account-wide API token), completing the lifecycle setup → hostname add/remove → delete. No account-wide Cloudflare API token is used anywhere in this change.

What's in it

tunnel hostname {add,list,remove} — N public hostnames per tunnel

"Deploy one domain, then another." Offer HTTPRoutes are host-agnostic, so every hostname serves the same x402-gated surface and adding a second never takes down the first.

  • Local-managed (cert): routes the DNS CNAME via cloudflared (cert-scoped), re-renders the connector ingress over all hostnames, and reloads the connector (a ConfigMap-only change doesn't roll the pods, so the connector would otherwise keep its old ingress).
  • Dashboard-managed (connector token): tracks state, updates the storefront route, and prints the one dashboard step (Obol holds no API token to edit a dashboard tunnel's ingress, by design).
  • tunnelState gains an ordered Hostnames slice (primary = Hostnames[0]) with legacy single-hostname migration; CreateStorefront publishes over the full set; buildLocalManagedConfigYAML emits one ingress rule per hostname.

tunnel login --reuse-cert — headless provisioning

Skip the browser and reuse an existing ~/.cloudflared/cert.pem to stand up additional clusters on the same Cloudflare account without a browser. Errors when no usable cert is present (explicit headless contract); the default still does a fresh browser login.

tunnel delete (alias teardown) — full teardown

The destructive counterpart to tunnel stop (which only pauses). Deletes the Cloudflare tunnel where Obol holds the credential (local cert-scoped cloudflared tunnel delete), cleans in-cluster connector resources + storefront + state, reverts the connector to a default quick tunnel, and prints the dashboard/DNS steps it can't do without a broad token. --force + confirm-on-TTY (matches agent delete); stop's usage clarified as pause-vs-delete.

Naming: delete matches the app's teardown verb (agent/sell/network/openclaw delete). Deliberately not aliased down, since stack down means stop (pause) — tunnel down→delete would be the opposite and confusing.

Relation to #668

Orthogonal substrate: this makes N hostnames live (all offers on all hosts); #668 is the inverse (bind one offer to a dedicated host). buildHTTPRoute is untouched, so #668 stays a clean follow-up.

Testing

  • 25 tunnel unit tests: state migration (legacy/dedup/scalar-less/round-trip), listing, multi-hostname + dedup ingress render, and every add/remove/delete guard. go build ./..., go vet, and the suite are green.
  • Local-managed multi-hostname validated live: added a second *.v1337.org hostname alongside a live tunnel → both served 200/402 simultaneously → removed it → the first was never disrupted.
  • tunnel delete is unit-tested + build-verified but not run end-to-end live: the only available cluster fronts a production tunnel, and the real teardown would destroy it. Wiring confirmed via tunnel delete --help.

Notes

  • Token-less hostname removal leaves a dangling CNAME (404s) for the operator to delete in the dashboard — consistent with the least-privilege model.
  • No whole-account DNS deletion anywhere (intentional).

https://claude.ai/code/session_01YUTW7NfxjUoVtyKgR6nPQC

… delete

Align the tunnel CLI with the new least-privilege Cloudflare auth (connector
token / dashboard-managed; no account-wide API token) and round out the tunnel
lifecycle: setup -> hostname add/remove -> delete.

tunnel hostname {add,list,remove}: serve N public hostnames on one tunnel.
Local-managed (cert) routes the DNS CNAME, re-renders the connector ingress over
all hostnames, and reloads the connector; dashboard-managed tracks state, updates
the storefront route, and prints the dashboard step. Adding a second hostname
never disrupts the first. tunnelState gains an ordered Hostnames slice
(primary = Hostnames[0]) with legacy single-hostname migration; CreateStorefront
publishes over the full set; buildLocalManagedConfigYAML emits one ingress rule
per hostname.

tunnel login --reuse-cert: skip the browser and reuse an existing
~/.cloudflared/cert.pem to provision additional clusters headlessly. Errors when
no usable cert is present, so the headless contract is explicit; the default
still does a fresh browser login.

tunnel delete (alias teardown): full teardown completing the lifecycle. Deletes
the Cloudflare tunnel where Obol holds the credential (local cert-scoped
'cloudflared tunnel delete'), cleans in-cluster connector resources + storefront
+ state, reverts the connector to a default quick tunnel, and prints the
dashboard/DNS steps it cannot do without a broad token. --force + confirm-on-TTY,
matching 'agent delete'; 'stop' usage clarified as pause-vs-delete.

No account-wide Cloudflare API token is used anywhere in the feature; DNS records
and dashboard-managed tunnels are left to the operator with explicit instructions.

Tests: 25 tunnel unit tests (state migration, listing, multi-hostname/dedup
render, add/remove/delete guards). Local-managed multi-hostname validated live
(two domains served simultaneously through the x402 gate, removed without
disrupting the first).

Claude-Session: https://claude.ai/code/session_01YUTW7NfxjUoVtyKgR6nPQC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant