Environment Variables¶
Config file alternative: All
KLANGK_*settings below can also be set in a YAML config file viaklangkd --config. The config file is the recommended substrate for production deployments; env vars override file values. See Configuration File.Applying configuration changes: After editing
KLANGK_*env vars or the YAML config file, sendSIGHUPto theklangkdprocess to reload and apply the new values without a full process restart. The HTTP listener and DB stay up; WebSocket clients reconnect automatically. A small set of settings (KLANGKD_PORT,KLANGKD_LISTEN,KLANGKD_DATA_DIR,KLANGKD_STATE_DIR) are bound for the life of the process and require a full restart — SIGHUP logs a warning if one of these changed. An invalid configuration denies the reload (runtime left on last-known-good config, reason logged atERROR). See Process Signals.
$DEVENV_STATE refers to <project root>/.devenv/state — this is where devenv stores runtime data.
For local development, settings live in klangkd.yaml (gitignored; devenv.nix seeds it from klangkd.yaml.devenv on first shell entry). Environment variables override config-file values. Both env vars and config-file values support file: and cmd: prefixes for secret indirection — see Configuration File — file: and cmd: resolution for the full precedence rules and resolution semantics.
Removed in 2.X:
KLANGKD_PROXY_ENGINEis gone — Caddy is the sole reverse-proxy engine. The env var is no longer recognized (silently ignored); remove it from your config.
| Variable | Default | Description |
|---|---|---|
KLANGKD_PROXY_PORT |
deprecated | Deprecated alias for KLANGKD_EGRESS_PORT. If KLANGKD_EGRESS_PORT is unset, this value is used as the egress port and a deprecation warning is logged; if both are set, this is ignored (KLANGKD_EGRESS_PORT wins) and a warning is logged. A future release will stop recognizing it — rename to KLANGKD_EGRESS_PORT. Renamed from KLANGKD_NGINX_PORT; the old KLANGKD_NGINX_PORT name is no longer recognized. |
KLANGKD_EGRESS_PORT |
8995 |
Container-egress port — the proxy listener for container→backend traffic. Three locations are served on it: /llm-proxy/*, /api/v1/browser-delegate, and /ws/egress-sidecar (the network sidecar's blocked-egress event channel). (Two more, /api/v1/auth/verify-workspace-token and @token_auth_failed, live on the same listener but are internal — reachable only as forward_auth subrequests, never callable directly.) Served in both headless and full/browser modes. Must differ from KLANGKD_PORT so ingress vs egress can be firewalled separately. |
KLANGKD_EGRESS_LISTEN |
0.0.0.0 |
Interface the proxy binds for the egress listener ({KLANGKD_EGRESS_LISTEN}:{KLANGKD_EGRESS_PORT}). Defaults to 0.0.0.0 (all interfaces) because the host interface container traffic arrives on is environment-specific (host.containers.internal resolves to a netavark/pasta virtual gateway that isn't bindable; the real interface traffic lands on varies by podman network mode and host IP) and cannot be detected reliably at render time. The all-interfaces bind is not a security hole: every one of the served locations is double-gated — CONTAINER_ACL (a container-source allowlist that returns 403 for any IP outside the container subnet set) plus forward_auth to /api/v1/auth/verify-workspace-token (→ 401 without a valid workspace JWT, even from an allowed container IP). So the network sees 403 on every location; a container IP without a token sees 401. Pin to a specific host IP (e.g. 192.168.1.5) to drop every other interface from the egress surface if your deployment's container-facing IP is stable. The browser listener (KLANGKD_LISTEN/KLANGKD_PORT, default loopback) is a separate server block and is not affected. Accepts a bare bind address only — an IPv4/IPv6 literal or a host name (a bracketed IPv6 literal normalizes to the bare form); a value carrying a port, CIDR suffix, or embedded whitespace fails startup with the setting named, because the value renders verbatim into the Caddyfile bind directive (#3275). A host name must be resolvable and bindable by Caddy: an interface name (eth0) is a valid host name that passes validation, and Caddy refusing to bind it aborts the proxy with Caddy's error instead of an endless restart loop. |
KLANGKD_PORT |
unset | Browser/proxy port (UI, API, WebSocket, hosted apps). Unset ⇒ headless mode (no browser listener rendered; only the container-egress listener on KLANGKD_EGRESS_PORT is served). Set ⇒ full/browser mode. Suggested 8997. |
KLANGKD_LISTEN |
127.0.0.1 |
Address the proxy binds for the browser listener ({KLANGKD_LISTEN}:{KLANGKD_PORT}). Rendered only when KLANGKD_PORT is set (full/browser mode). Default 127.0.0.1 (loopback); set to 0.0.0.0 to expose the browser listener on all interfaces. Accepts a bare bind address only — an IPv4/IPv6 literal or a host name (a bracketed IPv6 literal normalizes to the bare form); the port lives in KLANGKD_PORT (Caddy ignores a port written inside a bind address), and a CIDR suffix or embedded whitespace fails startup with the setting named, because the value renders verbatim into the Caddyfile bind directive (#3275). A host name must be resolvable and bindable by Caddy; an unbindable one aborts the proxy with Caddy's error instead of an endless restart loop. |
KLANGKD_TLS_HOSTNAME |
The public FQDN klangkd serves, arming automatic TLS on the built-in Caddy proxy (#3192). Unset (the default) keeps plain HTTP with auto_https off — the outer-proxy model, byte-identical to before. Set to a public DNS name (e.g. klangk.example.com) and the browser listener is rendered as https://<fqdn>:<KLANGKD_PORT> with a CA-issued, auto-renewed certificate (ACME HTTP-01 / TLS-ALPN via Let's Encrypt + ZeroSSL) and an HTTP→HTTPS redirect on port 80. Requires KLANGKD_PORT set (arming without it refuses to boot), a public DNS record pointing at the host, and ports 80/443 reachable from the internet. Validated at construction: with the default acme issuer it must be a syntactically valid public FQDN (not an IP, URL, or single-label name); with KLANGKD_TLS_ISSUER=internal any RFC 1123 host name or IPv4 literal is accepted. Certificate material persists under <state_dir>/caddy-storage so restarts do not re-issue. Reloadable on SIGHUP (the re-rendered config is pushed to the running Caddy). See HTTPS Hosting. |
|
KLANGKD_TLS_ISSUER |
acme |
How the armed listener's certificate is obtained (#3192). acme (the default, also when unset): ACME issuance (Let's Encrypt + ZeroSSL) for the public FQDN. internal: a self-generated certificate from the built-in proxy's internal CA — the TLS hop behind an outer proxy. Any host name or IPv4 literal arms (klangkd.internal, localhost, 10.0.0.5). The certificate is generated on the host: klangkd contacts no certificate authority and needs no public DNS record. The HTTPS listener binds KLANGKD_LISTEN:KLANGKD_PORT, and that is the only port the TLS setup involves; the automatic HTTP→HTTPS redirect stays off because the outer proxy already sends browsers to HTTPS (an enabled redirect would try to bind port 80). Certificates renew automatically and are stored in <state_dir>/caddy-storage — back that directory up (a lost root means a new CA the outer proxy must re-trust). Setting it without KLANGKD_TLS_HOSTNAME logs a warning (inert). Reloadable on SIGHUP. See HTTPS Hosting. |
KLANGKD_ACME_EMAIL |
The ACME account email (certificate expiry / renewal-failure notices, CA account registration) used when KLANGKD_TLS_HOSTNAME arms automatic TLS (#3192). Rendered as Caddy's global email directive — must be a plain address like ops@example.com (a display-name form is rejected at construction, since the value is passed verbatim into the proxy config). Strongly recommended whenever automatic TLS is armed. Reloadable on SIGHUP. |
|
KLANGKD_SOCKET |
<state_dir>/klangk.sock |
Backend UDS path that klangkd binds. Override when the default overflows the AF_UNIX sun_path limit (≤104 chars). Resolved paths exceeding the limit fail at construction with a diagnostic directing the deployer to shorten KLANGKD_SOCKET or move KLANGKD_STATE_DIR shallower. |
KLANGKD_CADDY_ADMIN_SOCKET |
<state_dir>/caddy-admin.sock |
The admin-API Unix domain socket for the Caddy engine. Same length-limit story as KLANGKD_SOCKET: a deep KLANGKD_STATE_DIR can push the default path over the portable AF_UNIX sun_path bound (≤104 chars), and since the admin UDS is the Caddy engine's only config-delivery path, a too-long path makes the engine unstartable. Override to a shorter absolute path when that happens; resolved paths exceeding the limit fail at construction (regardless of engine) with a diagnostic naming KLANGKD_CADDY_ADMIN_SOCKET. |
KLANGKD_PROXY_BIN |
Path to the proxy (Caddy) executable the renderer spawns; renamed from KLANGKD_NGINX_BIN (the old name is no longer recognized). Unset → shutil.which("caddy"), then /usr/bin/caddy at render time. |
|
KLANGKD_DATA_DIR |
<state_dir>/data |
Database, workspaces, Pi sessions. Defaults to <state_dir>/data (so it tracks the KLANGKD_STATE_DIR default); an explicit value always wins. In dev it resolves to $DEVENV_STATE/klangk/data via klangkd.yaml; the host container sets it to /home/klangk/data. |
KLANGKD_STATE_DIR |
$XDG_STATE_HOME/klangkd |
Runtime state — the UDS socket (<state_dir>/klangk.sock), rendered proxy config, pid file, SSL trust bundle (<state_dir>/ssl/ca-bundle.crt when custom CAs are configured). Defaults to $XDG_STATE_HOME/klangkd (→ ~/.local/state/klangkd when the var is unset, incl. macOS) when no explicit value is supplied; explicit KLANGKD_STATE_DIR / config-file values still win. Construction fails fast only in the genuinely-unconfigured case (neither $XDG_STATE_HOME nor $HOME set), preserving the intent. In dev it resolves to $DEVENV_STATE/klangk via klangkd.yaml; the host container sets it to /tmp/klangk-state. klangkd no longer mutates os.environ to inject a default. The server's XDG subdir is klangkd (the binary name); the CLI's is klangk — different audiences, different trees. |
KLANGKD_CONFIG_DIR |
$XDG_CONFIG_HOME/klangkd |
The config-tree root for user-edited, durable intent — the config-tree analogue of KLANGKD_STATE_DIR. Defaults to $XDG_CONFIG_HOME/klangkd (→ ~/.config/klangkd, incl. macOS when the var is unset) when no explicit value is supplied; KLANGKD_CUSTOMIZE_DIR derives from the resolved config_dir (like KLANGKD_DATA_DIR derives from state_dir). Set this to relocate the config tree with one knob; KLANGKD_CUSTOMIZE_DIR still wins over the derivation. Read at boot and on SIGHUP (reloadable). |
KLANGKD_CUSTOMIZE_DIR |
<config_dir>/custom |
Root directory for deployment customization files (branding, email templates, certs). User-edited, durable intent — so it's config, not state: defaults to <config_dir>/custom (→ ~/.config/klangkd/custom) when unset, deriving from KLANGKD_CONFIG_DIR. Subsystems look for well-known subdirectories (certs/, branding/, email-templates/) here when their per-feature env var is unset; missing subdirectories are silently skipped. An explicit KLANGKD_CUSTOMIZE_DIR always wins. See Customizing a Deployment. |
KLANGKD_TRUSTED_CA_DIR |
Operator-managed approved CA baseline restricting which custom CAs are trusted (#3198). When set (a non-empty path; an explicitly empty value behaves as unset), only CAs whose SHA-256 fingerprint appears among the .pem/.crt certs in this directory are trusted — by the backend's outbound TLS and every workspace container. klangkd stages the approved certs (one canonical PEM per unique parseable cert) under <state_dir>/ssl/approved and that staged copy — not the raw directories — is what both trust scopes consume, so a file the strict parser rejects is never trusted even if a lenient consumer might accept its raw bytes. Each cert dropped into <KLANGKD_CUSTOMIZE_DIR>/certs is audited against the baseline: approved ones log at debug with subject/issuer, non-approved or unparseable ones are refused with a warning and never reach a trust bundle. Lets locked-down deployments (e.g. a DoD-approved CA baseline) enforce a defined CA set. Unset (default) = no restriction (every cert in the customize certs dir is trusted). A missing, unreadable, or cert-less baseline fails closed — no custom CAs trusted, error logged; trust applied earlier in the process is revoked on reload. Read live on every resolution: reloadable on SIGHUP (backend trust re-applies — or revokes — on reload; containers started afterwards pick up the new baseline). See Customizing a Deployment. Also accepted via its field-name form KLANGKD_APPROVED_CA_DIR (same setting; when both are set, KLANGKD_TRUSTED_CA_DIR wins). |
|
KLANGKBUILD_VARIANT |
Identifies a custom build (a variant) of klangk, independent of the upstream klangk version. Baked into version.json as variant by scripts/generate-version.sh at build time; surfaced at GET /api/v1/version and the debug pane's Variant row. Empty/absent = stock klangk (no variant reported, output byte-identical to a non-customized build). See Customizing a Deployment. |
|
KLANGKD_IMAGE_NAME |
klangk-workspace |
Podman image name for workspace containers |
KLANGKD_IMAGE_PULL_POLICY |
never |
Podman --pull policy for workspace containers (never, missing, always, newer). Default never requires the image to exist locally; missing pulls from a registry if not found |
KLANGKD_ALLOWED_IMAGES |
Comma-separated allowlist of container images users may pick when creating a workspace (in addition to KLANGKD_IMAGE_NAME). Unset → only KLANGKD_IMAGE_NAME is allowed. |
|
KLANGKD_USERNS |
keep-id:uid=1000,gid=1000 |
Podman --userns mode for workspace containers. Maps the host user to uid/gid 1000 inside the container so bind-mounted files are owned correctly. |
KLANGKD_PODMAN_BIN |
podman |
Path to the podman binary |
KLANGKD_PORT_RANGE_START |
9000 |
First host port for workspace app allocations. Each workspace gets 5 ports starting from this value. See Hosted Apps. |
KLANGKD_HOSTED_PORTS_PER_WORKSPACE |
5 |
Server-wide ceiling on hosted-app ports per workspace. Set to 0 to disable hosted-app serving entirely — klangkd stops allocating ports and injecting the hosting env into containers, and /hosted/... returns 404. Any other value clamps each workspace down to this cap. See Hosted Apps. |
KLANGKD_ALLOWED_MOUNT_ROOTS |
Comma-separated list of allowed host path prefixes for user bind mounts (e.g., /home,/data). Unset (the default) disables user bind mounts entirely (#3153) — only named volumes may be mounted; a mount with a host-path source is rejected at workspace create/edit and re-validated at container start (#3278). klangkd's own mounts (home, config, SSL, nix) are internal and unaffected. Protected paths are always blocked regardless. Reloadable on SIGHUP. |
|
KLANGKD_DNS_SERVERS |
Comma-separated DNS server IPs for containers (e.g., 100.100.100.100,8.8.8.8 for Tailscale MagicDNS). If unset, containers use podman's default DNS. |
|
KLANGKD_DNS_SEARCH |
Comma-separated DNS search domains for containers (e.g., corp.example,svc.example for short-name resolution). Passed to podman --dns-search; unset → podman's default search behavior. Applies to newly-created containers. |
|
KLANGKD_HOSTING_HOSTNAME |
(auto-derived) | URL authority (host[:port]) for hosted-app and user-facing URLs — the port lives here, it is never synthesized from KLANGKD_EGRESS_PORT (that is internal container wiring, not the public port). Set explicitly in production; behind a real reverse proxy it must carry the public host and port. When unset, klangkd derives the authority from a trusted X-Forwarded-Host, or from the request's Host header when that Host names the deployment's own address — loopback, the KLANGKD_TLS_HOSTNAME name, or the KLANGKD_LISTEN IP-literal address on the browser port; any other Host value falls back to localhost (localhost:<KLANGKD_PORT> when the browser listener is armed), so a client-chosen Host never lands in a reset/verify/invite link or the OIDC redirect (#3276). A deployment whose browsers reach klangkd by any other name (e.g. KLANGKD_LISTEN=0.0.0.0 with a DNS name) must pin this value for correct links. |
KLANGKD_HOSTING_PROTO |
(auto-derived) | Protocol for user-facing app URLs. Auto-derived from X-Forwarded-Proto or defaults to http. |
KLANGKD_HOSTING_BASE_PATH |
(auto-derived) | Base path prefix for user-facing app URLs (e.g., /klangk). Auto-derived from X-Forwarded-Prefix. |
KLANGKD_TRUSTED_PROXY_CIDRS |
127.0.0.1,::1 |
Comma-separated CIDRs/IPs whose connections are trusted to supply X-Forwarded-Host/Proto/Prefix headers. klangk's proxy runs on loopback, so the default works out of the box; requests from peers outside this set have forwarded headers ignored (preventing host poisoning if the backend port is exposed), and the managed Caddy drops the X-Forwarded-Host it would otherwise derive from a client-chosen Host for such peers, so a direct request with a forged Host reaches the backend with no forwarded host at all (#3276). |
KLANGKD_CONTAINER_SUBNETS |
(auto-derived) | Container source IPs/CIDRs (pasta NAT → host's own IPs). Allowlist for the three container endpoints AND denylist for the catch-all location / (deny-by-default from containers). Unset → auto-detect host IPv4s. |
KLANGKD_REJECT_PROXY_HEADERS |
Set to 1 to ignore X-Forwarded-Host/Proto/Prefix headers unconditionally (hard override; back-compat with the old opt-out). |
|
KLANGKD_IDLE_TIMEOUT_SECONDS |
3600 |
Container idle timeout in seconds (check interval auto-computed as timeout/3, clamped 10-60s) |
KLANGKD_QUIESCE_TIMEOUT |
15 |
Seconds the graceful stop (TERM/INT), graceful restart (SIGHUP), and scheduled server stop/recycle wait for in-flight HTTP requests before draining workspaces. Stragglers at expiry are logged (WARNING) and left to finish against the exiting/recycling process. Keep the quiesce + drain budget inside your service manager’s stop deadline — under systemd’s default 90s TimeoutStopSec, values above ~85s are cut off. See Process Signals. |
KLANGKD_LOGIN_LOCKOUT_WINDOW |
300 |
Time window in seconds for counting failed login attempts. |
KLANGKD_LOGIN_LOCKOUT_FAILURES |
5 |
Number of failed login attempts before a lockout. Default 5 (enabled). Set to 0 to disable. |
KLANGKD_LOGIN_LOCKOUT_DURATION |
900 |
Duration of lockout in seconds (only relevant when KLANGKD_LOGIN_LOCKOUT_FAILURES > 0). |
KLANGKD_MAX_SESSIONS_PER_USER |
0 |
Concurrent login-session cap per user. 0 (the default) = no limit. Each login (password, OIDC, verification, reset, invite acceptance) counts as one session; token refresh keeps the same slot. When a new login pushes the user past the cap, the oldest session is revoked via the token blocklist — its next HTTP request gets 401 and its next WebSocket connect is rejected with 4001. Sessions whose token already expired are purged and never count. Reloadable on SIGHUP. |
KLANGKD_SESSION_WORKSTATION_BINDING |
off |
Session workstation binding: replay protection for bearer JWTs. off (the default) = any token holder can use it until expiry. ip = every authenticated HTTP request, token refresh, and WebSocket connect must come from the same network the session was established from (the effective client IP; two IPv6 addresses inside one /64 count as the same). strict = additionally require the same User-Agent. A token presented from a different workstation is rejected (401 / WS close 4001) and its session revoked, with an audit record naming both workstations; the legitimate client shares the token, so it is logged out too and must re-login. Sessions with an unknown recorded IP (pre-feature rows) are never rejected. Reloadable on SIGHUP. |
KLANGKD_STEP_UP_WINDOW_MINUTES |
0 |
Step-up (sudo-mode) reauthentication window in minutes. When armed, privileged admin writes (user/group/invitation management, admin ACL rewrites, server schedules, volume deletes, and takeover-class writes on a workspace you do not own: delete, raw ACL rewrite, ownership transfer, role assignments) are refused with a machine-readable 403 step_up_required until the session's owner confirms their password via POST /api/v1/auth/step-up; the confirmation is stamped on that session's row and honored for this many minutes. Per session (not per user): it survives token refresh, dies with logout/revocation, and never unlocks a second session. The confirmation endpoint has login-grade lockout accounting. OIDC-managed accounts (no klangk password) are exempt; each exempt pass is audit-logged. 0 (the default) disables the gate; 15 is the recommended hardening value. The web client prompts and retries automatically; the CLI prompts once. Reloadable on SIGHUP. |
KLANGKD_WEB_BIND_GRACE_SECONDS |
300 |
The DPoP bind grace window for web-client sessions, in seconds — the backstop behind born-bound web mints. Sessions minted for the web app are born DPoP-bound: the SPA's minting requests carry its public binding JWK (Klangk-Binding-Jwk; an OIDC login URL carries it into the state cookie, and an OIDC web flow arriving without it is refused at the callback unless it explicitly rode none from a key-less build), so the token carries cnf.jkt from mint and no unbound window exists. A token that nonetheless mints unbound carries a signed bind deadline: mint time plus this many seconds. Still unbound past the deadline, the session is refused — every API request, token refresh, bind call, and WebSocket connect answers 401 until the user logs in again, and an established WebSocket (main or consent-decider) closes at the deadline rather than the token's natural expiry. The deadline survives token refresh and bind swaps unchanged (a rotation re-arms the live sockets for the replacement token), so a rotation can never reset it; the web client retries a transiently failed bind every 30 seconds inside the window. CLI/TUI sessions are unmarked and stay unbound indefinitely. 0 drops the deadline (born-bound minting stays on). Reloadable on SIGHUP; the deadline is read at mint time. |
KLANGKD_INACTIVITY_DISABLE_DAYS |
35 |
Dormant-account auto-disable window in days. Accounts (except the system agent and admin-group members) whose newest activity signal — last authenticated API access, last login, or creation — is older than the window are disabled by an hourly sweep; their logins, token refreshes, and authenticated requests then fail with 403 Account disabled and their live WebSocket connections are closed (4001 → client logout) until an admin re-enables them. 0 disables the sweep. Reloadable on SIGHUP. |
KLANGKD_SESSION_IDLE_TIMEOUT_MINUTES |
0 |
Idle session timeout in minutes. Minutes of inactivity — no authenticated HTTP request and no WebSocket frame; a token refresh does not count — after which a login session terminates: the next token refresh is refused with 401 (and the token blocklisted), and a quiet WebSocket is closed by the server (4001 → client logout). admins-group members get the shorter window of this setting and KLANGKD_PRIVILEGED_SESSION_IDLE_TIMEOUT_MINUTES. When armed, access-token lifetimes are capped at the window so an idle client surfaces at its next refresh within the window plus one refresh interval. Any WebSocket message (including the frontend's 60-second heartbeat) keeps a watched session alive. 0 (the default) restores age-only expiry exactly. Reloadable on SIGHUP. |
KLANGKD_PRIVILEGED_SESSION_IDLE_TIMEOUT_MINUTES |
10 |
The privileged (admins-group) idle window in minutes: admins-group members terminate after the lesser of this and KLANGKD_SESSION_IDLE_TIMEOUT_MINUTES. 0 turns the privileged split off (admins then use the general window). Read live, so a SIGHUP reload applies at the next issue/refresh/sweep. Reloadable on SIGHUP. |
KLANGKD_LLM_API_KEY |
Default LLM API key for models that don't specify their own (supports file:/cmd: prefix) |
|
KLANGKD_LLM_MODELS |
Comma-separated list of provider/model:api_base:api_key entries for the in-process LLM router (see LLM proxy) |
|
KLANGKD_JWT_SECRET |
JWT signing secret. A warning is logged at startup if unset or left as the insecure dev default. | |
KLANGKD_AUDIT_HMAC_KEY |
HMAC-SHA256 key used to tag audit rows (container_events, egress_consent, audit_events) at insert time (#3174, #3205). Opt-in: when unset (the default), no tag is computed or stored. There is deliberately no derivation from KLANGKD_JWT_SECRET: that secret ships a known insecure dev default, and audit integrity should not silently ride on it. klangkd only writes tags — verification (re-computing tags against the key) is an external consumer's job, e.g. an off-host audit backup. Rotation: changing the key invalidates every tag written under the old key; export rows before rotating. Reloadable on SIGHUP. |
|
KLANGKD_PREVENT_INSECURE_JWT_SECRET |
Set to 1 to fail at startup if KLANGKD_JWT_SECRET is unset or insecure. Recommended for production. |
|
KLANGKD_ACCESS_TOKEN_HOURS |
24 |
Lifetime in hours for user session JWTs. Tokens are automatically refreshed before expiry. |
KLANGKD_WORKSPACE_TOKEN_HOURS |
24 |
Lifetime in hours for per-workspace JWTs. Tokens are automatically renewed at 80% of their lifetime. |
KLANGKD_DEFAULT_USER |
Auto-seeded admin email on startup | |
KLANGKD_DEFAULT_PASSWORD |
Auto-seeded password on startup (omit to generate random; supports file:/cmd: prefix) |
|
KLANGKD_MIN_PASSWORD_LENGTH |
8 |
Minimum password length |
KLANGKD_PASSWORD_HISTORY_COUNT |
0 |
Number of previous passwords remembered per user (max 24); a new password matching the current or any remembered one is rejected with 400. 0 disables reuse checking. |
KLANGKD_PASSWORD_MIN_CHANGED |
0 |
Minimum number of characters (edit distance, max 72 — substitutions, insertions, and deletions each count) that a self-service password change must alter from the current password. Rejected with 400 otherwise. Enforced only on POST /auth/change-password, where the current password is re-authenticated — forgot-password resets and admin-set passwords never present the old plaintext, so the rule cannot apply there (password-history reuse still does). 0 (the default) disables the gate. Reloadable on SIGHUP. Advertised via /api/v1/config (password_min_changed) so change-password forms pre-check inline. |
KLANGKD_PASSWORD_MIN_AGE_HOURS |
0 |
Minimum password age in hours (#3177). Self-service password changes and forgot-password resets made inside the window are rejected with 400; admin-forced resets bypass it. 0 (the default) disables the check; 24 hours is the recommended hardening value (max 8760). Passwords set before the column existed are dated from account creation. Reloadable on SIGHUP. |
KLANGKD_PASSWORD_MAX_AGE_DAYS |
0 |
Maximum password age in days (#3177). Login, token refresh, and the next authenticated request / WS connect refuse a session for an expired password with a machine-readable 403 (detail.error = "password_expired"); the CLI/TUI then prompt for a new password via POST /auth/change-expired-password. Local password accounts only — OIDC logins are unaffected. 0 (the default) disables expiry; 60 days is the recommended hardening value (max 3650). Reloadable on SIGHUP. |
KLANGKD_FILE_UPLOAD_SIZE_MAX |
524288000 |
Maximum upload size in bytes for file uploads and workspace imports (default 500 MB) |
KLANGKD_WEBSOCKET_MSG_SIZE_MAX |
16777216 |
Maximum WebSocket message size in bytes (default 16 MB). Increase if syncing very large files via rsync over WebSocket. |
KLANGKD_API_RATE_LIMIT |
0 |
Per-client-IP /api/* request budget (fixed 60s window, 429 + Retry-After) enforced in the backend. Default 0 = off (opt-in; 300 is the documented example budget). The client IP resolves through KLANGKD_TRUSTED_PROXY_CIDRS — behind an outer proxy not in that set, all forwarded clients share the proxy's single bucket. Static assets, /ws upgrades, /hosted/*, and /health never count. Reloadable on SIGHUP. |
KLANGKD_FRONTEND_DIR |
(in-package klangk/frontend) |
Directory the built Flutter Web UI is served from. Defaults to the in-package location (klangk/frontend/, shipped in the wheel) so a pip install klangk deployment serves the UI out of the box; source-tree deployments (devenv, host container) set this to the repo's src/frontend/build/web. The UI is mounted only when the directory exists; klangkd logs a warning otherwise. |
KLANGKD_FEATURES_ENABLE |
(unset → manifest defaults) | The deploy's chosen active-feature list (canonical semantics). Comma-separated feature names; any explicit value → exactly that list, nothing implied (no * form). Unset → the manifest's defaults list (the stock known-good set: beep, bobdobbs, browser-fetch, celebrate, git-credential). The frontend reads its sibling features.json for per-feature metadata + the defaults list, and this value (forwarded via /api/config) for the deploy's chosen set; filtering happens in main.dart before feature registration. Distinct from the build-time declaration: "what's compiled in" is build-time; "what's turned on" is deploy-time. When the defaults change upstream, a pinned list doesn't pick up the change — re-pin deliberately. Read at boot and on SIGHUP (reloadable). |
KLANGKD_PASSWORD_REQUIRE_UPPER |
0 |
Minimum number of uppercase characters required in new passwords. 0 (default) = no requirement. |
KLANGKD_PASSWORD_REQUIRE_LOWER |
0 |
Minimum number of lowercase characters required in new passwords. 0 (default) = no requirement. |
KLANGKD_PASSWORD_REQUIRE_DIGIT |
0 |
Minimum number of digits required in new passwords. 0 (default) = no requirement. |
KLANGKD_PASSWORD_REQUIRE_SPECIAL |
0 |
Minimum number of special characters required in new passwords. 0 (default) = no requirement. |
KLANGKD_CORS_ORIGINS |
Comma-separated list of allowed CORS origins (e.g., https://klangk.example.com). If unset, derived from KLANGKD_HOSTING_HOSTNAME/KLANGKD_HOSTING_PROTO, or defaults to http://localhost. Set to * to allow all origins (not recommended for public deployments). |
|
KLANGKD_CSP_CONNECT_ORIGINS |
Comma-separated URLs whose origins the browser site's Content-Security-Policy admits in connect-src — for frontend features that fetch a deploy-configured remote server browser-side (e.g. soliplex's knowledge-base server, fetched from the SPA at its soliplex_url). Each origin the browser may fetch must be listed in advance: the soliplex feature's configured server is covered automatically via its csp_connect_origin feature-config flag, while any additional server (e.g. one added at runtime via the soliplex overlay) needs its origin added to this list before it can be reached. Only the scheme://host[:port] origin of each URL is used; values that are not absolute http(s) URLs are ignored. Applies on the next proxy config render; reloadable on SIGHUP. See csp_connect_origins. |
|
KLANGKWS_FEATURE_GITEA_OAUTH_CLIENT_ID |
Gitea/Forgejo OAuth application client ID — with KLANGKWS_FEATURE_GITEA_OAUTH_REDIRECT_URI, the git-credential shorthand that runs the browser authorization flow (authorization code + PKCE S256) for the Gitea host a workspace clones: git clone https://<gitea>/owner/repo.git opens the authorization popup without a KLANGKWS_FEATURE_OAUTH_PROVIDERS entry, because Gitea's authorize and token endpoints derive from the host being cloned. Register the application on the Gitea instance itself (user Settings → Applications, Confidential off) — one client ID serves the instance it was registered on; several instances, or a Gitea the browser and the containers reach by different names, use KLANGKWS_FEATURE_OAUTH_PROVIDERS. A host whose authentication challenge names other software (git 2.46+ relays it) keeps the PAT dialog, exactly as before. Also settable under features_config: (as gitea_oauth_client_id); env wins per key. See Gitea. |
|
KLANGKWS_FEATURE_GITEA_OAUTH_REDIRECT_URI |
The klangk origin registered as the OAuth redirect URI in the Gitea application (e.g. https://klangk.example.com/) — the second half of the KLANGKWS_FEATURE_GITEA_OAUTH_CLIENT_ID shorthand. The authorization popup returns to this origin, so it is the address the browser uses; it is set explicitly because the bridge URL reaches the backend, not the public frontend origin, and Gitea checks the value against the registered Redirect URI in both the authorization request and the code exchange (the device-flow shorthands carry only a client ID because that flow has no browser round-trip). The shorthand stays inactive while this var is unset: the PAT dialog answers, and GIT_CREDENTIAL_KLANGK_DEBUG=1 prints a line naming the missing pair. Also settable under features_config: (as gitea_oauth_redirect_uri); env wins per key. See Gitea. |
|
KLANGKWS_FEATURE_GITHUB_OAUTH_CLIENT_ID |
GitHub OAuth App client ID for device-flow authentication. Injected into workspace containers by the git-credential feature. When set, git push to github.com triggers the OAuth device flow automatically. No client secret needed. May also be set (durable, deploy-wide) under the features_config: block of the YAML config file (as github_oauth_client_id) — env still wins per key; see Configuration File. See GitHub Authentication. |
|
KLANGKWS_FEATURE_GITLAB_OAUTH_CLIENT_ID |
GitLab OAuth application client ID — the git-credential device-flow shorthand for gitlab.com (same role as KLANGKWS_FEATURE_GITHUB_OAUTH_CLIENT_ID for GitHub). Needs GitLab 17.1+ with the device flow enabled on the app; requests read_repository write_repository and reports username oauth2. Self-hosted GitLab uses KLANGKWS_FEATURE_OAUTH_PROVIDERS (its host is not gitlab.com). Also settable under features_config: (as gitlab_oauth_client_id); env wins per key. See GitHub Authentication. |
|
KLANGKWS_FEATURE_OAUTH_PROVIDERS |
JSON list of OAuth providers for the git-credential feature ([{host, flow, client_id, token_url, scope, username}]) — activates an OAuth flow for any git host (GitLab, Gitea, self-hosted), not just GitHub. flow is device_code (default; requires device_code_url) or authorization_code_pkce (requires authorize_url + redirect_uri; the authorization-code browser flow with PKCE S256, for hosts like Gitea that implement no device flow — the authorize popup opens on first auth and the tab cache refreshes the token headlessly; see Gitea). An entry whose host matches the remote wins over every shorthand — KLANGKWS_FEATURE_GITHUB_OAUTH_CLIENT_ID, KLANGKWS_FEATURE_GITLAB_OAUTH_CLIENT_ID, and the Gitea pair. Host matching ignores case, explicit port, trailing dot, and a www. prefix. scope (omitted when empty) and username (default oauth2) are optional. Malformed JSON disables the whole map (PAT dialog fallback). Also settable under features_config: (as oauth_providers); env wins per key. See GitHub Authentication. |
|
KLANGKWS_FEATURE_SOLIPLEX_URL |
Soliplex RAG server endpoint URL. Declared by the (vendored) soliplex feature (features/soliplex/); resolved server-side and surfaced to the UI as soliplex_url via GET /api/v1/config when the feature is active. Set via the env var, or under the features_config: block as soliplex_url (see Configuration File); env wins per key. Renamed from SOLIPLEX_URL to the KLANGKWS_FEATURE_ namespace. |
|
KLANGKD_DISABLE_REGISTRATION |
Set to 1/true/yes to block new user signups and hide the registration link in the UI (any other value, including empty, leaves registration open) |
|
KLANGKD_DISABLE_INVITES |
Set to 1/true/yes to disable the invitation system (any other value, including empty, leaves invitations enabled) |
|
KLANGKD_INVITE_EXPIRE_HOURS |
72 |
How long invitation links remain valid (hours) |
KLANGKD_ALLOW_AUTOSTART |
Set to 1/true/yes to allow workspaces to be configured for auto-start on server boot. Default: disabled — the auto-start option is hidden in the UI, CLI, and API. See Auto-start. |
|
KLANGKD_ALLOW_SUDO |
true |
Ceiling for per-workspace sudo: permits a workspace to opt in to passwordless sudo (allow_sudo: true in its settings bag) but grants nothing by itself — sudo is off for a workspace unless it opts in. Default: enabled (1/true/yes); set 0/false/no to forbid the opt-in entirely — an explicit deny rule (!ALL) is then written even for opted-in workspaces. Configured at container start via podman exec --user root. Applies to containers started after the change. |
KLANGKD_PER_HANDLE_HOME |
false |
Ceiling for per-workspace per-handle homes: true permits workspaces to choose the per-handle layout (each connecting user gets a private /home/.users/{id} directory with a /home/{handle} symlink); false — the default — gives every workspace the shared klangk home, regardless of the per-workspace per_handle_home value (POST /workspaces / PUT /workspaces/{id}): a stored true is inert while the ceiling is off, clamped at the next connect/start (no DB rewrite, no 400 — the same shape KLANGKD_ALLOW_SUDO got in #3047). With the ceiling on, an omitted create field still stores this flag's value, so an untouched create gets per-handle homes. Reloadable on SIGHUP (applies to containers started after the reload). Hardened deploys (e.g. FIPS hosts that need one auditable home) leave it off. |
KLANGKD_CLASSIFICATION_BANNER |
Deploy-wide default classification marking shown as a persistent banner at the top and bottom of the web workspace page and as a status line in the TUI (marking sensitive/classified output). Free text (e.g. UNCLASSIFIED, CUI, SECRET). A workspace overrides it via classification_banner on POST /workspaces / PUT /workspaces/{id} (klangk create/edit --classification-banner, or the create/edit UIs); workspaces without their own marking inherit this value at display time. Empty/unset (the default) renders no banner and reserves no screen space. Reloadable on SIGHUP. Downloaded/exported files are not marked — the screen banner is the scope. |
|
KLANGKD_DISABLE_TMUX |
Set to 1/true/yes to drop new terminals straight into a plain login shell instead of tmux. Only affects the default per-user terminal; shared/joined terminals always use tmux. |
|
KLANGKD_BROWSER_DELEGATE_ENABLED |
true |
Master switch for the browser-delegate bridge — the workspace-token-gated /api/v1/browser-delegate{,/stream} endpoints that let a container drive the user's browser tab (fetch with the user's cookies, clipboard, feature actions) and read back everything it renders: a workspace-data read channel that bypasses file permissions entirely. Defaults to true (the bridge ships on); set false for hardened deploys to: return 403 from both endpoints, stop registering browser tabs for bridge routing, and stop attaching a browser ID into terminals started after the change — an already-running terminal keeps its previously attached ID (klangk-browser-id still prints it), but its bridge POSTs get the same 403. Also advertises browser_delegate_enabled: false via /api/v1/config so the web UI stops answering bridge requests. Reloadable on SIGHUP (a disable 403s new requests immediately — a stream already past the gate streams to its idle timeout; a re-enable arms the server from the next terminal_start, and browser tabs resume answering after a page reload). See Browser Bridge. |
KLANGKD_BRIDGE_TIMEOUT_SECONDS |
Idle timeout (seconds) for browser-delegate bridge streams (Browser Bridge). Precedence: per-workspace bridge_timeout setting > this deploy default > 30s. Reloadable on SIGHUP. |
|
KLANGKD_HEALTH_CHECK_INTERVAL |
30 |
Seconds between workspace container health checks (Health check). Unset → 30s. Reloadable on SIGHUP. |
KLANGKD_HEALTH_CHECK_TIMEOUT |
10 |
Seconds a single health-check probe may run before it counts as failed (Health check). Unset → 10s. Reloadable on SIGHUP. |
KLANGKD_HEALTH_CHECK_STARTUP_GRACE |
30 |
Grace window (seconds) after container start during which failed health checks don't count toward unhealthy (Health check). Unset → 30s. Reloadable on SIGHUP. |
KLANGKD_NETFILTER_ENABLED |
true |
Master on/off switch for per-workspace egress filtering. Defaults to true. A workspace that declares allowed_domains is filtered via the network sidecar (network_sidecar_image); set false to disable the feature entirely. When disabled, a workspace with allowed_domains fails to start (fail-closed) rather than running unrestricted. This is the operator intent; the /api/v1/config field of the same name is the resolved armed status (intent AND network_sidecar_image set). Reloadable on SIGHUP. See Egress Filtering. |
KLANGKD_NETFILTER_DEFAULT_DOMAINS |
(unset) | Deploy-wide egress allow-list applied to every workspace that doesn't declare its own. A workspace with a non-empty allowed_domains overrides (replaces) this default; one with none inherits it. Unset preserves the per-workspace-only behavior (empty = unrestricted). Comma-separated host / host:port / IPv4 CIDR specs (e.g. 10.0.0.0/8, 10.0.0.0/8:443) (env var) or a YAML list (netfilter_default_domains); validated server-side, and a malformed value aborts startup (a SIGHUP reload with a bad value is denied, leaving the runtime on last-known-good config — reversing the fall-back). Read at boot and on SIGHUP (reloadable). See Egress Filtering. |
KLANGKD_NIX_ENABLED |
false |
Master on/off switch for the per-workspace /nix feature. Defaults to false: the nix toggle is absent from every create/edit surface (web, TUI, CLI — they read the resolved armed status from the authenticated-only /api/v1/config field nix_available), the API rejects a new nix: true opt-in with a clear error (an edit echoing an already-stored value is tolerated), and a workspace start with a stored nix flag proceeds without the /nix mount (logged once at info; re-enabling resumes it — the per-workspace layers persist, and workspace delete still tears them down while off). Set true (together with a nix_seed block, Nix workspaces) to arm the feature. Reloadable on SIGHUP. |
KLANGKD_EGRESS_CONSENT_RETENTION_DAYS |
30 |
Retention window for the egress_consent table: terminal rows (static policy records, expired, revoked, elapsed timed verdicts, stale pendings) older than this many days are deleted by the consent monitor's sweep (at startup, then hourly on a wall-clock deadline — event traffic never postpones it). Verdicts still in effect (forever/tilrestart, or a timed window not yet elapsed) are enforcement state and are never pruned — they leave via workspace deletion or the tilrestart reap. 0 disables retention. Reloadable on SIGHUP (applies on the next sweep). See Egress Filtering. |
KLANGKD_EGRESS_CONSENT_ROW_CAP |
2000 |
Per-workspace cap on total egress_consent rows: when a workspace exceeds it, the oldest prunable rows (never in-effect verdicts, never live pendings) are trimmed down to the cap by the same sweep — belt-and-suspenders against a flood of decided requests outpacing age-based retention. 0 disables the cap. Reloadable on SIGHUP (applies on the next sweep). See Egress Filtering. |
KLANGKD_NETWORK_SIDECAR_IMAGE |
klangk-network-sidecar |
Container image for the FQDN network sidecar: filtered workspaces run two containers sharing a netns — this sidecar (NET_ADMIN, runs the DNS proxy) plus the workspace (--network container:<sidecar>). Set to an empty value to disable egress filtering entirely. Reloadable on SIGHUP. See Egress Filtering. |
KLANGKD_CONTAINER_CPU_LIMIT |
2.0 |
Deploy-wide CPU cap applied to every workspace container via podman --cpus. Non-empty default: a fresh deployment caps each container at 2 CPUs. A finite positive float (number of CPUs, e.g. 1.5 = one and a half cores). Explicitly emptied = no flag = unbounded. A malformed value (non-numeric, non-finite, or <= 0) aborts startup — a safety control that silently disables itself on a typo is worse than none — and a malformed SIGHUP reload is denied, leaving the runtime on the last-good config. Applies to containers started after the change; an existing container keeps its original cgroup limit for the rest of its life. Reloadable on SIGHUP. |
KLANGKD_EGRESS_CONSENT_RATE_LIMIT |
50 |
Interactive-mode consent monitor tuning: cap on pending egress-consent requests per workspace — an anti attention-flood bound against adversarial containers. Reloadable on SIGHUP. See Egress Filtering. |
KLANGKD_NIX_SEED |
(nested) | Seed configuration for the per-workspace /nix feature (Nix workspaces): a nested block with type and path (env form KLANGKD_NIX_SEED__TYPE / KLANGKD_NIX_SEED__PATH, config-file form nix_seed:). Omit the block or leave path unset to disable the feature; nix_enabled must also be true. |
KLANGKD_EGRESS_CONSENT_TIMEOUT |
120 |
How long (seconds) an egress-consent request stays pending before the monitor auto-expires it (DECISION_EXPIRED). The default (120s) matches the kernel's ~127s connect timeout now that consent gates the connection SYN. Reloadable on SIGHUP. See Egress Filtering. |
KLANGKD_CONSENT_DECIDER_TIMEOUT |
45 |
Consent-decider liveness window: a decider (live client that can approve/deny held egress) whose last ping is older than this many seconds is reaped, so an unclean disconnect can't leave a workspace falsely "interactive". Reloadable on SIGHUP. See Egress Filtering. |
KLANGKD_CONTAINER_MEMORY_LIMIT |
8g |
Deploy-wide RAM cap applied to every workspace container via podman --memory. A positive size matching docker/go-units ParseSize grammar: a number with an optional unit suffix — a single base unit b/k/m/g/t/p (case-insensitive) plus an optional trailing b, so 2g, 2gb, 512m, 512mb, 2t, 1024 (bare bytes), 1.5g all pass; the IEC i-forms (kib/gib/…) are not accepted. 0 is rejected (podman treats --memory=0 as “no limit”; unset the var instead). Non-empty default: a fresh deployment caps each container at 8 GB; explicitly emptied = no flag = unbounded. A malformed value aborts startup (and is denied on SIGHUP); the runtime check is a syntax guard only — podman remains the authority on what cgroups can actually apply (no cgroups v2, rootless without delegation, etc.) and fails loudly at workspace-start if it can't honour the value. Applies to containers started after the change. Reloadable on SIGHUP. |
KLANGKD_CONTAINER_PIDS_LIMIT |
16384 |
Deploy-wide process-count cap applied to every workspace container via podman --pids-limit — the best fork-bomb defense for the effort. Non-empty default: a fresh deployment caps each container at 16384 processes. A positive integer (e.g. 512). Explicitly emptied = no flag = unbounded. A malformed value (non-integer, or <= 0) aborts startup (and is denied on SIGHUP). Applies to containers started after the change. Reloadable on SIGHUP. |
KLANGKD_CONTAINER_RESTART_ENABLED |
false |
Opt-in crash recovery: auto-restart a workspace whose container died unexpectedly (OOM kill, non-zero exit, external removal) after an exponential backoff, with a bounded retry count. Death events/logs carry the classified cause either way — an OOM kill names the workspace's effective memory limit. Expected stops (user stop, idle stop, delete, logout) never restart. Default false: recovery stays manual. Reloadable on SIGHUP; a pending restart re-checks the flag when it fires. See Crash recovery. |
KLANGKD_CONTAINER_RESTART_MAX_RETRIES |
5 |
Bound on restart attempts per crash episode. Exhausting the budget leaves the workspace stopped in a visible crash-loop state (surfaced on GET /workspaces/<id>/status as restart.state) instead of spinning forever. The counter resets after the container stays up 10 minutes, or on any user-driven start/stop. Positive integer; a malformed value aborts startup. |
KLANGKD_CONTAINER_TMP_SIZE |
2g |
Per-workspace /tmp tmpfs size via podman --tmpfs /tmp:...,size=<n>. Default 2g preserves the previous hardcoded mount size; a workspace may override it via its settings bag (tmp_size). Applies to containers started after the change. Reloadable on SIGHUP. |
KLANGKD_CONTAINER_EVENTS_RETENTION_DAYS |
90 |
Retention window for the container_events audit table: rows older than this many days are deleted by the retention sweep (at startup, then hourly on a wall-clock deadline) — an upgrade over a bloated table trims on the first sweep. Every row is history, so unlike egress_consent there is no in-effect exemption. 0 disables retention. Reloadable on SIGHUP (applies on the next sweep). |
KLANGKD_CONTAINER_EVENTS_ROW_CAP |
10000 |
Deploy-wide cap on total container_events rows: when the table exceeds it, the oldest rows are trimmed down to the cap keeping the newest (same created_at DESC, id DESC ordering the events API uses), belt-and-suspenders against a flood of lifecycle events outpacing age-based retention. The cap is deploy-wide, not per-workspace — an audit log bounds the whole table. 0 disables the cap. Reloadable on SIGHUP (applies on the next sweep). |
KLANGKD_AUDIT_EVENTS_RETENTION_DAYS |
365 |
Retention window for the audit_events identity/privilege audit table (#3205): rows older than this many days are deleted by the retention sweep (at startup, then hourly). The table records account create/update/delete, group/ACL/workspace-role changes, login/logout/failed-login, session revocation, and the data-level file events (#3257: workspace archive export/import, downloads and text reads, uploads, renames, and deletes through the files API, each with the path and byte size — the export's size is a pre-flight estimate) — every row with actor, target, and the request's source IP, user agent, HTTP method, and Referer (#3255). Login events are lower-volume than container transitions but are where an incident review starts, so the default is longer than container_events'. File events are the highest-frequency class and are bounded by their own row-cap bucket (see KLANGKD_AUDIT_EVENTS_ROW_CAP). 0 disables retention. Reloadable on SIGHUP (applies on the next sweep). |
KLANGKD_AUDIT_EVENTS_ROW_CAP |
100000 |
Deploy-wide cap on audit_events rows, applied per class: unauthenticated login.failed rows (the only class an anonymous caller can mint) and the high-frequency file.* rows (#3257) each get their own bucket, so a spray of failed logins or a burst of file traffic evicts at most its own class — never the account/privilege history. When a bucket exceeds the cap, the oldest rows are trimmed keeping the newest (same ordering the /events/audit API uses) — so the table can hold up to three caps' worth of rows, one per bucket. 0 disables the cap. Reloadable on SIGHUP (applies on the next sweep). |
KLANGKD_AUDIT_FAIL_CLOSED |
false |
Fail-closed container auditing (#3154, security finding): when true, the interactive API container transitions — POST start/stop/restart and delete's stop — write their container_events audit row before acting and refuse the request with a 503 when it cannot be written — never start-then-rollback. Create's eager start is skipped instead (the workspace row is already committed, so it is left created-not-started with a warning — the same shape as a drain/capacity refusal there). WS-connect starts and autonomous transitions (idle timeout, eviction, logout, drain, shutdown sweep, crash teardown, boot reaps, crash-monitor restarts) are never gated: refusing those would keep containers running and lose the record. Default false keeps the best-effort behavior; every audit-write failure is counted either way and surfaced as write_failures on the /audit endpoint (an in-memory counter, zeroed on restart). Reloadable on SIGHUP (applies to transitions started after the reload). |
KLANGKD_AUDIT_FORWARD_URL |
Opt-in native forwarding of the audit records themselves (#3252; STIG SV-222481/482). When set, klangkd ships every new row of the three audit tables — audit_events (identity/privilege events), container_events (container lifecycle), egress_consent (egress verdicts) — to this endpoint as JSON POST bodies ({"records": [...]}, one batch per few seconds, in-order per table, at-least-once; an egress_consent row ships once, at creation — typically as a pending request — and a later decision on the row is an update, not a new row, so it does not forward again). Use an https:// URL for any endpoint off the host — records name users and actions; http:// is accepted for loopback collectors. Validated at startup (a malformed URL aborts boot). A down target delays delivery (rows stay queued, retried with exponential backoff) and surfaces on /audit under forwarding. Delivery is bounded by the table's own retention and row cap: rows past KLANGKD_AUDIT_EVENTS_RETENTION_DAYS/_ROW_CAP (or the container_events/egress_consent equivalents) are pruned even if a down target has not delivered them yet — size the caps to your worst-case outage. Reloadable on SIGHUP. See Logging. Empty (the default) leaves the forwarder off. |
|
KLANGKD_AUDIT_FORWARD_SYSLOG |
Second target family for #3252: an RFC 5424 receiver as tcp://host[:port] or tls://host[:port] (a bare host[:port] means tcp; ports default to 514/6514). Each record ships as one syslog line (facility audit, severity info, the source table as MSGID, the full record as the JSON message, newline-framed per the common TCP convention). tls:// verifies the server certificate against the system trust store (put a private CA in the approved baseline via KLANGKD_TRUSTED_CA_DIR). Both target families may be configured — each receives every record. Keeps its own delivery cursor, so a down URL target does not delay it; same queueing/backoff//audit behavior as the URL target; empty (the default) leaves the syslog channel off. Reloadable on SIGHUP. |
|
KLANGKD_AUDIT_FORWARD_HEADER |
One optional HTTP header sent with every URL-target POST (#3252), as Name: value — the auth a collector typically requires (Splunk HEC: Authorization: Splunk <token>; bearer collectors: Authorization: Bearer <token>). Applies to the URL target only. Validated at startup (a malformed header aborts boot). Reloadable on SIGHUP. Empty (the default) sends no extra header. |
|
KLANGKD_CONTAINER_RESTART_BACKOFF_SECONDS |
5.0 |
Base delay for the restart backoff: attempt n waits base * 2^(n-1) seconds, capped at 60s (5s → 10s → 20s → 40s → 60s …). Positive float; a malformed value aborts startup. Reloadable on SIGHUP. |
KLANGKD_FIPS_MODE |
false |
FIPS enforcement: when on, every workspace container must prove an actively-enforcing OpenSSL FIPS provider at start (distro-agnostic probe — a non-approved digest rejected on the OpenSSL fetch path, or an SHA-2-only fips=yes approved set via the openssl CLI); failure or non-verifiability fails closed (container removed, start refused). The klangkd process's own OpenSSL is probed once at startup: on a control host the result is logged (warning on failure); inside a container (docker host-container deployment) a failed probe aborts the boot. Use with the FIPS workspace image (klangk:build-fips-image) and, when containerized, the FIPS host image (klangk:build-fips-host-image). See FIPS 140-3 Mode. |
KLANGKD_MEMORY_EVICTION_ENABLED |
true |
Host memory-pressure eviction: when availability stays below KLANGKD_MEMORY_EVICTION_THRESHOLD_PERCENT for KLANGKD_MEMORY_EVICTION_SUSTAIN_POLLS consecutive polls, gracefully stop the least-recently-active workspace with no connected clients (one per poll) until availability recovers to the recovery threshold. Workspaces with live clients and workspaces pinned never-stop (idle_timeout 0, e.g. auto-started boot services) are never chosen. Reloadable on SIGHUP. |
KLANGKD_MEMORY_EVICTION_THRESHOLD_PERCENT |
10.0 |
Pressure threshold (percent of total memory) that opens an eviction episode; availability below this for SUSTAIN_POLLS consecutive polls starts evictions. Platform-aware measurement: MemAvailable/MemTotal on Linux, the more-pressured of meminfo and cgroup headroom inside a memory-limited container (Docker -m), vm_stat+sysctl on macOS — capped there by the podman machine's configured memory (podman machine ls, cached 5 minutes), because containers run in that VM and its default 2048 MiB is far below the Mac's RAM. The percent scales with RAM: 10% of a 256 GB server is 25.6 GB (effectively never triggers — fine, the OOM killer is not near), while 10% of a 4 GB dev laptop is ~400 MB and may trigger during heavy local use — tune up or set KLANGKD_MEMORY_EVICTION_ENABLED=false on small dev machines. Positive float; recovery must be >= threshold. Reloadable on SIGHUP. |
KLANGKD_MEMORY_EVICTION_RECOVERY_PERCENT |
15.0 |
Availability an eviction episode must see (percent of total memory) before it ends — the gap to the threshold is the hysteresis that prevents flap-eviction at the boundary. Must be >= the threshold; equal values mean no gap (operator's explicit choice). Reloadable on SIGHUP. |
KLANGKD_MEMORY_EVICTION_SUSTAIN_POLLS |
3 |
Consecutive below-threshold polls before the first eviction; a transient spike never evicts. Integer >= 1. Reloadable on SIGHUP. |
KLANGKD_MEMORY_EVICTION_POLL_INTERVAL |
10.0 |
Seconds between availability measurements; floored at 1s — smaller configured values are clamped. Positive float. Reloadable on SIGHUP. |
KLANGKD_RESOURCE_WATCHDOG_ENABLED |
true |
Resource detection (#3206, #3309): every KLANGKD_RESOURCE_WATCHDOG_POLL_INTERVAL seconds the loop checks four surfaces — statvfs the filesystems holding the data directory (where the audit records live), the state directory, the podman container-storage root, and any KLANGKD_DISK_WATCHDOG_PATHS entries, deduplicated by device; the memory utilization of the machine containers run on (see KLANGKD_MEMORY_WATCHDOG_WARN_PERCENT); CPU pressure (see KLANGKD_CPU_WATCHDOG_WARN_PERCENT); and the audit-write-failure counters. Threshold crossings send resource.disk.* / resource.memory.* / resource.cpu.* notifications to the admin channels (Notifications) on state transitions — usage hovering at a boundary holds its state (hysteresis bands below both thresholds), a metric that stays degraded refreshes its alert once per 5 minutes so a swallowed transition dispatch is late, never lost, and a measurement that cannot be taken is logged at WARNING and skipped, never blocking anything. Turning this off stops all four surfaces (the write sites' own per-failure audit.failure events keep firing). With no notification channel configured the watchdog only logs. The same last-known state drives GET /health (#3308): any warn/critical metric or an audit-write-failure episode in the latest window flips the status to degraded (HTTP stays 200) with a per-metric detail block. Reloadable on SIGHUP. |
KLANGKD_DISK_WATCHDOG_WARN_PERCENT |
75.0 |
Filesystem usage (percent) that enters the warn state — 75% mirrors the ASD STIG storage-capacity warning allocation (SV-222483). Recovery requires usage to fall to 5 percentage points below this threshold (the fixed hysteresis gap), so a boundary-hovering level sends one alert per episode rather than one per poll. Values below 5 are rejected — the gap needs room below the threshold, and a smaller warn would leave the recovery floor beneath 0% where usage can never reach. Must be ≤ the critical threshold. Reloadable on SIGHUP. |
KLANGKD_DISK_WATCHDOG_CRITICAL_PERCENT |
90.0 |
Filesystem usage (percent) that enters the critical state (SV-222668 low-resource alerting). A critical filesystem eases back to warn at 5 percentage points below this threshold (the same hysteresis gap), so usage hovering at the critical boundary holds the critical state. Must be in [KLANGKD_DISK_WATCHDOG_WARN_PERCENT, 100]; equal values mean the warn state is skipped (OK↔critical only). Reloadable on SIGHUP. |
KLANGKD_DISK_WATCHDOG_PATHS |
Extra filesystems to monitor — any path on each filesystem suffices (e.g. a backup mount). Comma-separated env var or YAML list; a path on an already-monitored filesystem (same device) is ignored, as is a path that cannot be statvfs'd (warned once). Empty (the default) monitors the data directory and container storage only. Reloadable on SIGHUP. | |
KLANGKD_MEMORY_WATCHDOG_ENABLED |
true |
Turns the resource watchdog's memory surface on and off (KLANGKD_RESOURCE_WATCHDOG_ENABLED stays the master switch for the whole loop). While off, remembered memory state is dropped, so re-enabling evaluates fresh: memory that filled while the surface was off alerts on the first poll after. Reloadable on SIGHUP. |
KLANGKD_MEMORY_WATCHDOG_WARN_PERCENT |
80.0 |
Memory utilization (percent) of the machine containers run on that enters the warn state. Measured as 100 × (1 − availability) from MemAvailable/MemTotal on a Linux host (pressed by the cgroup limit when klangkd itself runs memory-capped); on macOS the podman machine VM's own /proc/meminfo is read via podman machine ssh, because containers live in that VM — the Mac host's numbers are the wrong machine. Recovery requires utilization to fall 5 percentage points below the threshold (the shared hysteresis gap). Values below 5 are rejected; must be ≤ the critical threshold. Reloadable on SIGHUP. |
KLANGKD_MEMORY_WATCHDOG_CRITICAL_PERCENT |
90.0 |
Memory utilization (percent) that enters the critical state. The default 90 aligns with the eviction loop's availability floor (KLANGKD_MEMORY_EVICTION_THRESHOLD_PERCENT, default 10%): the critical alert fires as eviction starts. A critical reading eases back to warn 5 percentage points below this threshold. Must be in [KLANGKD_MEMORY_WATCHDOG_WARN_PERCENT, 100]. Reloadable on SIGHUP. |
KLANGKD_CPU_WATCHDOG_ENABLED |
true |
Turns the resource watchdog's CPU-pressure surface on and off (KLANGKD_RESOURCE_WATCHDOG_ENABLED stays the master switch). While off, remembered CPU state is dropped, so re-enabling evaluates fresh. Reloadable on SIGHUP. |
KLANGKD_CPU_WATCHDOG_WARN_PERCENT |
30.0 |
CPU pressure (PSI some avg60 percent — the share of time at least one task was stalled on CPU over the last minute) that enters the warn state. PSI is read from /proc/pressure/cpu on a Linux host and inside the podman machine VM on macOS (podman machine ssh); a kernel or VM without PSI disables the CPU check with one logged warning (re-armed if the file appears). avg60 is already a 60-second average, so a single-poll crossing is sustained pressure, not a scheduler spike. Recovery requires the reading to fall 5 percentage points below the threshold. Values below 5 are rejected; must be ≤ the critical threshold. Reloadable on SIGHUP. |
KLANGKD_CPU_WATCHDOG_CRITICAL_PERCENT |
60.0 |
CPU pressure (PSI some avg60 percent) that enters the critical state. Eases back to warn 5 percentage points below this threshold. Must be in [KLANGKD_CPU_WATCHDOG_WARN_PERCENT, 100]. Reloadable on SIGHUP. |
KLANGKD_RESOURCE_WATCHDOG_POLL_INTERVAL |
60.0 |
Seconds between watchdog polls — all four surfaces share the one loop; floored at 1s (smaller configured values are clamped). The first sweep runs at startup, so a host restarted because a resource filled alerts immediately. Positive float. Reloadable on SIGHUP. |
KLANGKD_ADMISSION_MEMORY_ENABLED |
false |
Admission control: before creating a workspace container, check that available host memory fits the workspace's resolved KLANGKD_CONTAINER_MEMORY_LIMIT (per-workspace memory_limit override wins) plus KLANGKD_ADMISSION_MEMORY_MARGIN. A start that does not fit fails fast with a clear 503 / WebSocket error ("host at capacity: 1.2 GB available, workspace wants 4 GB") instead of deferring the failure to the kernel OOM killer. Runs at the single start choke point, so every path is covered (API start/restart, WS connect, create's eager start, boot auto-start, crash-recovery restart); a reconnect to an already-running workspace is never re-admitted. Skipped when no memory limit is configured; fails open (start allowed, one-time warning) when memory cannot be measured. Concurrent sibling starts have their resolved limits subtracted from the measured availability, so N simultaneous starts cannot each fit against the same stale reading. WebSocket refusals carry the machine-readable error code capacity. Caveat: when klangkd itself runs under a cgroup memory limit (Docker -m, a systemd slice MemoryMax), the measurement reads that cgroup's headroom — a slice tighter than the workspaces' needs systematically refuses starts; size the slice or disable the check. Default false because the check is advisory against the limit — with the default 8g limit, small dev/CI hosts would be refused every start; multi-user deployments should enable it with limits sized to the host. Reloadable on SIGHUP. See Workspace Admission. |
KLANGKD_ADMISSION_MEMORY_MARGIN |
1g |
Reserve kept for the server itself (klangkd, the proxy, page cache) when fitting a workspace's memory limit against available host memory. Podman size-string grammar (same as KLANGKD_CONTAINER_MEMORY_LIMIT: 1g, 512m, 1024, 1.5g …); unset/empty = no reserve (fit against the bare limit). Malformed or 0 aborts startup (and is denied on SIGHUP). Reloadable on SIGHUP. |
KLANGKD_MAX_RUNNING_WORKSPACES_PER_USER |
0 |
Per-user cap on concurrently running workspaces, the k8s ResourceQuota analogue: checked at start time; a user at the cap gets a clear "stop a workspace first" 503 / WebSocket error instead of an overloaded host. Counts the owner's workspaces that are running or mid-start/stop (which closes the two-workspaces-starting-at-once race); a workspace being restarted by its owner is not double-counted. 0 (the default) = unlimited. Reloadable on SIGHUP. See Workspace Admission. |
KLANGKD_VOLUME_QUOTA_PER_WORKSPACE |
0 |
Per-workspace cap on instance-managed named volumes (#3153 — volumes are workspace-owned): each volume is a directory tree on the host's storage, so without a cap a workspace's editors can consume unbounded disk via mounts. Enforced at BOTH doors that mint volumes — POST /api/v1/volumes and the workspace-start auto-create of mounted named volumes (mounts: entries) — and a create past the cap returns 429 (start: a clear start error) with a message naming this setting. A per-workspace lock spans count+create, so concurrent creates (API or start) cannot jointly exceed the cap. Counts the workspace's volumes carrying this instance's klangk.instance label and its klangk.workspace-id label; other workspaces' and other instances' volumes never count. 0 (the default) = unlimited, and the create path makes no extra podman call. Reloadable on SIGHUP. |
KLANGKD_OIDC_CONFIG |
Path to OIDC provider config file (YAML or JSON). Enables OIDC authentication when set. See OIDC Configuration. | |
KLANGKD_OIDC_LOGIN_HOOK |
File path to a Python login hook script (e.g., /etc/klangk/login_hook.py). Optionally append :func_name (defaults to on_login). Called on each OIDC login to enforce custom policies. See OIDC Configuration. |
|
KLANGKD_WORKSPACE_CREATED_HOOK |
File path to a Python workspace-created hook script (e.g., /etc/klangk/workspace_created.py). Optionally append :func_name (defaults to on_workspace_created). Called after every workspace creation (create, import, duplicate) to mutate the workspace and/or rewrite its ACL; failures are logged, never fatal. Reloaded on SIGHUP. See Customizing a Deployment. |
|
KLANGKD_AUTH_MODES |
none |
Auth modes: password, oidc, both, or none. Defaults to none (no-login single-user, loopback-bound) — OIDC settings (KLANGKD_OIDC_*) never change the mode; a fresh install "just works" locally with no password. Set explicitly to pin a mode; see Auth Modes. |
KLANGKD_ALLOW_INSECURE_NO_AUTH |
Set to 1 to allow KLANGKD_AUTH_MODES=none on a non-loopback browser bind (KLANGKD_LISTEN). By default none mode refuses to start unless KLANGKD_LISTEN is loopback (IPv4 127.0.0.0/8, IPv6 ::1, or localhost). See Auth Modes. |
|
KLANGKD_SMTP_HOST |
SMTP server hostname (if set, uses SMTP; otherwise uses sendmail) | |
KLANGKD_SMTP_PORT |
587 |
SMTP server port |
KLANGKD_SMTP_USER |
SMTP auth username | |
KLANGKD_SMTP_PASSWORD |
SMTP auth password | |
KLANGKD_SMTP_FROM |
Email sender address (falls back to SMTP_USER, then noreply@localhost) | |
KLANGKD_SMTP_USE_TLS |
true |
Use STARTTLS for SMTP |
KLANGKD_SMTP_REPLY_TO |
Reply-To address on outgoing email; unset uses the From address. | |
KLANGKD_SENDMAIL_PATH |
sendmail |
Path to sendmail binary (used when KLANGKD_SMTP_HOST is not set) |
KLANGKD_EMAIL_TEMPLATES_DIR |
Deprecated — use <KLANGKD_CUSTOMIZE_DIR>/email-templates/ instead. Directory of Jinja2 email template overrides. When unset, falls back to <KLANGKD_CUSTOMIZE_DIR>/email-templates/. See Email Templating. |
|
KLANGKD_ADMIN_NOTIFICATION_EMAILS |
Comma-separated SA/ISSO email addresses receiving security-relevant notifications (#3250): account lifecycle events (user.create, user.register, user.update, user.delete, user.unlock, user.disable, user.enable), credential and email/handle changes, group membership changes, audit-write failures, and memory-capacity start refusals. Delivery uses the same SMTP/sendmail transport as the auth emails (KLANGKD_SMTP_HOST / KLANGKD_SENDMAIL_PATH). Empty (the default) leaves the email channel off. Reloadable on SIGHUP. |
|
KLANGKD_ADMIN_NOTIFICATION_WEBHOOK_URL |
URL of the endpoint that receives one JSON POST per notification (#3250; body: event name, timestamp, actor, target, detail, source IP). Use an https:// URL for any endpoint that leaves the host — the notification body names users and actions. Sends use a short timeout with no retries; a failed delivery is logged at WARNING and never blocks the action it annotates. Empty (the default) leaves the webhook channel off. Reloadable on SIGHUP. |
|
KLANGKD_ADMIN_NOTIFY_EVENTS |
every supported event | Allowlist of event types that trigger notifications, comma-separated (#3250). The default is every supported event; the persistent conditions audit.failure and resource.low notify at most once per 5 minutes — audit.failure per source table (audit_events vs container_events) — so a degraded state alerts once, not on every occurrence. An unknown event name aborts startup (a typo would silently disable a security notification); blanking the variable restores the default allowlist, while a config-file admin_notify_events: [] explicitly turns event notifications off (the channels stay configured). Reloadable on SIGHUP. |
KLANGKD_PRODUCT_NAME |
Klangk |
White-label product name shown in the browser tab title, app-bar logo, and outgoing emails. Supports file:/cmd: prefix. See Customizing a Deployment. |
KLANGKD_LOGIN_BANNER_TITLE |
Title shown on the consent banner page (e.g., company name). If empty, no title is displayed. | |
KLANGKD_LOGO_URL |
URL of the deployment logo shown in the browser UI (empty = built-in mark). See Customizing a Deployment. | |
KLANGKD_BRAND_COLOR |
#E65100 |
Theme accent color for the browser UI (hex form). See Customizing a Deployment. |
KLANGKD_LOGIN_BANNER |
Consent banner text shown before login. Blocks all access until accepted. Supports file:/cmd: prefix. If empty, no banner is shown. |
|
KLANGKD_LOGIN_BANNER_EVERY_VISIT |
false |
When true, the consent banner must be re-accepted on every fresh app load / login (acceptance is tracked for the session only, never persisted). When false (default), acceptance is cached permanently against the banner text hash. See Consent Banner. |
KLANGKD_TERMS_URL |
Terms of Service link rendered on the login/registration screens and in email footers. Plain value (no file:/cmd: resolution — it is public, shown pre-auth). Empty hides it. |
|
KLANGKD_PRIVACY_URL |
Privacy Policy link. Same semantics as KLANGKD_TERMS_URL (plain value, hidden when empty). |
|
KLANGKD_AUP_URL |
Acceptable Use Policy link. Same semantics as KLANGKD_TERMS_URL. |
|
KLANGKD_SUPPORT_URL |
Support/help link shown as a help icon in the app bar (authenticated) and on the auth screens. Plain value; hidden when empty and no KLANGKD_SUPPORT_EMAIL is set. |
|
KLANGKD_SUPPORT_EMAIL |
Support email address; shown as a mailto: when KLANGKD_SUPPORT_URL is unset. Plain value; hidden when empty. |
|
KLANGKD_TERMINAL_BANNER |
Text displayed in every terminal session on shell init. Supports file:/cmd: prefix. If empty (default), no banner is shown. |
|
KLANGKD_WEBSOCKET_DEBUG |
Set to any non-empty value to enable verbose WebSocket message logging on the backend. Useful for diagnosing connection issues. | |
KLANGKD_LOG_LEVEL |
INFO |
Root logger level for the klangkd backend — a level name (DEBUG/INFO/WARNING/ERROR/CRITICAL, any case) or a numeric string. Applied at boot and re-applied on every SIGHUP reload, so the level can be changed without a restart. A malformed value aborts startup (fail-fast). |
KLANGKD_LOG_FORMAT |
text |
Root logger output format for the klangkd backend — text (colored console format) or json (one JSON object per line: ISO-8601 UTC timestamp, level, logger, message, host (the emitting machine's hostname), and instance (the per-data-dir klangk instance id from <data_dir>/instance-id, the id the audit trail's app lifecycle rows will key on (#3329)) — plus exc_info when present) for SIEM/central-log ingestion. Applies to the whole stream — klangk, uvicorn (startup/error/access), and other third-party records alike — and contains no ANSI codes. Applied at boot and re-applied on every SIGHUP reload; a malformed value aborts startup (fail-fast). Lines emitted before settings load (early boot) are always text. |
KLANGKD_LOG_FILE |
Optional path to an always-JSON log sink: when set, every record at or above KLANGKD_LOG_LEVEL is additionally written to this file as one JSON object per line (same payload as KLANGKD_LOG_FORMAT=json), while the console keeps KLANGKD_LOG_FORMAT — so stdout can stay human-readable text while the file feeds a SIEM via rsyslog imfile/fluent-bit. ~ is expanded; relative paths resolve against the process working directory (the systemd unit's). The path is probed for writability at construction (a bad path aborts startup, fail-fast). The sink follows external rotation (logrotate rename) by reopening on inode change. Reloadable on SIGHUP — a path change closes the old sink and opens the new one; a path that breaks between validation and use is logged at warning (or suspends the sink with one warning, leaving the console stream live). |
|
KLANGKD_LOG_FILE_MAX_BYTES |
0 |
Size trigger for in-app rotation of the KLANGKD_LOG_FILE sink: once the file reaches this many bytes it is rolled over to <path>.1 (existing .N shift up; oldest deleted per KLANGKD_LOG_FILE_BACKUP_COUNT). A file may overshoot by one record (checked before each write). 0 (the default) = never self-rotate — external rotation (logrotate/rsyslog) stays the mechanism. With any rotation trigger set the app owns rotation; keep external rotators off the same path. Reloadable on SIGHUP; a negative or non-integer value aborts startup (fail-fast). Only meaningful with KLANGKD_LOG_FILE set. |
KLANGKD_LOG_FILE_ROTATE |
Time trigger for in-app rotation of the KLANGKD_LOG_FILE sink: hourly, daily, weekly, or monthly (case-insensitive), rolled at UTC boundaries (weekly = Monday). Empty (the default) = no time trigger; size and time triggers can be combined (either fires). Reloadable on SIGHUP; anything else aborts startup (fail-fast). Only meaningful with KLANGKD_LOG_FILE set. |
|
KLANGKD_LOG_FILE_BACKUP_COUNT |
3 |
How many rotated <path>.N files to keep when a rotation trigger fires (oldest deleted first). 0 discards the rotated file outright. Reloadable on SIGHUP; a negative value aborts startup (fail-fast). Only meaningful with a rotation trigger set. |
KLANGKD_LOGFIRE_TOKEN |
Pydantic Logfire write token (logfire_token, opt-in; flat YAML key or this env var). While it is unset (or empty), instrumentation stays off — the logfire package is not imported and no Logfire API is probed. Supports the file:/cmd: secret indirection forms. The token is passed to logfire.configure() explicitly, so ambient LOGFIRE_TOKEN env vars and credentials files play no part. Read once at process start; a change applies on the next start. |
|
KLANGKD_LOGFIRE_BASE_URL |
Logfire API base URL for self-hosted instances (logfire_base_url). Unset = the Logfire SDK default (https://logfire-api.pydantic.dev). Read once at process start. |
|
KLANGKD_LOGFIRE_ENVIRONMENT |
Logfire environment tag (e.g., production, staging) — filters traces in the dashboard (logfire_environment). Read once at process start. |
|
KLANGKD_TEST_MODE |
Internal/test-harness flag: when truthy, registration auto-verifies so E2E tests get immediate access. Not for production use. | |
KLANGKD_VERSION_FILE |
Path to a version.json served from GET /api/v1/version and recorded in app.start audit rows. When unset, unreadable, or wrong-shaped, the copy packaged inside the klangk wheel serves instead (set at wheel-build time from git state, #3517); a source checkout without either source reports the dev block. |