Skip to content

Latest commit

 

History

History
87 lines (82 loc) · 17.9 KB

env-reference.md

File metadata and controls

87 lines (82 loc) · 17.9 KB

Environment reference

Auto-generated by scripts/gen-env-docs.ts. Do not edit by hand — change the zod schema in apps/api/src/config/schema.ts or the comment in .env.example and re-run bun run gen:env-docs. CI verifies the generated file is up to date.

Variable Type Default Required Description
APP_DISPLAY_NAME string Lode Server no Human-readable display name. Shown in HTML title, TOTP issuer, etc.
APP_NAME string lode-server no Application slug — must match /^[a-z][a-z0-9-]*$/. Used as the backup filename prefix, localStorage namespace, and (via VITE_APP_NAME) the frontend build.
APP_URL url — no Public URL used to build OAuth callback URLs. REQUIRED in production: forwarded request headers are not trusted there. Optional in development: falls back to the inbound request's Host header.
AUDIT_RETENTION_DAYS number 0 no Audit retention in days. 0 = keep forever. The schema default is 0 to preserve "never sweep" semantics for the literal value, but production deployments should set a finite value so audit_events does not grow unbounded. 365 (one year) is a reasonable starting point.
BACKUP_EXPORT_MIN_INTERVAL_SECONDS number 300 no Minimum seconds between successful service-token backup exports. Throttles a leaked backup token from being turned into a DOS lever (repeated full-DB reads amplify WAL pressure). 0 disables the gate.
BASE_PATH string — no URL prefix the app is mounted under. Unset (default) means the app is served at root: SPA at "/" and API at "/api". When set, "app", "/app", and "/app/" all normalise to "/app" — SPA at "/app/", API at "/app/api". Match this with the reverse-proxy mount.
CORS_ORIGIN string — no CORS (required in production). Comma-separated list of allowed origins. In development, requests through the dev URL router (nsl) are same-origin and do not need this set.
CREATE_RATE_LIMIT_EXEMPT string — no Resources to leave unthrottled, comma-separated. Names are the singular collection names derived from the route table — group, job, member, resource-group, token, totp, tuple.
CREATE_RATE_LIMIT_PER_HOUR number 600 no
CREATE_RATE_LIMIT_PER_MINUTE number 60 no Per-user caps on content creation. The limiter finds the creating routes itself: a POST whose path also answers GET is creating a member of that collection, so a new module is covered as soon as it mounts. Counted per resource — group, member, token, tuple, ... — so a burst of tuples does not spend the budget for creating groups. The minute window bounds a burst; the hour window bounds the sustained rate a burst limit alone would still allow. Rejected requests keep counting, so sustained abuse escalates out of the minute window into the hour one. The counters live in the rate_limits table rather than in memory, so they survive a restart — and an eviction on runtimes that evict the app when idle. Either at 0 disables that window; both at 0 disables the gate.
CRON_ACTIONS_ENABLED string — no Opt-in list (comma-separated) of cron actions whose spec.defaultEnabled is false. Each entry is matched against an action's spec.name; listed actions are registered and surfaced in the catalog, otherwise they stay unregistered (and jobs referencing them fail with UNKNOWN_ACTION). Actions shipped with defaultEnabled: true (the common case) are always registered regardless of this list. The built-in shell action ships with defaultEnabled: false because it runs sh -c <command> with the API process's UID and has no sandbox — a compromise of one admin account would otherwise pivot to host RCE without an operator opt-in. To enable: CRON_ACTIONS_ENABLED=shell (add more names comma-separated as you ship custom actions that follow the same opt-in pattern).
CRON_ENABLED enum(true,false) false no Cron scheduler gate. When false (default) the Baker timer is NOT started and the built-in log-cleanup default is NOT auto-seeded. Admins can still create / edit / delete cron jobs through the UI and fire manual triggers — they just won't run on schedule until this is set to true and the API is restarted. The UI shows an amber "scheduler is off" banner while the flag is false. See docs/modules/cron.md for the runtime model + shell-action security notes before turning this on. Refused at boot on a runtime without a resident event loop (Cloudflare Workers) — see develop/runtime.md.
DATA_DIR string — no Top-level directory for persistent data (DB, uploads, logs). Anchors the relative paths below. Resolution order mirrors the lode SDK's dataDir() (DATA_DIR > LODE_DIR > ROOT_DIR), but the lode/root dirs are used as a base with a data/ subdir (e.g. ${LODE_DIR}/data); DATA_DIR, when set, is the data dir itself. An absolute DB_PATH / LOG_FILE / FILE_STORAGE_LOCAL_ROOT still overrides individually.
DB_ENCRYPTION enum(true,false) false no Database-at-rest encryption. Defaults to false for a friction-free dev experience. Turn on (DB_ENCRYPTION=true) before any production deploy — the setup flow then derives a master keypair and DEK. See docs/develop/deployment.md and docs/modules/encryption.md.
DB_PATH string data/db/app.db no Database (relative to DATA_DIR, or absolute)
DEFAULT_ADMIN string — no Comma-separated identities that are always admins. A matching login is granted admin on its first login and on every later one, whether or not other admins exist. Entries containing "@" match only an email the IdP marks verified; other entries match the username. The setting grants and never revokes, so demoting a listed admin in the UI lasts only until their next login — remove them from this list instead.
ENABLE_EXPERIMENTAL_DEK_ROTATION enum(true,false) false no Opt-in flag for the experimental DEK-rotation flow. When false (default) the rotation endpoints respond with 501 Not Implemented.
FILE_GC_INTERVAL_SECONDS number 3600 no Sweep interval for the GC. 0 disables the periodic sweep entirely (orphans accumulate; admin runs a manual sweep via the API).
FILE_GC_MODE enum(async,sync) async no GC mode. async (default) — releases only decrement ref_count; a background sweeper deletes the blob + the files row once per FILE_GC_INTERVAL_SECONDS. sync — foreground request also deletes the blob (used by tests and local-only deployments).
FILE_PRESIGN_ENABLED enum(true,false) true no When true and the active driver implements presignDownload, file downloads 302 to a short-lived signed URL instead of streaming through the API. Permission is enforced at signing time via the consumer hook.
FILE_PRESIGN_TTL_SECONDS number 300 no TTL for signed URLs in seconds. Short by design: a leaked URL stays valid only briefly; re-issuing requires the consumer permission hook to pass again.
FILE_STORAGE_DRIVER string local no Active storage backend. Built-in: local. Downstream projects can register additional drivers (e.g. s3, azure-blob) and switch here.
FILE_STORAGE_LOCAL_ROOT string data/uploads/files no On-disk root for the local driver. Resolved against the project root when relative.
HOME_PAGE enum(off,base,root) off no Where the SPA's home page (apps/web/src/app/routes/-home.tsx) lives. off — none; "/" goes to the app's overview (default) base — at ${BASE_PATH}/, following the base path root — at the origin's "/", outside BASE_PATH Without a BASE_PATH, base and root are the same. With root and a BASE_PATH, the home page is the only SPA page outside the prefix.
HOST string 0.0.0.0 no Bind address. 0.0.0.0 is fine inside containers / behind a reverse proxy; restrict to 127.0.0.1 for host-local deployments.
HTTP_ACTION_ALLOW_PRIVATE enum(true,false) false no Allow the http-request cron action to reach private / loopback / link-local destinations (127/8, 10/8, 172.16/12, 192.168/16, 169.254/16, ::1, fc00::/7). Default false to block SSRF-style pivots into cloud metadata endpoints and internal services from a compromised admin session. Enable for legitimate internal pings (e.g. talking to a sidecar on localhost).
HTTP_ACTION_TIMEOUT_SECONDS number 30 no Per-execution timeout (seconds) for the http-request cron action. Fetch is aborted via AbortSignal.timeout after this many seconds.
LOCAL_LOGIN enum(true,false) false no Let admins add users in the admin UI (Users → Add user) and let them sign in with a username and password, so the deployment does not depend on an identity provider. Unlike single-user mode it hides nothing: with OIDC configured the login page offers both. Passwords are stored as PBKDF2 hashes; the username locks after repeated failures; two-step verification applies. An OIDC login never takes over a password account by username — only a verified email the IdP vouches for links the two. Ignored while SINGLE_USER_MODE is on.
LOG_FILE string data/logs/app.log no Path the pino destination writes to when LOG_TO_STDOUT=false. Resolved against the project root when relative. Honours SIGHUP for logrotate hand-off (see docs/develop/operations.md).
LOG_LEVEL enum(debug,info,warn,error) info no Logging
LOG_TO_STDOUT enum(true,false) false no When true, write JSON log lines to stdout instead of LOG_FILE — preferred for container deployments that capture stdout/stderr at the runtime level.
MASTER_PASSWORD_FILE string — no Path to a single-use file containing the master password for unattended unlock. Mode must be 0600. The API reads the file at boot, POSTs to /api/encryption/unlock on loopback, then deletes the file. See docs/develop/operations.md § Sealed-file unlock.
MAX_ATTACHMENTS_PER_RESOURCE number 20 no Per-resource attachment count cap. Defaults to 20.
MAX_UPLOAD_BYTES number 10485760 no Per-file cap (bytes). Defaults to 10 MB. Applies to every upload-capable module.
NODE_ENV enum(development,production,test) development no Deployment lifecycle. development enables hot dev paths; production turns on the stricter boot guards (CORS_ORIGIN + APP_URL required, example-sentinel refusals); test is reserved for the e2e harness.
OAUTH_AUTHORIZE_URL url — no Explicit endpoint overrides — only needed when the IdP does not expose OIDC discovery. Each must be a full HTTPS URL. Authorization endpoint the browser is redirected to to start login.
OAUTH_CLIENT_ID string — no OAuth/OIDC is read from environment variables at runtime, not from the settings database. Use OAUTH_ISSUER for discovery or provide all endpoints. Client id registered at the IdP for this deployment.
OAUTH_CLIENT_SECRET string — no Client secret. Keep out of public configuration management; pull from a secrets store and inject as env var at runtime.
OAUTH_GROUPS_CLAIM string — no Claim carrying the user's group names (userinfo first, then the id_token). When set, every login makes the user's memberships in IdP-managed groups match the claim: missing groups are created as IdP-managed, and the user leaves IdP groups no longer listed. Admin-created (local) groups are never touched, and IdP-managed groups refuse manual member edits and renames. A login whose tokens lack the claim leaves memberships unchanged.
OAUTH_ISSUER url — no OIDC issuer URL — when set the API hits <issuer>/.well-known/openid- configuration and auto-fills the four endpoints below. Production discovery hits are cached next to the DB for boot-time fallback.
OAUTH_PKCE enum(true,false) true no Authorization Code with PKCE. Leave true unless the IdP forbids PKCE.
OAUTH_SCOPES string openid profile email no Space-separated scopes requested at login. Add the scope your IdP gates a groups claim behind (often groups) when using OAUTH_GROUPS_CLAIM.
OAUTH_TOKEN_URL url — no Token endpoint the server posts to to exchange the code for tokens.
OAUTH_USERINFO_URL url — no Userinfo endpoint the server calls with the access token to load claims used to upsert the user row.
OIDC_LOGOUT_URL url — no RP-Initiated Logout endpoint. When set the /account/auth/logout flow redirects the browser here after revoking the local session so the IdP also tears down its SSO cookie.
PORT number 3000 no Port the API listens on. The bundled SPA is served from the same port.
RAW_API_RATE_LIMIT_PER_MINUTE number 120 no Per-client cap on the raw API (${BASE_PATH}/api/raw/*), the bare surface other services integrate with. That surface carries no security headers, CSRF guard, CORS policy or session middleware; this limit is the only protection applied to every route on it. Counted per IP in a one-minute window. 0 = unlimited.
REGISTRY_BLOB_DRIVER enum(local,r2) local no Where asset bytes live. local writes under REGISTRY_BLOB_LOCAL_ROOT (Bun); r2 uses the PACKAGES bucket binding (Cloudflare Workers).
REGISTRY_BLOB_LOCAL_ROOT string data/uploads/packages no On-disk root for the local blob driver.
REGISTRY_MANIFEST_MAX_VERSIONS number 50 no How many published versions a package manifest lists, newest first. Versions a channel points at are always listed.
REGISTRY_MAX_ASSET_BYTES number 524288000 no Largest single asset in bytes. 500 MiB is both the default and the ceiling; a larger value is refused at boot.
REGISTRY_MAX_PACKAGES_PER_USER number 50 no Platform default for how many packages one user may own. An administrator can override it per user.
REGISTRY_MAX_STORAGE_BYTES_PER_USER number 10737418240 no Platform default for the total asset bytes one user may store (10 GiB), counting uploads in flight.
REGISTRY_UPLOAD_SWEEP_INTERVAL_SECONDS number 3600 no How often expired multipart uploads are aborted, in seconds. 0 disables the sweep.
REGISTRY_UPLOAD_TTL_SECONDS number 86400 no Lifetime in seconds of a multipart upload that is never completed.
SERVICE_TOKEN_BACKUP string — no Bearer token authorising backup export over POST /api/backup/export-via-token. Keep distinct from SERVICE_TOKEN_METRICS so a leaked scraper credential cannot also exfiltrate the database.
SERVICE_TOKEN_METRICS string — no Bearer credentials for tooling that cannot use a session cookie. Each scope is independent so a leaked metrics scraper credential cannot also exfiltrate the database. Each value must be ≥ 32 chars; generate with: openssl rand -hex 32 Bearer token authorising scrapes of /api/metrics (read-only, Prometheus).
SESSION_MAX_AGE number 86400 no Session lifetime in seconds; seeded into the settings table on first run.
SHELL_ACTION_TIMEOUT_SECONDS number 300 no Per-execution timeout (seconds) for the shell cron action. Child process is killed via SIGTERM (then SIGKILL after a 5s grace).
SINGLE_USER_EMAIL string — no Optional email used for audit / display (defaults to @local).
SINGLE_USER_MODE enum(true,false) false no Enable to authenticate against a username + password kept in the env instead of an OIDC provider. Registration and password change are not exposed; rotate by editing these variables and restarting the API. When SINGLE_USER_MODE=true the OAuth /login route is disabled and the SPA renders a local form.
SINGLE_USER_NAME string — no Optional display name shown in the UI (defaults to the username).
SINGLE_USER_PASSWORD_HASH string — no Inline alternative when a file is not practical. Every $ in the hash must be backslash-escaped, e.g.
SINGLE_USER_PASSWORD_HASH_FILE string — no Path to a file containing the password hash. Recommended over the inline variable below because Bun's dotenv parser expands $VAR (even inside '...'), so an inline hash needs every $ backslash-escaped. Accepted hash formats (auto-detected by prefix): bcrypt ($2a/2b/2y$...), argon2id ($argon2id$...), PBKDF2-SHA256 (pbkdf2-sha256$...). The file may contain a raw hash, OR a single htpasswd line — user:hash is recognised and the leading user: is stripped. Easiest one-liner: mkdir -p .secret && htpasswd -B -n -b admin 'my-password' > .secret/admin.hash
SINGLE_USER_USERNAME string — no Username for the single-user account. Compared case-insensitively.
TRUST_PROXY enum(true,false) false no When true, honour the rightmost X-Forwarded-For entry (and, as a fallback only, X-Real-IP) for client-IP resolution. Default is false: forwarding headers are ignored and the connection peer IP is used. Only enable behind a sanitising proxy that strips client-supplied forwarding headers.
TRUSTED_PROXY_IPS string — no Comma-separated CIDR allow-list of immediate proxy peer addresses. Forwarding headers are honoured only when the TCP peer matches one of these ranges. Empty (default) means "any peer is trusted". Recommended in production: set this to the load-balancer / ingress subnet.
UPLOADS_TOTAL_BYTES number 0 no Total disk quota (bytes) across all attachment tables. 0 = unlimited. When set, uploads beyond it return 413 QUOTA_EXCEEDED.

Read outside the config schema

These are read directly from the environment, not through loadConfig, so they have no schema type or default.

Variable Read by Description
LODE_DIR apps/api/src/config.ts Set by the lode supervisor to the instance directory it manages; data then defaults to ${LODE_DIR}/data. Not normally set by hand.
ROOT_DIR apps/api/src/root.ts Install root: where the bundled SPA (dist/) and migrations (drizzle/) are found, and the last fallback base for DATA_DIR. Detected from the layout when unset (the packaged artifact's directory, or the monorepo root in development); set it only to run from somewhere else. Read before config is parsed.