From 26d3cd72d1cb02e4487a847fb3671c19b8624e27 Mon Sep 17 00:00:00 2001 From: Zach Dunn Date: Thu, 8 Oct 2026 14:36:52 -0400 Subject: [PATCH] chore(dev): move local origin to local.uploadrouter.dev Retire the shared local.buildinternet.dev zone for the project's own uploadrouter.dev domain. Document the uploadrouter.com alias redirect and the uploadrouter.dev local records in docs/ops.md. --- CONTRIBUTING.md | 2 +- apps/auth/src/index.test.ts | 6 ++--- apps/auth/src/local-demo.ts | 10 ++++---- apps/auth/src/trusted-origins.test.ts | 4 ++-- apps/web/.dev.vars.example | 2 +- apps/web/src/lib/auth-client.test.ts | 6 ++--- apps/web/src/lib/auth-client.ts | 6 ++--- apps/web/src/lib/workspace-cache.test.ts | 6 ++--- docs/local-dev.md | 29 ++++++++++++------------ docs/ops.md | 15 ++++++++++++ portless.json | 10 ++++---- scripts/dev-stack-common.mjs | 10 ++++---- scripts/dev-stack.mjs | 2 +- 13 files changed, 62 insertions(+), 46 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea862df0..b0445424 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ pnpm dev:stack:check --json # machine-readable readiness + session smoke proof ``` `pnpm dev:stack` runs through [portless](https://npmjs.com/portless), so WEB -gets `https://uploads.local.buildinternet.dev` instead of a bare port. Local +gets `https://local.uploadrouter.dev` instead of a bare port. Local sign-in behaves like production — `/account/*` and `/admin/*` work in a browser. [docs/local-dev.md](docs/local-dev.md#named-local-urls-portless) has the origin table, OAuth redirect URIs, and a curl smoke test. diff --git a/apps/auth/src/index.test.ts b/apps/auth/src/index.test.ts index b8c1c9d0..51cb8fb6 100644 --- a/apps/auth/src/index.test.ts +++ b/apps/auth/src/index.test.ts @@ -181,10 +181,10 @@ describe("local demo session", () => { } }); - it("is available for the owned local.buildinternet.dev zone, including worktree-prefixed", async () => { + it("is available for the owned local.uploadrouter.dev zone, including worktree-prefixed", async () => { for (const webOrigin of [ - "https://uploads.local.buildinternet.dev", - "https://fix-ui.uploads.local.buildinternet.dev", + "https://local.uploadrouter.dev", + "https://fix-ui.local.uploadrouter.dev", ]) { const env = localEnv({ BETTER_AUTH_URL: webOrigin, WEB_ORIGIN: webOrigin }); const res = await app.request( diff --git a/apps/auth/src/local-demo.ts b/apps/auth/src/local-demo.ts index b43f75ff..e0fb003f 100644 --- a/apps/auth/src/local-demo.ts +++ b/apps/auth/src/local-demo.ts @@ -23,18 +23,18 @@ import * as schema from "./schema"; export const LOCAL_STACK_WEB_ORIGIN = "http://127.0.0.1:4321"; /** - * Default portless hostname (TLD `dev` + name `uploads.local.buildinternet`). + * Default portless hostname (TLD `dev` + name `local.uploadrouter`). * Keep in sync with `portless.json`, `PORTLESS_BASE` in * `scripts/dev-stack-common.mjs`, and `isLocalDemoStack` in the web app. */ -export const LOCAL_STACK_DEV_HOST = "uploads.local.buildinternet.dev"; +export const LOCAL_STACK_DEV_HOST = "local.uploadrouter.dev"; /** * True for a local-stack WEB origin shape: the raw stack's pinned loopback * port, a leftover portless `*.localhost` origin (optionally worktree- * prefixed, optionally on the sudo-less proxy port), or the owned - * `uploads.local.buildinternet.dev` zone (same sibling convention as Either; - * worktree prefix `fix-ui.uploads.local.buildinternet.dev`). Bare + * `local.uploadrouter.dev` zone (the project's dedicated dev domain; + * worktree prefix `fix-ui.local.uploadrouter.dev`). Bare * `uploads.dev` is not local — that is the collision you get if the app name * stays `uploads` under `--tld dev`. */ @@ -68,7 +68,7 @@ const DEMO_ORGANIZATION = { id: "local-dev-demo-org", slug: "dev-demo", name: "D * see scripts/dev-stack.mjs), so this requires that equality PLUS a * recognized local-stack web-origin shape (the raw stack's pinned loopback * port, a leftover `*.localhost` origin, or the owned - * `uploads.local.buildinternet.dev` zone). Unrelated real TLDs never enable + * `local.uploadrouter.dev` zone). Unrelated real TLDs never enable * the bypass, even if they happen to be same-origin. */ export function localDemoEnabled(env: AuthEnv): boolean { diff --git a/apps/auth/src/trusted-origins.test.ts b/apps/auth/src/trusted-origins.test.ts index 1345ba02..4201a83f 100644 --- a/apps/auth/src/trusted-origins.test.ts +++ b/apps/auth/src/trusted-origins.test.ts @@ -56,7 +56,7 @@ describe("isTrustedOrigin", () => { const worktree = { WEB_ORIGIN: "https://fix-ui.uploads.localhost", ENVIRONMENT: "development" }; expect(isTrustedOrigin("https://fix-ui.uploads.localhost", worktree)).toBe(true); - const zone = "uploads.local.buildinternet.dev"; + const zone = "local.uploadrouter.dev"; const realTld = { WEB_ORIGIN: `https://${zone}`, ENVIRONMENT: "development" }; expect(isTrustedOrigin(`https://${zone}`, realTld)).toBe(true); expect(isTrustedOrigin(`https://auth.${zone}`, realTld)).toBe(false); @@ -66,7 +66,7 @@ describe("isTrustedOrigin", () => { const env = { WEB_ORIGIN: "https://uploads.sh", ENVIRONMENT: "development" }; expect(isTrustedOrigin("https://evil.example", env)).toBe(false); expect(isTrustedOrigin("https://uploads.localhost", env)).toBe(false); - expect(isTrustedOrigin("https://uploads.local.buildinternet.dev", env)).toBe(false); + expect(isTrustedOrigin("https://local.uploadrouter.dev", env)).toBe(false); }); it("allows extra trusted origins from env in any environment", () => { diff --git a/apps/web/.dev.vars.example b/apps/web/.dev.vars.example index 2392ee54..78ed63f3 100644 --- a/apps/web/.dev.vars.example +++ b/apps/web/.dev.vars.example @@ -12,7 +12,7 @@ # # NOTE: `pnpm dev:stack` runs through portless (see docs/local-dev.md) and # injects the resolved auth/API upstream origins (plain loopback ports; only -# web gets https://uploads.local.buildinternet.dev) via process env at +# web gets https://local.uploadrouter.dev) via process env at # startup — it does not need this file. If you run the portless stack and # signed-in pages still resolve loopback/prod origins, a stale .dev.vars here # is the usual culprit. diff --git a/apps/web/src/lib/auth-client.test.ts b/apps/web/src/lib/auth-client.test.ts index 33dc85dd..fe7e0c80 100644 --- a/apps/web/src/lib/auth-client.test.ts +++ b/apps/web/src/lib/auth-client.test.ts @@ -796,9 +796,9 @@ describe("isLocalDemoStack", () => { expect(isLocalDemoStack("http://uploads.localhost:1355")).toBe(true); }); - it("matches the owned local.buildinternet.dev zone, including worktree-prefixed", () => { - expect(isLocalDemoStack("https://uploads.local.buildinternet.dev")).toBe(true); - expect(isLocalDemoStack("https://fix-ui.uploads.local.buildinternet.dev")).toBe(true); + it("matches the owned local.uploadrouter.dev zone, including worktree-prefixed", () => { + expect(isLocalDemoStack("https://local.uploadrouter.dev")).toBe(true); + expect(isLocalDemoStack("https://fix-ui.local.uploadrouter.dev")).toBe(true); }); it("rejects production and other non-local-stack origins", () => { diff --git a/apps/web/src/lib/auth-client.ts b/apps/web/src/lib/auth-client.ts index 890baa64..2b2d3849 100644 --- a/apps/web/src/lib/auth-client.ts +++ b/apps/web/src/lib/auth-client.ts @@ -117,14 +117,14 @@ export type SessionResult = const LOCAL_STACK_WEB_ORIGIN = "http://127.0.0.1:4321"; /** Keep in sync with `LOCAL_STACK_DEV_HOST` in `apps/auth/src/local-demo.ts`. */ -const LOCAL_STACK_DEV_HOST = "uploads.local.buildinternet.dev"; +const LOCAL_STACK_DEV_HOST = "local.uploadrouter.dev"; /** * True only when `pageOrigin` is a recognized local-stack web origin: the * raw stack's pinned loopback port, a leftover portless `*.localhost` * origin (optionally worktree-prefixed), or the owned - * `uploads.local.buildinternet.dev` zone (worktree prefix - * `fix-ui.uploads.local.buildinternet.dev`). + * `local.uploadrouter.dev` zone (worktree prefix + * `fix-ui.local.uploadrouter.dev`). * * #731 phase C: this used to compare the injected auth origin against its * own pinned loopback port, but Phase B made every injected auth origin `""` diff --git a/apps/web/src/lib/workspace-cache.test.ts b/apps/web/src/lib/workspace-cache.test.ts index 38c54924..e6357463 100644 --- a/apps/web/src/lib/workspace-cache.test.ts +++ b/apps/web/src/lib/workspace-cache.test.ts @@ -32,7 +32,7 @@ function snapshot(overrides: Partial = {}): WorkspaceSnapshot slug: "buildinternet", role: "owner", hasPublicUrl: true, - publicBaseUrl: "https://media.buildinternet.dev", + publicBaseUrl: "https://media.uploadrouter.dev", plan: "free", usage: { bytes: 8_500_000, objects: 78, uploadsInPeriod: 15, maxStorageBytes: 10_000_000_000 }, ...overrides, @@ -120,7 +120,7 @@ describe("toWorkspaceSnapshot", () => { organization: { id: "org_1", slug: "buildinternet", name: "BuildInternet" }, role: "owner", hasPublicUrl: true, - publicBaseUrl: "https://media.buildinternet.dev", + publicBaseUrl: "https://media.uploadrouter.dev", plan: "free", }, { @@ -140,7 +140,7 @@ describe("toWorkspaceSnapshot", () => { slug: "buildinternet", role: "owner", hasPublicUrl: true, - publicBaseUrl: "https://media.buildinternet.dev", + publicBaseUrl: "https://media.uploadrouter.dev", plan: "free", usage: { bytes: 8_500_000, diff --git a/docs/local-dev.md b/docs/local-dev.md index 945d1065..459ffda5 100644 --- a/docs/local-dev.md +++ b/docs/local-dev.md @@ -19,16 +19,16 @@ pnpm typecheck # wrangler types + tsc across workspaces ## Named local URLs (portless) `pnpm dev:stack` runs through [portless](https://npmjs.com/portless) on the -shared `local.buildinternet.dev` infra zone, same as the sibling repos. WEB -gets a stable named HTTPS origin instead of a bare port. Google and Apple -reject `*.localhost` redirect URIs; this hostname is a real Public Suffix -domain that they accept. +project's own `local.uploadrouter.dev` zone. WEB gets a stable named HTTPS +origin instead of a bare port. Google (like most OAuth providers) rejects +`*.localhost` redirect URIs; this hostname is a real Public Suffix domain +that it accepts. -| Service | URL | Browser-visible? | -| ------- | ----------------------------------------- | ----------------------------------------------------- | -| web | `https://uploads.local.buildinternet.dev` | yes — the only origin the browser talks to | -| auth | plain loopback (dynamic) | no — internal upstream behind web's `/api/auth` proxy | -| api | plain loopback (dynamic) | no — internal upstream behind web's `/api` proxy | +| Service | URL | Browser-visible? | +| ------- | -------------------------------- | ----------------------------------------------------- | +| web | `https://local.uploadrouter.dev` | yes — the only origin the browser talks to | +| auth | plain loopback (dynamic) | no — internal upstream behind web's `/api/auth` proxy | +| api | plain loopback (dynamic) | no — internal upstream behind web's `/api` proxy | The name and TLD live in the repo (`portless.json` plus `PORTLESS_TLD=dev` on `dev:stack`). You do not set `PORTLESS_NAME` in a local env file. A short @@ -45,20 +45,21 @@ as plain `127.0.0.1` loopback processes on ports assigned dynamically at boot is host-only on the web origin (same shape as prod's host-only `uploads.sh` cookie), and signed-in pages (`/account/*`, `/admin/*`) just work in a local browser, including agent browser panels. In a linked git worktree, portless -prefixes the branch name (`fix-ui.uploads.local.buildinternet.dev`); nothing +prefixes the branch name (`fix-ui.local.uploadrouter.dev`); nothing else changes. `dev:stack` prints the resolved `previewUrl` when ready, and `pnpm dev:stack:check --json` reports it too. -The zone is deliberately not under uploads.sh. Prod's session cookie is +The zone is deliberately not under uploads.sh, and not shared with other +projects. Prod's session cookie is host-only on `uploads.sh`, and keeping local dev off that host means a local stack never shares an origin with production. DNS: -`local.buildinternet.dev` + `*.local.buildinternet.dev` are public DNS-only +`local.uploadrouter.dev` + `*.local.uploadrouter.dev` are public DNS-only A records → `127.0.0.1` (never proxy them), so the names resolve to loopback on any machine, worktree prefixes included. `pnpm exec portless hosts sync` is only a fallback for offline work. Register OAuth redirect URIs on the web origin: -`https://uploads.local.buildinternet.dev/api/auth/callback/`. +`https://local.uploadrouter.dev/api/auth/callback/`. Auth has no named subdomain — it is a loopback upstream that web forwards `/api/auth/*` to. `pnpm dev:stack:oauth` is an alias of `pnpm dev:stack`. @@ -134,7 +135,7 @@ smoke test above sets up. This recipe uses the raw loopback stack (`pnpm dev:stack:raw`, or `pnpm dev` + `pnpm dev:auth` + `pnpm dev:web` separately) so thumbnail bytes can come from a loopback `publicBaseUrl`. `pnpm dev:stack` also enables the `dev-session` bypass on -`https://uploads.local.buildinternet.dev`. A few steps here aren't obvious +`https://local.uploadrouter.dev`. A few steps here aren't obvious from the API alone: 1. **Sign in with the `dev-session` bypass, from a page already on diff --git a/docs/ops.md b/docs/ops.md index 6ce51b9f..1bdf74c8 100644 --- a/docs/ops.md +++ b/docs/ops.md @@ -29,6 +29,21 @@ you run `workspace:limits`. KV cache ~60s. Agents: `uploads usage`. +## Domains + +`uploads.sh` is the only canonical domain. Two backup domains live in the same +Cloudflare account. Neither one serves app content. + +| Zone | Use | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `uploadrouter.com` | Alias. Apex and `www` are proxied placeholder `AAAA 100::` records. A zone Single Redirect sends every request to `https://uploads.sh` + path, query kept. | +| `uploadrouter.dev` | Local dev only. `local` and `*.local` are DNS-only `A 127.0.0.1` records for the portless stack ([local-dev.md](local-dev.md#named-local-urls-portless)). | + +The `uploadrouter.com` redirect is a **302**, on purpose. It is a backup +domain, so keep it temporary until there is a decision to make it permanent. +Do not proxy the `uploadrouter.dev` records. The apex stays unassigned until +there is a staging environment to point it at. + ## Dual public hosts (stable vs embed / GitHub Camo) Shared-bucket objects are available on two custom domains of `uploads-default` diff --git a/portless.json b/portless.json index ae683add..fd273f1b 100644 --- a/portless.json +++ b/portless.json @@ -1,9 +1,9 @@ { - "name": "uploads.local.buildinternet", + "name": "local.uploadrouter", "apps": { - "apps/web": { "name": "uploads.local.buildinternet" }, - "apps/api": { "name": "api.uploads.local.buildinternet" }, - "apps/auth": { "name": "auth.uploads.local.buildinternet" }, - "apps/mcp": { "name": "mcp.uploads.local.buildinternet" } + "apps/web": { "name": "local.uploadrouter" }, + "apps/api": { "name": "api.local.uploadrouter" }, + "apps/auth": { "name": "auth.local.uploadrouter" }, + "apps/mcp": { "name": "mcp.local.uploadrouter" } } } diff --git a/scripts/dev-stack-common.mjs b/scripts/dev-stack-common.mjs index 447695c1..ffcf825a 100644 --- a/scripts/dev-stack-common.mjs +++ b/scripts/dev-stack-common.mjs @@ -6,9 +6,9 @@ import { homedir } from "node:os"; import { join } from "node:path"; // Portless (see the `portless` skill) gives WEB a stable named HTTPS origin -// (`https://uploads.local.buildinternet.dev`), the only origin the browser -// ever talks to. Same sibling-repo convention as Either: TLD `dev` plus the -// long name under the owned `local.buildinternet.dev` zone (OAuth providers +// (`https://local.uploadrouter.dev`), the only origin the browser +// ever talks to. TLD `dev` plus the name `local.uploadrouter`, under the +// project's own `uploadrouter.dev` domain (OAuth providers // reject `*.localhost`). Since #731 auth and api are internal upstreams // reached through web's same-origin `/api/auth` + `/api` proxies, so they // need no hostname of their own — they run as plain loopback ports @@ -16,8 +16,8 @@ import { join } from "node:path"; // `PORTLESS=0` falls back to the legacy pinned ports (also the path for the // dev GitHub OAuth app, whose callback is pinned to 127.0.0.1:8788). export const USE_PORTLESS = process.env.PORTLESS !== "0"; -export const PORTLESS_BASE = process.env.PORTLESS_NAME || "uploads.local.buildinternet"; -export const LOCAL_STACK_DEV_HOST = "uploads.local.buildinternet.dev"; +export const PORTLESS_BASE = process.env.PORTLESS_NAME || "local.uploadrouter"; +export const LOCAL_STACK_DEV_HOST = "local.uploadrouter.dev"; const PORTLESS_CA = join(process.env.PORTLESS_STATE_DIR || join(homedir(), ".portless"), "ca.pem"); diff --git a/scripts/dev-stack.mjs b/scripts/dev-stack.mjs index e464e7f0..c2410726 100644 --- a/scripts/dev-stack.mjs +++ b/scripts/dev-stack.mjs @@ -240,7 +240,7 @@ async function main() { if (stopping) return; // The bypass is gated on WEB_ORIGIN's shape (auth's localDemoEnabled). // AUTH_ORIGIN is always a bare loopback port and no longer signals the mode. - // The owned `uploads.local.buildinternet.dev` zone is local (DNS → 127.0.0.1) + // The owned `local.uploadrouter.dev` zone is local (DNS → 127.0.0.1) // and is the default portless origin, so smoke runs there too. const smoke = isLocalDemoWebOrigin(WEB_ORIGIN) ? await runSmoke()