Files
member-console/docs/environment-reference.md
T
cgalo5758 71818de0bd Add setup checklist and empty-state guidance
Implement the ux-first-run change: a state-derived setup checklist on
/operator/setup with a landing region that recedes once required steps
are done, and empty states that distinguish blocked from empty across
operator and member surfaces. Also add production deployment and
environment reference docs, plus a config-key completeness test.
2026-08-23 03:06:11 -05:00

176 lines
14 KiB
Markdown

---
title: "Environment Variable Reference"
audience: [admin, developer]
summary: "Every configuration key member-console reads: purpose, default, required/optional, and its derived MC_ environment variable override."
---
# Environment Variable Reference
member-console reads configuration through [Viper](https://github.com/spf13/viper),
layered in this precedence order (highest wins): command-line flag, `MC_*`
environment variable, `mc-config.yaml` (or the file passed to `--config`),
built-in default. `internal/config/config_reference_test.go` asserts every
configuration key the source code reads is documented on this page.
**Derivation rule:** every key below is written in `mc-config.yaml` as its
kebab-case name (e.g. `db-dsn`). Its environment variable override is
`MC_` followed by that name upper-cased with every `-` replaced by `_` (e.g.
`MC_DB_DSN`). This is a single mechanical rule (`cmd/root.go`:
`viper.SetEnvPrefix("MC")` + `viper.SetEnvKeyReplacer(strings.NewReplacer("-",
"_"))`) applied uniformly; the table below states each key once and omits the
env column's value where it is a pure derivation, but always names the env
form so it can be found by search.
**Secret keys and `-file` variants.** A key marked *secret* also accepts a
`<key>-file` variant (env `MC_<KEY>_FILE`) whose value is a filesystem path;
at boot, member-console reads the file's contents into the key. Setting both
the direct value and the `-file` variant for the same key is a fatal
misconfiguration (the process exits before starting any service). This is
how Docker/Podman secrets and Kubernetes-mounted secrets are wired in without
putting secret material in the environment or the config file — see
[production-deployment.md](production-deployment.md).
## Core
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `port` | `MC_PORT` | Port the HTTP server listens on. | `8080` | Optional |
| `base-url` | `MC_BASE_URL` | Public URL of this console, e.g. `https://console.example.com`. Also the console's sole CSRF trusted origin — see [production-deployment.md](production-deployment.md). | none | Required |
| `env` | `MC_ENV` | `development` or `production`. Controls the CSP `upgrade-insecure-requests` directive, the CSRF cookie's `Secure` flag, and JSON vs. text log formatting. | `development` | Optional |
| `deployment-name` | `MC_DEPLOYMENT_NAME` | Name this deployment presents for itself on the member and operator mastheads, page titles, and OpenGraph tags. | `Member Console` | Optional (rejected if set to blank/whitespace-only) |
| `support-url` | `MC_SUPPORT_URL` | URL shown in error messages for users to get support. | none | Optional |
## Database and session store
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `db-dsn` | `MC_DB_DSN` | PostgreSQL connection string, e.g. `postgres://user:pass@host:5432/dbname?sslmode=disable`. | none | Required |
| `valkey-addr` | `MC_VALKEY_ADDR` | Valkey/Redis address (`host:port`) for server-side session storage. | `localhost:6379` | Required (satisfied by the default unless explicitly cleared) |
## CSRF and identity (OIDC)
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `csrf-secret` (secret) | `MC_CSRF_SECRET` | HMAC key signing the anti-CSRF tokens embedded in every page and form. Must be exactly 32 bytes. | none | Required, unless `csrf-secret-file` is set |
| `csrf-secret-file` | `MC_CSRF_SECRET_FILE` | File path holding `csrf-secret`. | none | Optional |
| `oidc-idp-issuer-url` | `MC_OIDC_IDP_ISSUER_URL` | OIDC issuer URL, e.g. `https://idp.example.com/realms/main`. | none | Required |
| `oidc-sp-client-id` | `MC_OIDC_SP_CLIENT_ID` | OIDC client ID registered for this console. | none | Required |
| `oidc-sp-client-secret` (secret) | `MC_OIDC_SP_CLIENT_SECRET` | OIDC client secret. | none | Not enforced by boot validation; set it (or `-file`) for a confidential OIDC client |
| `oidc-sp-client-secret-file` | `MC_OIDC_SP_CLIENT_SECRET_FILE` | File path holding `oidc-sp-client-secret`. | none | Optional |
See [identity-provider-setup.md](identity-provider-setup.md) for configuring the IdP side of this exchange.
## Temporal
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `temporal-host` | `MC_TEMPORAL_HOST` | Temporal server `host:port`, e.g. `localhost:7233`. Leaving it unset disables Temporal, the worker, integration workflows, and every scheduled sweep below. | none | Optional (functionally required for provisioning and scheduled jobs) |
| `temporal-namespace` | `MC_TEMPORAL_NAMESPACE` | Temporal namespace. | `default` | Required once `temporal-host` is set |
| `temporal-oauth-token-url` | `MC_TEMPORAL_OAUTH_TOKEN_URL` | OAuth2 token endpoint for Temporal client authentication. | none | Optional; if any `temporal-oauth-*` key below is set, all three must be |
| `temporal-oauth-client-id` | `MC_TEMPORAL_OAUTH_CLIENT_ID` | OAuth2 client ID for Temporal authentication. | none | Optional; see above |
| `temporal-oauth-client-secret` (secret) | `MC_TEMPORAL_OAUTH_CLIENT_SECRET` | OAuth2 client secret for Temporal authentication. | none | Optional; see above |
| `temporal-oauth-client-secret-file` | `MC_TEMPORAL_OAUTH_CLIENT_SECRET_FILE` | File path holding `temporal-oauth-client-secret`. | none | Optional |
| `temporal-oauth-scopes` | `MC_TEMPORAL_OAUTH_SCOPES` | OAuth2 scopes for Temporal authentication (comma-separated). | none | Optional |
| `temporal-connect-timeout` | `MC_TEMPORAL_CONNECT_TIMEOUT` | Max time to retry connecting to Temporal at boot before failing. | `90s` | Optional |
See [temporal-authorization-setup.md](temporal-authorization-setup.md) for configuring the IdP side of Temporal's own JWT authorization.
## Custom domains (external domain claims)
Members with the capability can point their own domain at a FedWiki site or
other resource; these keys tune that subsystem. All are optional — leaving
`domains-connect-target` unset disables external domain claims
deployment-wide.
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `domains-connect-target` | `MC_DOMAINS_CONNECT_TARGET` | DNS target members point external custom domains at (CNAME host or A-record IP). Empty disables external domain claims deployment-wide. | none | Optional |
| `domains-ask-fallback-url` | `MC_DOMAINS_ASK_FALLBACK_URL` | Legacy on-demand-TLS answerer consulted for names unknown to the domains registry. Empty refuses unknown names. | none | Optional |
| `domains-claim-window` | `MC_DOMAINS_CLAIM_WINDOW` | How long a member has to publish the TXT challenge before a pending claim expires. | `24h` | Optional |
| `domains-pending-cap` | `MC_DOMAINS_PENDING_CAP` | Concurrent pending external domain verifications allowed per workspace. | `5` | Optional |
| `domains-abandon-budget` | `MC_DOMAINS_ABANDON_BUDGET` | Verifications a workspace may start and abandon under one domain scope, within the abandonment window, before further claims are refused. `0` disables the ledger. | `3` | Optional |
| `domains-abandon-window` | `MC_DOMAINS_ABANDON_WINDOW` | Rolling window abandoned verifications are counted over. | `168h` | Optional |
| `domains-scope-labels` | `MC_DOMAINS_SCOPE_LABELS` | Trailing DNS labels forming the scope abandonments are counted under, so sibling names share one scope. | `2` | Optional |
| `domains-initiation-budget` | `MC_DOMAINS_INITIATION_BUDGET` | New external domain claims allowed per workspace per rolling 24h. `0` disables the cap. | `10` | Optional |
| `domains-expiry-sweep-interval` | `MC_DOMAINS_EXPIRY_SWEEP_INTERVAL` | Interval between the Temporal-backed domain-claim expiry sweep (backstop for claims whose verification workflow is gone). | `15m` | Optional |
## Billing and maintenance sweeps
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `billing-sweep-interval` | `MC_BILLING_SWEEP_INTERVAL` | Interval between billing scheduled-change sweeps (backstop firing path for due subscription changes). | `1h` | Optional |
| `webhook-partition-ensure-interval` | `MC_WEBHOOK_PARTITION_ENSURE_INTERVAL` | Interval between `core.webhook_events` partition-ensure runs, keeping monthly partitions provisioned ahead of need. | `24h` | Optional |
## Stripe (optional — only for paid products)
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `stripe-api-key` (secret) | `MC_STRIPE_API_KEY` | Stripe secret API key. | none | Required together with `stripe-webhook-secret` to enable billing; both empty leaves it disabled |
| `stripe-api-key-file` | `MC_STRIPE_API_KEY_FILE` | File path holding `stripe-api-key`. | none | Optional |
| `stripe-webhook-secret` (secret) | `MC_STRIPE_WEBHOOK_SECRET` | Stripe webhook endpoint signing secret. | none | Required together with `stripe-api-key` |
| `stripe-webhook-secret-file` | `MC_STRIPE_WEBHOOK_SECRET_FILE` | File path holding `stripe-webhook-secret`. | none | Optional |
| `stripe-mode` | `MC_STRIPE_MODE` | `test` or `live`. Controls the operator panel's "view in Stripe" dashboard link URLs. | `test` | Optional |
See [stripe.md](stripe.md) for the webhook endpoint, subscribed events, and catalog sync.
## FedWiki (optional — the FedWiki farm connection)
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `fedwiki-farm-api-url` | `MC_FEDWIKI_FARM_API_URL` | URL of the FedWiki farm's FarmManager API endpoint, e.g. `https://admin.wiki.example.com`. | none | Required together with `fedwiki-admin-token`; both empty leaves the farm connection disabled |
| `fedwiki-admin-token` (secret) | `MC_FEDWIKI_ADMIN_TOKEN` | FarmManager API admin access token. | none | Required together with `fedwiki-farm-api-url` |
| `fedwiki-admin-token-file` | `MC_FEDWIKI_ADMIN_TOKEN_FILE` | File path holding `fedwiki-admin-token`. | none | Optional |
| `fedwiki-allowed-domains` | `MC_FEDWIKI_ALLOWED_DOMAINS` | Domains where users can create FedWiki sites (comma-separated), e.g. `wiki.example.com,wiki2.example.com`. | none | Optional |
| `fedwiki-site-scheme` | `MC_FEDWIKI_SITE_SCHEME` | URL scheme for generated FedWiki site links: `http` or `https`. | `https` | Optional |
| `fedwiki-sync-enabled` | `MC_FEDWIKI_SYNC_ENABLED` | Enable periodic sync of sites from the FedWiki farm. | `false` | Optional |
| `fedwiki-sync-interval` | `MC_FEDWIKI_SYNC_INTERVAL` | Interval between FedWiki site syncs. Inert while sync is disabled. | `1h` | Optional |
| `fedwiki-sync-trigger-immediately` | `MC_FEDWIKI_SYNC_TRIGGER_IMMEDIATELY` | Run a sync immediately at startup, in addition to the interval. | `true` | Optional |
| `fedwiki-swap-cooldown` | `MC_FEDWIKI_SWAP_COOLDOWN` | Cooldown window between FedWiki active-site rotations (swap or archive-then-restore). `0` disables it. | `720h` (30 days) | Optional |
See [fedwiki-setup.md](fedwiki-setup.md) for the FarmManager security stack and local dev setup.
## Discourse (optional)
| Key | Env override | Purpose | Default | Required |
|---|---|---|---|---|
| `discourse-base-url` | `MC_DISCOURSE_BASE_URL` | Base URL of the Discourse instance, e.g. `https://forum.example.com`. | none | Required together with `discourse-api-key`; both empty leaves the integration dormant |
| `discourse-api-key` (secret) | `MC_DISCOURSE_API_KEY` | Discourse admin API key (global key, used with `discourse-api-username`). | none | Required together with `discourse-base-url` |
| `discourse-api-key-file` | `MC_DISCOURSE_API_KEY_FILE` | File path holding `discourse-api-key`. | none | Optional |
| `discourse-api-username` | `MC_DISCOURSE_API_USERNAME` | `Api-Username` header value for Discourse admin API calls. | `system` | Optional |
| `discourse-linkage-mode` | `MC_DISCOURSE_LINKAGE_MODE` | How persons are matched to Discourse users: `email`, `oidc`, or `discourseconnect`. | `oidc` | Optional |
| `discourse-auto-create-users` | `MC_DISCOURSE_AUTO_CREATE_USERS` | Create Discourse accounts for entitled persons with no forum user (`true`/`false`). The default only finds and links existing users. | `false` | Optional |
| `discourse-webhook-secret` (secret) | `MC_DISCOURSE_WEBHOOK_SECRET` | Shared secret verifying Discourse webhook signatures. Leave empty to disable the webhook endpoint. | none | Optional |
| `discourse-webhook-secret-file` | `MC_DISCOURSE_WEBHOOK_SECRET_FILE` | File path holding `discourse-webhook-secret`. | none | Optional |
| `discourse-sync-interval` | `MC_DISCOURSE_SYNC_INTERVAL` | Interval between Discourse group-sync sweeps. | `15m` | Optional |
| `discourse-sync-trigger-immediately` | `MC_DISCOURSE_SYNC_TRIGGER_IMMEDIATELY` | Run a group-sync sweep immediately at startup. | `false` | Optional |
See [discourse-setup.md](discourse-setup.md) for forum-side prerequisites and linkage-mode selection.
## Integration settings overlay
Every key above that is part of an installed integration's declared spec
(FedWiki, Stripe, Discourse) can also be reviewed and overridden after boot
from the operator Integration Settings UI, without touching `mc-config.yaml`
or the environment. An override an operator saves there is stored in
`core.integration_config_overrides` and layered over the environment's value
at the *next* boot (`internal/config.ApplyOverlay`); it is not read live, so
a saved override takes effect on restart. Secret-marked keys are never
overridable from this UI — only their presence is shown, never their value
(`internal/config/validate.go:180`, `internal/config.RequiredKeysUnresolved`).
This page does not restate each integration's key list a second time for
the UI; the keys are the same ones documented above.
## Direct environment reads outside Viper
| Variable | Purpose | Default |
|---|---|---|
| `LOG_LEVEL` | Log verbosity, read directly via `os.Getenv` in `internal/logging/logging.go` (not a Viper/`MC_*` key). Accepts any value `slog.Level.UnmarshalText` parses: `debug`, `info`, `warn`, `error` (case-insensitive). An unparseable value panics at boot. | `info` |
## Test-only environment variables
`TEST_DATABASE_URL` is read directly (`os.Getenv`) by the Go test suite's
DB-backed tests; it is not a member-console application configuration key and
has no `MC_*` form. It, `E2E_DATABASE_URL`, and `POSTGRES_ADMIN_DSN` are part
of the test-stack contract documented in [`test/AGENTS.md`](../test/AGENTS.md)
and [`testing.md`](testing.md), not this reference.