#283. The retention purge (archived sites removed after 30 days) and parking (sites over a lowered plan limit made read-only) ran only inside the farm sync, after its projection, and the sync is off by default, so the default deployment did neither. FedWiki's existing schedule now exists whenever the farm connection is configured, and its one workflow runs, in order: the farm listing and projection, only when fedwiki-sync-enabled is on; the retention purge; parking. One schedule keeps today's order and skips an overlapping tick, so no purge or parking runs beside a projection working from a stale listing. The interval is the existing fedwiki-sync-interval for the whole run; boot refreshes the existing schedule's action and keeps an operator's pause. After a failed projection with the sync on, the purge is skipped (deletion is irreversible and farm-side restores reach the database only through the projection) and parking still runs, since it is reversible and uses local state, as every sync-off deployment already does; the run reports the sync failure as before. Counts go on the existing completion line as structured fields. Parking orders sites by the farm's recorded modification time, falling back to the site's creation time when the sync never recorded one, so it works with the sync off; the member's Keep active swap uses the same order, which changes the site a swap parks when no time was recorded. FedWiki now requires Temporal, because expired archives and lowered limits are work nothing else processes; the shared refusal for the three integrations is shortened. Ride-alongs, listed in the proposal: - A failed entitlement read during parking logs a warning with its pool ("quota reconcile: read entitlement") instead of counting as no entitlement. - An unconfigured farm pauses a leftover schedule even with the sync flag on. - The consequence spec names its manifest and boot registration path. - ReconcileDomains' comment now says Startup waits for a published Temporal connection, not for the sync flag. - fedwiki-swap-cooldown's description reads "How often an organization over its site limit can change which site is active; 0 means any time". Strings (approved): "Sites over the limit become read-only."; "%s needs Temporal: set temporal-host (or leave %s empty to disable it)"; the interval and creation-trigger descriptions ("Interval between FedWiki maintenance runs", "Run FedWiki maintenance when its schedule is created"); the operator panel's "Site maintenance", "Last run", "No run yet", and its tooltip "Syncs with the farm when the sync is on, deletes archived sites after 30 days, and makes sites over a plan's limit read-only." (the day count from ArchiveRetention); the swap-cooldown description above. No schema or privilege change. Upgrading a deployment (not in the product docs, which describe the new behaviour only): 1. Before deploying, count read-only the archived sites already past their 30 days and the pools over a force-reduce limit, from the site rows and the governing policies, not from stored usage. The first run processes that whole backlog, and the purge's deletion is irreversible. 2. If an old sync run is executing, pause the schedule and let the run finish on the old worker, then deploy and resume. If that is skipped and the open old run fails replay, terminate it: the schedule skips overlapping ticks, so a stuck run blocks every later maintenance tick until it is terminated. The next tick runs the new code. Full suite on reset databases, on this commit's tree: 5441 passed, 16 skipped, none failed. make screens against main's baseline: the FedWiki sites page changed (the panel's title, label and tooltip); no other capture this commit touches changed. The FedWiki settings page, which make screens does not capture, was captured by hand at 1280 and 390. Breaks, all CAUGHT (56): - createSchedule identity - createSchedule projection flag - createSchedule interval - createSchedule overlap - createSchedule pause on failure - createSchedule trigger - updateSchedule identity - updateSchedule projection flag - updateSchedule interval - updateSchedule overlap - updateSchedule pause on failure - updateSchedule operator state - describe failure is not absence - create race still updates - startup sync guard - startup flag - startup interval - startup trigger - startup unpauses operator - gate purge on sync - gate quota on sync - purge after failed projection - skip quota after failed projection - retention failure exits before quota - friendly failure becomes workflow error - friendly failure message cleared - purge count log inflated - completion logged after failed sync - projection flag mistaken for completion - bound retries - purge starts before projection completes - quota starts before retention completes - ListActiveSitesByWorkspace nulls last - swap nulls last - ListActiveSitesByWorkspace creation only - swap creation only - ListActiveSitesByPool nulls last - ListActiveSitesByPool creation only - ListForceReducedSitesByPool nulls last - ListForceReducedSitesByPool creation only - entitlement error hidden - entitlement error falls through - FedWiki temporal need TestFedWikiTemporalRequirement - FedWiki temporal need TestIntegrationsWithStoredWorkNeedTemporal - consequence - section title - last run label - no run value - drop maintenance tooltip - success sync counts - completion sync counts - failed projection listing count - effective retry policy - update triggers run - resolve failure still runs quota - literal maintenance retention days
18 KiB
title, audience, summary
| title | audience | summary | ||
|---|---|---|---|---|
| Environment Variable Reference |
|
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,
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.
Type. Every key's value is one of a closed set: string, url
(absolute, http or https, with a host), duration (a Go duration
string, e.g. 1h30m; never negative), int, bool (true/false), list
(comma-separated), or an enum's members joined by |. duration, greater than zero marks a duration key whose own consumer replaces a saved zero
with its default silently, so zero is refused rather than accepted and
ignored (design D6, typed-config-keys). Every declared integration key's
Type column is checked against its declaration by
internal/config/config_reference_test.go; core keys are filled by hand
from internal/config/validate.go's (ValidateStart) checks.
Core
| Key | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
port |
string | MC_PORT |
Port the HTTP server listens on. | 8080 |
Optional |
base-url |
url | MC_BASE_URL |
Public URL of this console, e.g. https://console.example.com; http or https. Its origin is the console's sole cross-origin trusted origin, and its scheme decides the session cookie's Secure flag, the CSP upgrade-insecure-requests directive, and the Strict-Transport-Security header (all on for https, off for http); see production-deployment.md. |
none | Required |
env |
string | MC_ENV |
Environment label. development selects text log formatting; any other value selects JSON. Nothing security-relevant hangs off it. |
development |
Optional |
deployment-name |
string | 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 |
url | MC_SUPPORT_URL |
URL shown in error messages for users to get support. | none | Optional |
Database and session store
| Key | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
db-dsn |
url (postgres) | MC_DB_DSN |
PostgreSQL connection string of the runtime user, the user the console serves from: not a superuser, owns nothing, and holds no role but console_runtime. E.g. postgres://user:pass@host:5432/dbname?sslmode=disable. Use this or db-dsn-file, never both. Treated as a secret: never logged; the boot log names host, port, database and user only. |
none | One of this or db-dsn-file |
db-dsn-file |
path | MC_DB_DSN_FILE |
File holding the runtime user's PostgreSQL DSN, read at startup. | none | One of this or db-dsn |
db-migrate-dsn |
url (postgres) | MC_DB_MIGRATE_DSN |
PostgreSQL connection string of the migration user, the user that owns the schemas. start runs the migrations and grants console_runtime to the runtime user on this connection, then closes it; with the key unset start runs no migrations and refuses a schema that is behind the code. member-console migrate up|down|status use this key and refuse to run without it. Use this or db-migrate-dsn-file, never both. Treated as a secret: never logged; the boot log names host, port, database and user only. |
none | Optional for start; required by migrate |
db-migrate-dsn-file |
path | MC_DB_MIGRATE_DSN_FILE |
File holding the migration user's PostgreSQL DSN, read at startup. | none | Optional |
valkey-addr |
string | MC_VALKEY_ADDR |
Valkey/Redis address (host:port) for server-side session storage. |
localhost:6379 |
Required (satisfied by the default unless explicitly cleared) |
valkey-username |
string | MC_VALKEY_USERNAME |
ACL username for the session store, when it uses one. | none | Optional |
valkey-password |
string | MC_VALKEY_PASSWORD |
Password for the session store. Use this or valkey-password-file, never both. |
none | Required (the store must not be reachable unauthenticated) |
valkey-password-file |
path | MC_VALKEY_PASSWORD_FILE |
File holding the session-store password, read at startup. | none | One of this or valkey-password |
valkey-tls |
bool | MC_VALKEY_TLS |
Connect to the session store over TLS. | false |
Optional |
valkey-tls-skip-verify |
bool | MC_VALKEY_TLS_SKIP_VERIFY |
Do not verify the session store's TLS certificate. Self-signed stores only; logs a warning at every boot. | false |
Optional |
Identity (OIDC)
Cross-origin request protection has no configuration key: it is the server's
origin check, trusting base-url's origin (internal/middleware/csrf.go). A
configuration that still carries csrf-secret boots; the key is not read.
| Key | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
oidc-idp-issuer-url |
url | MC_OIDC_IDP_ISSUER_URL |
OIDC issuer URL, e.g. https://idp.example.com/realms/main. |
none | Required |
oidc-idp-account-url |
url | MC_OIDC_IDP_ACCOUNT_URL |
URL of the identity provider's self-service account console, opened by the shell's Identity and Access control. OpenID discovery defines no such URL, so providers other than Keycloak set it here. | {oidc-idp-issuer-url}/account (Keycloak's shape) |
Optional |
oidc-sp-client-id |
string | MC_OIDC_SP_CLIENT_ID |
OIDC client ID registered for this console. | none | Required |
oidc-sp-client-secret (secret) |
string | 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 |
string | MC_OIDC_SP_CLIENT_SECRET_FILE |
File path holding oidc-sp-client-secret. |
none | Optional |
See identity-provider-setup.md for configuring the IdP side of this exchange.
Temporal
| Key | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
temporal-host |
string | 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. A configured Stripe, Discourse or FedWiki needs it: Stripe and Discourse process stored events; FedWiki must purge expired archives and enforce limits even with sync off, and start refuses the configuration without it. |
none | Required when Stripe, Discourse or FedWiki is configured; otherwise optional (functionally required for provisioning and scheduled jobs) |
temporal-namespace |
string | MC_TEMPORAL_NAMESPACE |
Temporal namespace. | default |
Required once temporal-host is set |
temporal-oauth-token-url |
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 |
string | MC_TEMPORAL_OAUTH_CLIENT_ID |
OAuth2 client ID for Temporal authentication. | none | Optional; see above |
temporal-oauth-client-secret (secret) |
string | MC_TEMPORAL_OAUTH_CLIENT_SECRET |
OAuth2 client secret for Temporal authentication. | none | Optional; see above |
temporal-oauth-client-secret-file |
string | MC_TEMPORAL_OAUTH_CLIENT_SECRET_FILE |
File path holding temporal-oauth-client-secret. |
none | Optional |
temporal-oauth-scopes |
list | MC_TEMPORAL_OAUTH_SCOPES |
OAuth2 scopes for Temporal authentication (comma-separated). | none | Optional |
temporal-connect-timeout |
duration | MC_TEMPORAL_CONNECT_TIMEOUT |
Time after startup from which failed Temporal connection attempts log at Error. The console keeps retrying for the life of the process, so the value sets when the log turns to Error and never stops the retries. | 90s |
Optional |
See 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 | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
domains-connect-target |
string | 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 |
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 |
duration | MC_DOMAINS_CLAIM_WINDOW |
How long a member has to publish the TXT challenge before a pending claim expires. | 24h |
Optional |
domains-pending-cap |
int | MC_DOMAINS_PENDING_CAP |
Concurrent pending external domain verifications allowed per workspace. | 5 |
Optional |
domains-abandon-budget |
int | 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 |
duration | MC_DOMAINS_ABANDON_WINDOW |
Rolling window abandoned verifications are counted over. | 168h |
Optional |
domains-scope-labels |
int | 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 |
int | MC_DOMAINS_INITIATION_BUDGET |
New external domain claims allowed per workspace per rolling 24h. 0 disables the cap. |
10 |
Optional |
domains-expiry-sweep-interval |
duration | 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 | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
billing-sweep-interval |
duration | MC_BILLING_SWEEP_INTERVAL |
Interval between billing scheduled-change sweeps (backstop firing path for due subscription changes). | 1h |
Optional |
webhook-partition-ensure-interval |
duration | 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 | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
stripe-api-key (secret) |
string | 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 |
string | MC_STRIPE_API_KEY_FILE |
File path holding stripe-api-key. |
none | Optional |
stripe-webhook-secret (secret) |
string | MC_STRIPE_WEBHOOK_SECRET |
Stripe webhook endpoint signing secret. | none | Required together with stripe-api-key |
stripe-webhook-secret-file |
string | MC_STRIPE_WEBHOOK_SECRET_FILE |
File path holding stripe-webhook-secret. |
none | Optional |
See stripe.md for the webhook endpoint, subscribed events, and catalog sync.
FedWiki (optional; the FedWiki farm connection)
| Key | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
fedwiki-farm-api-url |
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) |
string | MC_FEDWIKI_ADMIN_TOKEN |
FarmManager API admin access token. | none | Required together with fedwiki-farm-api-url |
fedwiki-admin-token-file |
string | MC_FEDWIKI_ADMIN_TOKEN_FILE |
File path holding fedwiki-admin-token. |
none | Optional |
fedwiki-allowed-domains |
list | 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 |
http|https | MC_FEDWIKI_SITE_SCHEME |
URL scheme for generated FedWiki site links: http or https. |
https |
Optional |
fedwiki-sync-enabled |
bool | MC_FEDWIKI_SYNC_ENABLED |
Enable the farm listing and projection before retention and quota; maintenance runs even when false. | false |
Optional |
fedwiki-sync-interval |
duration, greater than zero | MC_FEDWIKI_SYNC_INTERVAL |
Interval for the whole maintenance run, whether sync is enabled or not; applied at restart. | 1h |
Optional |
fedwiki-sync-trigger-immediately |
bool | MC_FEDWIKI_SYNC_TRIGGER_IMMEDIATELY |
Trigger the whole maintenance run when its schedule is first created, not on updates. | true |
Optional |
fedwiki-swap-cooldown |
duration | MC_FEDWIKI_SWAP_COOLDOWN |
How often an organization over its site limit can change which site is active; 0 means any time | 720h (30 days) |
Optional |
See fedwiki-setup.md for the FarmManager security stack and local dev setup.
Discourse (optional)
| Key | Type | Env override | Purpose | Default | Required |
|---|---|---|---|---|---|
discourse-base-url |
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) |
string | 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 |
string | MC_DISCOURSE_API_KEY_FILE |
File path holding discourse-api-key. |
none | Optional |
discourse-api-username |
string | MC_DISCOURSE_API_USERNAME |
Api-Username header value for Discourse admin API calls. |
system |
Optional |
discourse-linkage-mode |
email|oidc|discourseconnect | MC_DISCOURSE_LINKAGE_MODE |
How persons are matched to Discourse users: email, oidc, or discourseconnect. |
oidc |
Optional |
discourse-auto-create-users |
false|true | 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) |
string | MC_DISCOURSE_WEBHOOK_SECRET |
Shared secret verifying Discourse webhook signatures. Leave empty to disable the webhook endpoint. | none | Optional |
discourse-webhook-secret-file |
string | MC_DISCOURSE_WEBHOOK_SECRET_FILE |
File path holding discourse-webhook-secret. |
none | Optional |
discourse-sync-interval |
duration, greater than zero | MC_DISCOURSE_SYNC_INTERVAL |
Interval between Discourse group-sync sweeps. | 15m |
Optional |
discourse-sync-trigger-immediately |
bool | MC_DISCOURSE_SYNC_TRIGGER_IMMEDIATELY |
Run a group-sync sweep immediately once the console connects to Temporal. | false |
Optional |
See 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. The overlay is read once, during
single-threaded boot, so an override that no longer parses has no running
value to fall back on and boot fails, naming the key. PostgreSQL's split,
fatal at start and ignored with a log entry at reload, is the rule to copy
if the overlay ever gains a reload path. 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 | Type | Purpose | Default |
|---|---|---|---|
LOG_LEVEL |
debug|info|warn|error | Log verbosity, read directly via os.Getenv in internal/logging/logging.go (not a Viper/MC_* key). Accepts any value slog.Level.UnmarshalText parses (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
and testing.md, not this reference.