Files
backspace/packages/server/src/config.ts
T
Jannis Braun d55e85d2c5 feat(federation): PUBLIC_ORIGIN env override for getOurOrigin
Adds an explicit override for the federation transport URL returned by
getOurOrigin(). When unset, behaviour is unchanged (https://${DOMAIN} ->
http://localhost:${PORT} dev fallback). Intended for reverse-proxy /
dev-without-TLS deployments where the public origin must be advertised
explicitly (typically http://...) and differs from the bare DOMAIN
value used for federated identity.

Wired via config.publicOrigin (envOptional('PUBLIC_ORIGIN')) so the
override flows through the existing config layer rather than scattering
process.env reads. Trailing slash is stripped for symmetry with
peer.origin storage.

docs/systems/federation.md gets a "Public Origin Override" subsection
under §14 Background Workers documenting the resolution order.
2026-05-04 00:02:14 +02:00

80 lines
2.7 KiB
TypeScript

import { config as dotenvConfig } from 'dotenv';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
dotenvConfig({ path: resolve(__dirname, '../../../.env') });
function env(key: string, defaultValue?: string): string {
const value = process.env[key] ?? defaultValue;
if (value === undefined) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}
function envOptional(key: string): string | undefined {
return process.env[key] || undefined;
}
function envInt(key: string, defaultValue: number): number {
const value = process.env[key];
if (value === undefined) return defaultValue;
const parsed = parseInt(value, 10);
if (isNaN(parsed)) {
throw new Error(`Environment variable ${key} must be a number, got: ${value}`);
}
return parsed;
}
function envBool(key: string, defaultValue: boolean): boolean {
const value = process.env[key];
if (value === undefined) return defaultValue;
return value === 'true' || value === '1';
}
// PUBLIC_ORIGIN overrides the federation transport URL returned by getOurOrigin().
// Used by integration test harnesses that bind to 127.0.0.1:<ephemeral> and by
// reverse-proxy setups where federation must advertise an http:// origin (the
// proxy terminates TLS upstream). When unset, getOurOrigin() falls back to
// https://${DOMAIN} for production safety.
const publicOrigin = envOptional('PUBLIC_ORIGIN');
if (publicOrigin !== undefined) {
if (!/^https?:\/\//i.test(publicOrigin)) {
throw new Error(
`PUBLIC_ORIGIN must start with http:// or https:// — got: ${publicOrigin}`
);
}
}
export const config = {
port: envInt('PORT', 3000),
host: env('HOST', '0.0.0.0'),
jwtSecret: env('JWT_SECRET'),
jwtExpiresIn: env('JWT_EXPIRES_IN', '30d'),
domain: envOptional('DOMAIN'),
publicOrigin,
livekit: {
url: envOptional('LIVEKIT_URL'),
apiKey: envOptional('LIVEKIT_API_KEY'),
apiSecret: envOptional('LIVEKIT_API_SECRET'),
},
uploadDir: env('UPLOAD_DIR', resolve(__dirname, '../../../data/uploads')),
tusUploadDir: resolve(env('UPLOAD_DIR', resolve(__dirname, '../../../data/uploads')), '.tus'),
tusExpirationMs: envInt('TUS_EXPIRATION_HOURS', 24) * 60 * 60 * 1000,
tusStragglerSweepMs: envInt('TUS_STRAGGLER_SWEEP_HOURS', 48) * 60 * 60 * 1000,
dbPath: env('DB_PATH', resolve(__dirname, '../../../data/backspace.db')),
maxUploadSize: envInt('MAX_UPLOAD_SIZE', 104857600),
registrationOpen: envBool('REGISTRATION_OPEN', true),
} as const;
if (config.jwtSecret.length < 32) {
throw new Error(
`JWT_SECRET must be at least 32 characters (got ${config.jwtSecret.length}). ` +
`Generate one with: openssl rand -hex 32`
);
}