Skip to main content

Workspace domains runbook

Policy and decisions: Workspace domains: access and abuse controls.

Components

Environment

apps/web (server only): Worker (apps/workspace-domains):

Cloudflare setup

The zone, apex and fallback origin are configuration, not code. Production uses the values below; moving the SaaS zone means changing the environment, the token and the Worker’s account, then re-verifying domains.
  1. SaaS zone (production ttr.gg). Enable SSL for SaaS, create the fallback origin (production saas.ttr.gg, a proxied record), and keep the existing platform hosts (ttr.gg, www.ttr.gg, fysh.ttr.gg) on their routes. They stay reserved in the hostname policy; any other existing record or route on the zone also blocks that name from becoming a platform subdomain.
  2. API token for apps/web, the SaaS zone only: DNS Edit (platform subdomain records and conflict checks), SSL and Certificates Edit (custom hostnames), Workers Routes Edit (per-host routes and conflict checks). No account-level permissions. Store it as a sensitive environment variable.
  3. Deploy token (operator only): account Workers Scripts Edit for publishing tuturuuu-workspace-domains. Do not give this scope to apps/web.

Personal-account dependency

The production SaaS zone ttr.gg lives in the owner’s personal Cloudflare account, not a Tuturuuu organization account. Consequences:
  • Rex owns rotation of CLOUDFLARE_SAAS_API_TOKEN.
  • Selene scopes the token to ttr.gg only, with DNS Edit, SSL and Certificates Edit and Workers Routes Edit, and keeps it a sensitive environment variable.
  • Losing access to that account stops new domains and certificate renewals; existing records keep serving until removed. Moving to an organization zone is a configuration change (zone ID, account ID, apex, fallback origin, token) plus re-verification of every domain.

Deploy the Worker

The Worker has workers_dev and preview URLs off and no routes. For a bootstrap host before apps/web ships, attach a route manually: Workers Routes, pattern <host>/*, Worker tuturuuu-workspace-domains, and point the host at the zone (custom hostname for external domains, proxied record for a platform subdomain). The bootstrap hosts vhp.ttr.gg and vohoangphuc.com are seeded by migration 20261111120200; their operator-made routes are not tracked on the rows, so removing them through the API leaves those routes for manual cleanup. The Worker reads published content from the edge site API, never from the public delivery route, which requires a session in production. Site answers contain published entries only, with an allowlist of fields and absolute asset URLs on the app origin. Worker behavior: GET and HEAD only (405 otherwise), /__health returns ok, /robots.txt for known hosts, 404 for unknown hosts, strict CSP without scripts, nosniff, HSTS, no cookies. Resolve and site answers are cached for 60 seconds with the Cache API. Origin requests send User-Agent: tuturuuu-workspace-domains/1 because apps/web rejects anonymous API calls without one.

Polar add-on (billing launch)

Do not create the product until billing is approved. Then:
  1. Create a recurring Polar product priced at $10 per unit per month, seat-based so the quantity is the number of domains, with metadata product_kind: custom_domain_addon.
  2. Set WORKSPACE_DOMAINS_POLAR_PRODUCT_ID to its ID.
  3. Webhooks from the existing Polar endpoint reconcile subscriptions into private.workspace_domain_addons; product sync skips the add-on so it never appears as a plan.
  4. After preview sign-off, set update private.workspace_domain_settings set paid_addon_enabled = true; and WORKSPACE_DOMAINS_BILLING_ENABLED=true.

Operations

  • Kill switch: update private.workspace_domain_settings set enabled = false; blocks adds, verification and serving through the resolver (the Worker returns 404 once its 60-second cache expires; clear STATIC_HOST_MAP too).
  • Cap override: insert into private.workspace_domain_caps (ws_id, domain_cap, reason).
  • Audit: private.workspace_domain_audit by ws_id, newest first.
  • Stuck provider cleanup: removed rows keep their Cloudflare IDs and an error such as provider_cleanup_403; delete those resources manually.
  • CLI: ttr domains list|add <host>|verify <host>|remove <host> --confirm <host> --workspace <id> [--json].