Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 51 additions & 95 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@
#
# cp .env.example .env
#
# Every variable up to HUB_STATIC_DIR is required; everything below
# it is optional (see its own comment). Boot validates the whole file
# and reports every missing or malformed value at once — nothing is
# silently defaulted.
# DATABASE_URL, HUB_DATA_DIR, CREDENTIAL_ENCRYPTION_KEY, and
# PRINCIPAL_KEY_ENCRYPTION_KEY are required; everything else is optional
# (see its own comment). `bun run dev` reports the first missing or
# malformed value it hits at boot — nothing is silently defaulted.
#
# The hub itself (apps/hub/src/server.ts) is upstream Interchange's own
# hub shape: it reads DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME
# individually rather than a single connection string, and mints its
# own session auth with no SESSION_SECRET or social-provider config.
# `bun run dev` derives those DB_* values from DATABASE_URL below so
# this file only needs to state one URL.

# Postgres connection URL for your local Postgres (see the README's
# "Running locally" section for a one-time setup). Prefer an explicit
# username (postgres://<user>@localhost:5432/workbench) — environments
# without $USER cannot infer one.
DATABASE_URL=postgres://localhost:5432/workbench

# The single public origin the stack serves from. `workbench setup` and
# `workbench seed` also read this to find the hub, unless HUB_URL is
# set separately.
# The origin the Corbits connections/OAuth mount uses to build callback
# URLs (github/gmail/huggingface connect, MCP OAuth). Unset falls back to
# http://localhost:<PORT>, which is wrong once anything sits in front of
# the hub — set this explicitly behind a reverse proxy (e.g. tailscale
# serve), where BASE_URL is the public origin and PORT is the local port
# the proxy forwards to.
BASE_URL=http://localhost:3000

# Secret used to sign browser sessions; any string of at least 32
# characters. The value below is fine for a laptop that only you can
# reach; generate your own for anything else: openssl rand -hex 32
SESSION_SECRET=insecure-dev-only-session-secret-0000
# PORT=3000

# Directory where the hub keeps durable repo and asset state. Created
# on boot if absent; relative paths resolve against the hub's working
Expand All @@ -36,11 +42,9 @@ HUB_DATA_DIR=.data/hub
# commit can land on the working branch and delete the checkout.
HUB_ALLOW_GIT_INSIDE_WORK_TREE=1

# Directory of built user-interface files the hub serves at BASE_URL.
# Relative paths resolve against the hub's working directory (apps/hub
# under `bun run dev`), so ../web/dist is the web app's build output;
# produce it with `bun run build` before starting the hub.
HUB_STATIC_DIR=../web/dist
# The web app now runs its own dev server (`bun run dev` starts it
# alongside the hub) rather than being served as a static build from the
# hub's own origin — see apps/web/vite.config.ts's /api proxy.

# The administrator identity `bun run dev` seeds for local development:
# the dev script signs this account up on the fresh hub, so a fresh
Expand All @@ -53,24 +57,10 @@ HUB_STATIC_DIR=../web/dist
# feature it configures off; the hub never treats a partially-set group
# as configured — it fails loudly at boot instead.

# Per-IP rate limit on email sign-up. Defaults to 5 sign-ups per 60
# seconds when unset.
# SIGNUP_RATE_LIMIT_WINDOW_SECONDS=60
# SIGNUP_RATE_LIMIT_MAX=5

# Per-account rate limit on email sign-in, keyed on the target email
# rather than client IP (client IP can't be trusted as a sign-in key in
# this deployment — see sign-in-rate-limit.ts). Defaults to 10 attempts
# per 60 seconds when unset — deliberately looser than better-auth's
# built-in 3-per-10-seconds default, which is tight enough that a mistyped
# password can lock an account out mid-window. See CL-6494.
# SIGNIN_RATE_LIMIT_WINDOW_SECONDS=60
# SIGNIN_RATE_LIMIT_MAX=10

# Self-serve signup is ungated: stock Interchange composition carries no
# signup mode, domain allowlist, or email-verification gate, so anyone
# can register (rate-limited only — see SIGNUP_RATE_LIMIT_* above). Gate
# access in the composition that embeds the hub if a deployment needs it.
# Self-serve signup is ungated: stock Interchange composition (createAuth)
# carries no signup mode, domain allowlist, rate limit, or
# email-verification gate, so anyone can register. Gate access in the
# composition that embeds the hub if a deployment needs it.

# Provider API keys are no longer read from the environment: hub boot
# plants nothing, and setting ANTHROPIC_API_KEY or any other provider's
Expand All @@ -91,49 +81,24 @@ HUB_STATIC_DIR=../web/dist
# tooling, never part of a real user's workflow set.
# WORKBENCH_SEED_CATALOG_TEST_WORKFLOWS=

# Behind a reverse proxy (e.g. tailscale serve), BASE_URL is the public
# origin and PORT is the local port the proxy forwards to.
# PORT=3000

# OAuth sign-in. Each provider is an independent pair — set both values
# to turn its button on at the sign-in screen, leave both unset to
# leave it off. Setting only one half of a pair fails the hub at boot,
# naming the missing value, rather than silently disabling the
# provider. Email/password sign-in always stays available regardless.
#
# Google: create an OAuth client at
# https://console.cloud.google.com/apis/credentials, with an authorized
# redirect URI of <BASE_URL>/api/auth/callback/google.
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
#
# GitHub: create an OAuth app at
# https://github.com/settings/developers, with an authorization
# callback URL of <BASE_URL>/api/auth/callback/github.
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=

# The `github` connector stores a personal access token today (Plugins
# page and the in-room connect-github card are PAT-first — CL-6345). Create
# a token at https://github.com/settings/tokens with the `repo` scope and
# paste it in the connect UI; no OAuth App is required for that path.
#
# Optional — reserved for a future GitHub App / hosted one-click connect
# (CL-6343, not the shipped welcome mat). A SEPARATE OAuth App from
# GITHUB_CLIENT_ID above (which only signs people in to Workbench itself).
# If you register one early: create it at https://github.com/settings/developers
# with an authorization callback URL of <BASE_URL>/api/tenants/ — GitHub
# matches any redirect under that prefix; the flow's actual callback would
# be tenant-scoped
# Optional — a hosted one-click GitHub connect (CL-6343, not the shipped
# welcome mat). Create an OAuth App at
# https://github.com/settings/developers with an authorization callback
# URL of <BASE_URL>/api/tenants/ — GitHub matches any redirect under that
# prefix; the flow's actual callback is tenant-scoped
# <BASE_URL>/api/tenants/<tenant-id>/connections/oauth/github/callback
# (CL-6394). Leave both unset for normal PAT-first deploys.
# GITHUB_APP_CLIENT_ID=
# GITHUB_APP_CLIENT_SECRET=

# Gmail connector (read, draft, send — sending always waits for approval):
# create an OAuth client at https://console.cloud.google.com/apis/credentials
# (a separate client from GOOGLE_CLIENT_ID's sign-in app) with the Gmail
# API enabled. Google requires EXACT-match redirect URIs, so register the
# with the Gmail API enabled. Google requires EXACT-match redirect URIs, so register the
# tenant-scoped callback for each tenant that will connect:
# <BASE_URL>/api/tenants/<tenant-id>/connections/oauth/gmail/callback
# The connect asks for the gmail.modify scope with offline access (the
Expand Down Expand Up @@ -232,27 +197,20 @@ HUB_STATIC_DIR=../web/dist
# webhook-trigger signing secrets, and the onboarding OAuth connect state
# (PKCE verifier) sealed between /start and /callback so it survives a hub
# restart in between. 64 hex characters (32 bytes, AES-256): openssl rand
# -hex 32. Required for any real deployment — the hub refuses to boot
# without it. Leave unset and instead set ALLOW_PLAINTEXT_SECRETS below
# for local dev/test.
# CREDENTIAL_ENCRYPTION_KEY=
# -hex 32. Required unconditionally — the hub refuses to boot without it,
# with no dev-only unencrypted fallback. The value below is fine for a
# laptop that only you can reach; generate your own for anything else.
CREDENTIAL_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000

# Seals every principal's per-principal signing key at rest, under its own
# key so it rotates independently of CREDENTIAL_ENCRYPTION_KEY. 64 hex
# characters (32 bytes, AES-256): openssl rand -hex 32. Required for any
# real deployment — the hub refuses to boot without it. Leave unset and
# instead set ALLOW_PLAINTEXT_SECRETS below for local dev/test.
# PRINCIPAL_KEY_ENCRYPTION_KEY=
# characters (32 bytes, AES-256): openssl rand -hex 32. Required
# unconditionally, same as CREDENTIAL_ENCRYPTION_KEY above.
PRINCIPAL_KEY_ENCRYPTION_KEY=1111111111111111111111111111111111111111111111111111111111111111

# Dev/test-only opt-out of the CREDENTIAL_ENCRYPTION_KEY and
# PRINCIPAL_KEY_ENCRYPTION_KEY requirements above:
# set to boot without a key, storing those secrets unencrypted with a boot
# warning. Uncommented here so a fresh checkout's `bun run dev` keeps
# working out of the box. Safe to inherit as-is: the hub refuses to boot
# with this set unless BASE_URL is also a loopback address (localhost /
# 127.0.0.1 / ::1), so it can never take effect against a real deployment
# even if this file is copied verbatim.
ALLOW_PLAINTEXT_SECRETS=1
# Maximum size, in bytes, of a tool-package tarball the package-registry
# PUT endpoint accepts. Defaults to 10 MiB when unset.
# HUB_MAX_TARBALL_BYTES=10485760

# Artifacts plane (@corbits/artifacts) uses DATABASE_URL too — different
# schema only (`artifacts.*` FKs into public.tenant/principal). Nothing to
Expand All @@ -276,17 +234,14 @@ ALLOW_PLAINTEXT_SECRETS=1
# e2b one remote E2B sandbox per allocation. Requires E2B_API_KEY
# and E2B_TEMPLATE (see packages/e2b-sandbox-sidecar/README.md).
#
# SIDECAR_DEFAULT_PROVISIONER picks which listed id exclusive placements
# provision on; optional when exactly one id is listed, required when
# listing more than one.
# Listing more than one id registers all of them; the first listed is the
# default an exclusive placement provisions on.
#
# Adding another backend is three steps, no hub surgery: implement
# SidecarProvisioner in its own package (copy packages/process-provisioner
# — the allocation lifecycle itself comes from @corbits/sandbox-sidecar),
# add its id to SIDECAR_PROVISIONER_IDS in apps/hub/src/config.ts, and add
# a case to buildSidecarProvisioner in apps/hub/src/index.ts.
# Adding another backend is: implement SidecarProvisioner in its own
# package (copy packages/process-provisioner — the allocation lifecycle
# itself comes from @corbits/sandbox-sidecar), then add a case for its id
# to buildSidecarProvisioner in apps/hub/src/server.ts.
# SIDECAR_PROVISIONERS=process
# SIDECAR_DEFAULT_PROVISIONER=process
# DOCKER_PROVISIONER_IMAGE=

# Overrides for the "process" backend, both optional. Unset, it spawns
Expand All @@ -298,9 +253,10 @@ ALLOW_PLAINTEXT_SECRETS=1

# HUB_SIDECAR_WEBSOCKET_URL overrides the ws(s):// address a provisioned
# sidecar dials back to reach this hub. Leave unset to derive it from
# BASE_URL — fine when the hub and its sidecars can reach each other at
# that address, which is always true for the "process" backend (a child
# process on the hub's own host). Set this explicitly whenever
# PORT (ws://127.0.0.1:<PORT>/api/sidecars/ws) — fine when the hub and its
# sidecars can reach each other at that address, which is always true for
# the "process" backend (a child process on the hub's own host). Set this
# explicitly whenever
# SIDECAR_PROVISIONERS includes "docker" or "e2b": a container's own
# "localhost" is itself, not the hub host, and an E2B sandbox is a remote
# machine that needs a publicly reachable wss:// URL or tunnel.
Expand Down
Loading
Loading