From 5f5e26a8d4d2d530ab2f2e3b981a708e70294317 Mon Sep 17 00:00:00 2001 From: Hampus Date: Wed, 14 Oct 2026 17:14:35 +0200 Subject: [PATCH] fix(config): default self-hosted instances to production (#3470) --- .../content/docs/operator/configuration.mdx | 2 +- packages/config/src/ConfigLoader.ts | 12 +++++++++- .../config/src/__tests__/ConfigLoader.test.ts | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/fluxer_docs/src/content/docs/operator/configuration.mdx b/fluxer_docs/src/content/docs/operator/configuration.mdx index b988441d0..9f301de34 100644 --- a/fluxer_docs/src/content/docs/operator/configuration.mdx +++ b/fluxer_docs/src/content/docs/operator/configuration.mdx @@ -1150,7 +1150,7 @@ Set by Docker. Node identity in metrics and logs. #### `FLUXER_ENV` -Default `development`. The runtime mode. `development`, `production`, or `test`. Compose pins `production`, which gates Postgres validation and the admin cookie flags. On a self-hosted instance, `production` also enforces the [setup token](#setup-token). +Default `production` when `FLUXER_SELF_HOSTED` is `true`, otherwise `development`. The runtime mode. `development`, `production`, or `test`. Compose pins `production`, which gates Postgres validation and the admin cookie flags. On a self-hosted instance, `production` also enforces the [setup token](#setup-token). #### `FLUXER_SELF_HOSTED` diff --git a/packages/config/src/ConfigLoader.ts b/packages/config/src/ConfigLoader.ts index 8890d850c..33e22f5b2 100644 --- a/packages/config/src/ConfigLoader.ts +++ b/packages/config/src/ConfigLoader.ts @@ -665,6 +665,13 @@ function normalizeConfig(config: MasterConfig): MasterConfig { return config; } +function applySelfHostedEnvDefault(config: MasterConfig, explicitEnv: string | undefined): MasterConfig { + if (explicitEnv !== undefined || !config.instance.self_hosted) { + return config; + } + return {...config, env: 'production'}; +} + function applyPublicOrigin(config: MasterConfig): MasterConfig { const raw = config.domain.public_origin; if (raw.trim().length === 0 && !/\p{Cc}/u.test(raw)) { @@ -787,7 +794,10 @@ export async function loadConfig(): Promise { return cachedConfig; } const overrides = buildNamedFluxerEnvOverrides(process.env); - const merged = applyPublicOrigin(mergeConfig(defaultConfig(), overrides)); + const merged = applySelfHostedEnvDefault( + applyPublicOrigin(mergeConfig(defaultConfig(), overrides)), + readEnvValue(process.env, 'FLUXER_ENV'), + ); const normalized = normalizeConfig(merged); const derived = deriveEndpointsFromDomain(normalized.domain); const endpoints = {...derived, ...(normalized.endpoint_overrides ?? {})}; diff --git a/packages/config/src/__tests__/ConfigLoader.test.ts b/packages/config/src/__tests__/ConfigLoader.test.ts index cc3fd82ab..7be642105 100644 --- a/packages/config/src/__tests__/ConfigLoader.test.ts +++ b/packages/config/src/__tests__/ConfigLoader.test.ts @@ -80,6 +80,29 @@ describe('ConfigLoader', () => { expect((await loadConfig()).domain.base_domain).toBe('localhost'); }); + test('a self-hosted instance without FLUXER_ENV runs in production', async () => { + stubMinimalEnv({ + FLUXER_SELF_HOSTED: 'true', + FLUXER_PROFILE_PSEUDONYM_SECRET: 'self-hosted-secret', + FLUXER_POSTGRES_HOST: 'postgres', + FLUXER_POSTGRES_DATABASE: 'fluxer', + FLUXER_POSTGRES_PASSWORD: 'self-hosted-password', + }); + vi.stubEnv('FLUXER_ENV', undefined); + expect((await loadConfig()).env).toBe('production'); + }); + + test('a self-hosted instance keeps an explicit FLUXER_ENV', async () => { + stubMinimalEnv({FLUXER_SELF_HOSTED: 'true', FLUXER_ENV: 'development'}); + expect((await loadConfig()).env).toBe('development'); + }); + + test('an instance that is not self-hosted still defaults to development', async () => { + stubMinimalEnv(); + vi.stubEnv('FLUXER_ENV', undefined); + expect((await loadConfig()).env).toBe('development'); + }); + test('endpoint overrides take precedence over derived endpoints', async () => { stubMinimalEnv({ FLUXER_API_ENDPOINT: 'https://custom-api.example.com',