chore: move lms to myclassroomio.com and use approximated for custom
domain
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* @cio/tenant-router — Cloudflare Worker that fronts every browser-facing host.
|
||||
*
|
||||
* Routes (see wrangler.toml):
|
||||
* - *.classroomio.school/* — free-tier tenant sites (acme.classroomio.school)
|
||||
* - classroomio.school/* — apex (301 → classroomio.com)
|
||||
* - *.myclassroomio.com/* — free-tier tenant sites (acme.myclassroomio.com)
|
||||
* - myclassroomio.com/* — apex (301 → classroomio.com)
|
||||
* - app.classroomio.com/* — admin dashboard
|
||||
* - <BYOD>/* — customer-owned domains via Cloudflare for SaaS
|
||||
*
|
||||
@@ -45,8 +45,8 @@ export default {
|
||||
const url = new URL(request.url);
|
||||
const originalHost = request.headers.get('host') ?? url.host;
|
||||
|
||||
// Apex on classroomio.school → permanent redirect to marketing apex.
|
||||
if (originalHost === 'classroomio.school') {
|
||||
// Apex on myclassroomio.com → permanent redirect to marketing apex.
|
||||
if (originalHost === 'myclassroomio.com') {
|
||||
const target = new URL(env.APEX_REDIRECT_TARGET);
|
||||
target.pathname = url.pathname;
|
||||
target.search = url.search;
|
||||
|
||||
@@ -3,12 +3,12 @@ main = "src/index.ts"
|
||||
compatibility_date = "2026-05-01"
|
||||
|
||||
# Production routes — bind the Worker to every browser-facing host.
|
||||
# Tenant subdomains and the apex live on the `classroomio.school` zone.
|
||||
# Tenant subdomains and the apex live on the `myclassroomio.com` zone.
|
||||
# The admin app `app.classroomio.com` is routed through the Worker so
|
||||
# it can use same-origin `/api/*` paths with host-only auth cookies.
|
||||
routes = [
|
||||
{ pattern = "*.classroomio.school/*", zone_name = "classroomio.school" },
|
||||
{ pattern = "classroomio.school/*", zone_name = "classroomio.school" },
|
||||
{ pattern = "*.myclassroomio.com/*", zone_name = "myclassroomio.com" },
|
||||
{ pattern = "myclassroomio.com/*", zone_name = "myclassroomio.com" },
|
||||
{ pattern = "app.classroomio.com/*", zone_name = "classroomio.com" }
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user