22#
33# cp .env.example .env
44#
5- # Every variable up to HUB_STATIC_DIR is required; everything below
6- # it is optional (see its own comment). Boot validates the whole file
7- # and reports every missing or malformed value at once — nothing is
8- # silently defaulted.
5+ # DATABASE_URL, HUB_DATA_DIR, CREDENTIAL_ENCRYPTION_KEY, and
6+ # PRINCIPAL_KEY_ENCRYPTION_KEY are required; everything else is optional
7+ # (see its own comment). `bun run dev` reports the first missing or
8+ # malformed value it hits at boot — nothing is silently defaulted.
9+ #
10+ # The hub itself (apps/hub/src/server.ts) is upstream Interchange's own
11+ # hub shape: it reads DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME
12+ # individually rather than a single connection string, and mints its
13+ # own session auth with no SESSION_SECRET or social-provider config.
14+ # `bun run dev` derives those DB_* values from DATABASE_URL below so
15+ # this file only needs to state one URL.
916
1017# Postgres connection URL for your local Postgres (see the README's
1118# "Running locally" section for a one-time setup). Prefer an explicit
1219# username (postgres://<user>@localhost:5432/workbench) — environments
1320# without $USER cannot infer one.
1421DATABASE_URL = postgres://localhost:5432/workbench
1522
16- # The single public origin the stack serves from. `workbench setup` and
17- # `workbench seed` also read this to find the hub, unless HUB_URL is
18- # set separately.
23+ # The origin the Corbits connections/OAuth mount uses to build callback
24+ # URLs (github/gmail/huggingface connect, MCP OAuth). Unset falls back to
25+ # http://localhost:<PORT>, which is wrong once anything sits in front of
26+ # the hub — set this explicitly behind a reverse proxy (e.g. tailscale
27+ # serve), where BASE_URL is the public origin and PORT is the local port
28+ # the proxy forwards to.
1929BASE_URL = http://localhost:3000
20-
21- # Secret used to sign browser sessions; any string of at least 32
22- # characters. The value below is fine for a laptop that only you can
23- # reach; generate your own for anything else: openssl rand -hex 32
24- SESSION_SECRET = insecure-dev-only-session-secret-0000
30+ # PORT=3000
2531
2632# Directory where the hub keeps durable repo and asset state. Created
2733# on boot if absent; relative paths resolve against the hub's working
@@ -36,11 +42,9 @@ HUB_DATA_DIR=.data/hub
3642# commit can land on the working branch and delete the checkout.
3743HUB_ALLOW_GIT_INSIDE_WORK_TREE = 1
3844
39- # Directory of built user-interface files the hub serves at BASE_URL.
40- # Relative paths resolve against the hub's working directory (apps/hub
41- # under `bun run dev`), so ../web/dist is the web app's build output;
42- # produce it with `bun run build` before starting the hub.
43- HUB_STATIC_DIR = ../web/dist
45+ # The web app now runs its own dev server (`bun run dev` starts it
46+ # alongside the hub) rather than being served as a static build from the
47+ # hub's own origin — see apps/web/vite.config.ts's /api proxy.
4448
4549# The administrator identity `bun run dev` seeds for local development:
4650# the dev script signs this account up on the fresh hub, so a fresh
@@ -53,24 +57,10 @@ HUB_STATIC_DIR=../web/dist
5357# feature it configures off; the hub never treats a partially-set group
5458# as configured — it fails loudly at boot instead.
5559
56- # Per-IP rate limit on email sign-up. Defaults to 5 sign-ups per 60
57- # seconds when unset.
58- # SIGNUP_RATE_LIMIT_WINDOW_SECONDS=60
59- # SIGNUP_RATE_LIMIT_MAX=5
60-
61- # Per-account rate limit on email sign-in, keyed on the target email
62- # rather than client IP (client IP can't be trusted as a sign-in key in
63- # this deployment — see sign-in-rate-limit.ts). Defaults to 10 attempts
64- # per 60 seconds when unset — deliberately looser than better-auth's
65- # built-in 3-per-10-seconds default, which is tight enough that a mistyped
66- # password can lock an account out mid-window. See CL-6494.
67- # SIGNIN_RATE_LIMIT_WINDOW_SECONDS=60
68- # SIGNIN_RATE_LIMIT_MAX=10
69-
70- # Self-serve signup is ungated: stock Interchange composition carries no
71- # signup mode, domain allowlist, or email-verification gate, so anyone
72- # can register (rate-limited only — see SIGNUP_RATE_LIMIT_* above). Gate
73- # access in the composition that embeds the hub if a deployment needs it.
60+ # Self-serve signup is ungated: stock Interchange composition (createAuth)
61+ # carries no signup mode, domain allowlist, rate limit, or
62+ # email-verification gate, so anyone can register. Gate access in the
63+ # composition that embeds the hub if a deployment needs it.
7464
7565# Provider API keys are no longer read from the environment: hub boot
7666# plants nothing, and setting ANTHROPIC_API_KEY or any other provider's
@@ -91,49 +81,24 @@ HUB_STATIC_DIR=../web/dist
9181# tooling, never part of a real user's workflow set.
9282# WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS=
9383
94- # Behind a reverse proxy (e.g. tailscale serve), BASE_URL is the public
95- # origin and PORT is the local port the proxy forwards to.
96- # PORT=3000
97-
98- # OAuth sign-in. Each provider is an independent pair — set both values
99- # to turn its button on at the sign-in screen, leave both unset to
100- # leave it off. Setting only one half of a pair fails the hub at boot,
101- # naming the missing value, rather than silently disabling the
102- # provider. Email/password sign-in always stays available regardless.
103- #
104- # Google: create an OAuth client at
105- # https://console.cloud.google.com/apis/credentials, with an authorized
106- # redirect URI of <BASE_URL>/api/auth/callback/google.
107- # GOOGLE_CLIENT_ID=
108- # GOOGLE_CLIENT_SECRET=
109- #
110- # GitHub: create an OAuth app at
111- # https://github.com/settings/developers, with an authorization
112- # callback URL of <BASE_URL>/api/auth/callback/github.
113- # GITHUB_CLIENT_ID=
114- # GITHUB_CLIENT_SECRET=
115-
11684# The `github` connector stores a personal access token today (Plugins
11785# page and the in-room connect-github card are PAT-first — CL-6345). Create
11886# a token at https://github.com/settings/tokens with the `repo` scope and
11987# paste it in the connect UI; no OAuth App is required for that path.
12088#
121- # Optional — reserved for a future GitHub App / hosted one-click connect
122- # (CL-6343, not the shipped welcome mat). A SEPARATE OAuth App from
123- # GITHUB_CLIENT_ID above (which only signs people in to Workbench itself).
124- # If you register one early: create it at https://github.com/settings/developers
125- # with an authorization callback URL of <BASE_URL>/api/tenants/ — GitHub
126- # matches any redirect under that prefix; the flow's actual callback would
127- # be tenant-scoped
89+ # Optional — a hosted one-click GitHub connect (CL-6343, not the shipped
90+ # welcome mat). Create an OAuth App at
91+ # https://github.com/settings/developers with an authorization callback
92+ # URL of <BASE_URL>/api/tenants/ — GitHub matches any redirect under that
93+ # prefix; the flow's actual callback is tenant-scoped
12894# <BASE_URL>/api/tenants/<tenant-id>/connections/oauth/github/callback
12995# (CL-6394). Leave both unset for normal PAT-first deploys.
13096# GITHUB_APP_CLIENT_ID=
13197# GITHUB_APP_CLIENT_SECRET=
13298
13399# Gmail connector (read, draft, send — sending always waits for approval):
134100# create an OAuth client at https://console.cloud.google.com/apis/credentials
135- # (a separate client from GOOGLE_CLIENT_ID's sign-in app) with the Gmail
136- # API enabled. Google requires EXACT-match redirect URIs, so register the
101+ # with the Gmail API enabled. Google requires EXACT-match redirect URIs, so register the
137102# tenant-scoped callback for each tenant that will connect:
138103# <BASE_URL>/api/tenants/<tenant-id>/connections/oauth/gmail/callback
139104# The connect asks for the gmail.modify scope with offline access (the
@@ -232,27 +197,20 @@ HUB_STATIC_DIR=../web/dist
232197# webhook-trigger signing secrets, and the onboarding OAuth connect state
233198# (PKCE verifier) sealed between /start and /callback so it survives a hub
234199# restart in between. 64 hex characters (32 bytes, AES-256): openssl rand
235- # -hex 32. Required for any real deployment — the hub refuses to boot
236- # without it. Leave unset and instead set ALLOW_PLAINTEXT_SECRETS below
237- # for local dev/test .
238- # CREDENTIAL_ENCRYPTION_KEY=
200+ # -hex 32. Required unconditionally — the hub refuses to boot without it,
201+ # with no dev-only unencrypted fallback. The value below is fine for a
202+ # laptop that only you can reach; generate your own for anything else .
203+ CREDENTIAL_ENCRYPTION_KEY = 0000000000000000000000000000000000000000000000000000000000000000
239204
240205# Seals every principal's per-principal signing key at rest, under its own
241206# key so it rotates independently of CREDENTIAL_ENCRYPTION_KEY. 64 hex
242- # characters (32 bytes, AES-256): openssl rand -hex 32. Required for any
243- # real deployment — the hub refuses to boot without it. Leave unset and
244- # instead set ALLOW_PLAINTEXT_SECRETS below for local dev/test.
245- # PRINCIPAL_KEY_ENCRYPTION_KEY=
207+ # characters (32 bytes, AES-256): openssl rand -hex 32. Required
208+ # unconditionally, same as CREDENTIAL_ENCRYPTION_KEY above.
209+ PRINCIPAL_KEY_ENCRYPTION_KEY = 1111111111111111111111111111111111111111111111111111111111111111
246210
247- # Dev/test-only opt-out of the CREDENTIAL_ENCRYPTION_KEY and
248- # PRINCIPAL_KEY_ENCRYPTION_KEY requirements above:
249- # set to boot without a key, storing those secrets unencrypted with a boot
250- # warning. Uncommented here so a fresh checkout's `bun run dev` keeps
251- # working out of the box. Safe to inherit as-is: the hub refuses to boot
252- # with this set unless BASE_URL is also a loopback address (localhost /
253- # 127.0.0.1 / ::1), so it can never take effect against a real deployment
254- # even if this file is copied verbatim.
255- ALLOW_PLAINTEXT_SECRETS = 1
211+ # Maximum size, in bytes, of a tool-package tarball the package-registry
212+ # PUT endpoint accepts. Defaults to 10 MiB when unset.
213+ # HUB_MAX_TARBALL_BYTES=10485760
256214
257215# Artifacts plane (@corbits/artifacts) uses DATABASE_URL too — different
258216# schema only (`artifacts.*` FKs into public.tenant/principal). Nothing to
@@ -276,17 +234,14 @@ ALLOW_PLAINTEXT_SECRETS=1
276234# e2b one remote E2B sandbox per allocation. Requires E2B_API_KEY
277235# and E2B_TEMPLATE (see packages/e2b-sandbox-sidecar/README.md).
278236#
279- # SIDECAR_DEFAULT_PROVISIONER picks which listed id exclusive placements
280- # provision on; optional when exactly one id is listed, required when
281- # listing more than one.
237+ # Listing more than one id registers all of them; the first listed is the
238+ # default an exclusive placement provisions on.
282239#
283- # Adding another backend is three steps, no hub surgery: implement
284- # SidecarProvisioner in its own package (copy packages/process-provisioner
285- # — the allocation lifecycle itself comes from @corbits/sandbox-sidecar),
286- # add its id to SIDECAR_PROVISIONER_IDS in apps/hub/src/config.ts, and add
287- # a case to buildSidecarProvisioner in apps/hub/src/index.ts.
240+ # Adding another backend is: implement SidecarProvisioner in its own
241+ # package (copy packages/process-provisioner — the allocation lifecycle
242+ # itself comes from @corbits/sandbox-sidecar), then add a case for its id
243+ # to buildSidecarProvisioner in apps/hub/src/server.ts.
288244# SIDECAR_PROVISIONERS=process
289- # SIDECAR_DEFAULT_PROVISIONER=process
290245# DOCKER_PROVISIONER_IMAGE=
291246
292247# Overrides for the "process" backend, both optional. Unset, it spawns
@@ -298,9 +253,10 @@ ALLOW_PLAINTEXT_SECRETS=1
298253
299254# HUB_SIDECAR_WEBSOCKET_URL overrides the ws(s):// address a provisioned
300255# sidecar dials back to reach this hub. Leave unset to derive it from
301- # BASE_URL — fine when the hub and its sidecars can reach each other at
302- # that address, which is always true for the "process" backend (a child
303- # process on the hub's own host). Set this explicitly whenever
256+ # PORT (ws://127.0.0.1:<PORT>/api/sidecars/ws) — fine when the hub and its
257+ # sidecars can reach each other at that address, which is always true for
258+ # the "process" backend (a child process on the hub's own host). Set this
259+ # explicitly whenever
304260# SIDECAR_PROVISIONERS includes "docker" or "e2b": a container's own
305261# "localhost" is itself, not the hub host, and an E2B sandbox is a remote
306262# machine that needs a publicly reachable wss:// URL or tunnel.
0 commit comments