chore: move lms to myclassroomio.com and use approximated for custom

domain
This commit is contained in:
Rotimi Best
2026-05-21 11:29:47 +01:00
parent c090b7b4d6
commit 61cbb258ea
14 changed files with 114 additions and 210 deletions
+8 -19
View File
@@ -2,10 +2,10 @@
Cloudflare Worker that fronts every browser-facing ClassroomIO host:
- `*.classroomio.school/*` — free-tier tenant sites
- `classroomio.school/*` — apex (301 → `classroomio.com`)
- `*.myclassroomio.com/*` — free-tier tenant sites
- `myclassroomio.com/*` — apex (301 → `classroomio.com`)
- `app.classroomio.com/*` — admin dashboard
- BYOD customer domains via Cloudflare for SaaS
- BYOD customer domains via Approximated (their proxy forwards to `cio-api.onrender.com` directly, not through this Worker)
The Worker forwards each request to one of two Render services based on path:
@@ -23,7 +23,7 @@ the API. Example: a browser request to
It preserves the original host as `X-Forwarded-Host` so:
- The dashboard's SvelteKit adapter-node reads it via `HOST_HEADER=x-forwarded-host` and `event.url.host` becomes the tenant/admin/BYOD host.
- Better Auth on the API emits session cookies without a `Domain` attribute (see `packages/db/src/auth.ts` — `crossSubDomainCookies: { enabled: false }`), so the browser scopes each session to the exact host it called. Tenants on `acme.classroomio.school`, the admin app on `app.classroomio.com`, and BYOD `learn.acme.com` each get host-only sessions.
- Better Auth on the API emits session cookies without a `Domain` attribute (see `packages/db/src/auth.ts` — `crossSubDomainCookies: { enabled: false }`), so the browser scopes each session to the exact host it called. Tenants on `acme.myclassroomio.com`, the admin app on `app.classroomio.com`, and BYOD `learn.acme.com` each get host-only sessions.
## Deploy
@@ -43,33 +43,22 @@ pnpm --filter @cio/tenant-router tail
|---|---|
| `DASHBOARD_UPSTREAM_HOST` | `.onrender.com` host of the dashboard service (no scheme) |
| `API_UPSTREAM_HOST` | `.onrender.com` host of the API service |
| `APEX_REDIRECT_TARGET` | URL to redirect `classroomio.school` apex to (default `https://classroomio.com`) |
| `APEX_REDIRECT_TARGET` | URL to redirect `myclassroomio.com` apex to (default `https://classroomio.com`) |
Update the values in `wrangler.toml` after looking them up in the Render dashboard.
## Cloudflare DNS expected setup
`classroomio.school` zone (new):
`myclassroomio.com` zone:
- `*` CNAME → any valid target, **Proxied**
- `@` CNAME → any valid target, **Proxied** (Worker serves the 301)
- `cname` CNAME → any valid target, **Proxied** — this is the value BYOD customers point their CNAME at
`classroomio.com` zone:
- `app` CNAME → **Proxied** so the route binds
- `api` CNAME → **DNS only** (server-to-server callers and webhooks bypass the Worker)
- The old wildcard `*` CNAME on `classroomio.com` should be deleted — tenant traffic now belongs on `classroomio.school`.
## Cloudflare for SaaS (BYOD)
## BYOD custom domains
On the `classroomio.school` zone:
- **SSL/TLS → Custom Hostnames**: enabled
- **Fallback Origin**: `cname.classroomio.school` — BYOD traffic lands on the Worker route and is forwarded through the same code path as `*.classroomio.school`.
The API uses these env vars (`apps/api/src/services/org/domain.ts`):
- `CLOUDFLARE_CUSTOM_HOSTNAMES_API_TOKEN` — Cloudflare API token scoped to the `classroomio.school` zone with `Zone → SSL and Certificates → Edit` + `Zone → Zone → Read`.
- `CLOUDFLARE_CUSTOM_HOSTNAMES_ZONE_ID` — Zone ID for `classroomio.school`.
- `CLOUDFLARE_CUSTOM_HOSTNAME_CNAME_TARGET=cname.classroomio.school`.
Handled by [Approximated](https://approximated.app/), not this Worker. Cloudflare for SaaS can't front a Render origin (Render itself runs on Cloudflare), so we use Approximated's dedicated edge cluster instead. See `apps/api/src/services/org/domain.ts` for the integration. Customers point an A record at the IP Approximated returns; the proxy forwards directly to `cio-api.onrender.com`.