import { readConfigFile } from "./config-file.js"; import { existsSync } from "node:fs"; import { config as loadDotenv } from "dotenv"; import { resolvePaperclipEnvPath } from "./paths.js"; import { SECRET_PROVIDERS, STORAGE_PROVIDERS, type SecretProvider, type StorageProvider } from "@paperclip/shared"; import { resolveDefaultEmbeddedPostgresDir, resolveDefaultSecretsKeyFilePath, resolveDefaultStorageDir, resolveHomeAwarePath, } from "./home-paths.js"; const PAPERCLIP_ENV_FILE_PATH = resolvePaperclipEnvPath(); if (existsSync(PAPERCLIP_ENV_FILE_PATH)) { loadDotenv({ path: PAPERCLIP_ENV_FILE_PATH, override: false, quiet: true }); } type DatabaseMode = "embedded-postgres" | "postgres"; export interface Config { port: number; databaseMode: DatabaseMode; databaseUrl: string | undefined; embeddedPostgresDataDir: string; embeddedPostgresPort: number; serveUi: boolean; uiDevMiddleware: boolean; secretsProvider: SecretProvider; secretsStrictMode: boolean; secretsMasterKeyFilePath: string; storageProvider: StorageProvider; storageLocalDiskBaseDir: string; storageS3Bucket: string; storageS3Region: string; storageS3Endpoint: string | undefined; storageS3Prefix: string; storageS3ForcePathStyle: boolean; heartbeatSchedulerEnabled: boolean; heartbeatSchedulerIntervalMs: number; } export function loadConfig(): Config { const fileConfig = readConfigFile(); const fileDatabaseMode = (fileConfig?.database.mode === "postgres" ? "postgres" : "embedded-postgres") as DatabaseMode; const fileDbUrl = fileDatabaseMode === "postgres" ? fileConfig?.database.connectionString : undefined; const fileSecrets = fileConfig?.secrets; const fileStorage = fileConfig?.storage; const strictModeFromEnv = process.env.PAPERCLIP_SECRETS_STRICT_MODE; const secretsStrictMode = strictModeFromEnv !== undefined ? strictModeFromEnv === "true" : (fileSecrets?.strictMode ?? false); const providerFromEnvRaw = process.env.PAPERCLIP_SECRETS_PROVIDER; const providerFromEnv = providerFromEnvRaw && SECRET_PROVIDERS.includes(providerFromEnvRaw as SecretProvider) ? (providerFromEnvRaw as SecretProvider) : null; const providerFromFile = fileSecrets?.provider; const secretsProvider: SecretProvider = providerFromEnv ?? providerFromFile ?? "local_encrypted"; const storageProviderFromEnvRaw = process.env.PAPERCLIP_STORAGE_PROVIDER; const storageProviderFromEnv = storageProviderFromEnvRaw && STORAGE_PROVIDERS.includes(storageProviderFromEnvRaw as StorageProvider) ? (storageProviderFromEnvRaw as StorageProvider) : null; const storageProvider: StorageProvider = storageProviderFromEnv ?? fileStorage?.provider ?? "local_disk"; const storageLocalDiskBaseDir = resolveHomeAwarePath( process.env.PAPERCLIP_STORAGE_LOCAL_DIR ?? fileStorage?.localDisk?.baseDir ?? resolveDefaultStorageDir(), ); const storageS3Bucket = process.env.PAPERCLIP_STORAGE_S3_BUCKET ?? fileStorage?.s3?.bucket ?? "paperclip"; const storageS3Region = process.env.PAPERCLIP_STORAGE_S3_REGION ?? fileStorage?.s3?.region ?? "us-east-1"; const storageS3Endpoint = process.env.PAPERCLIP_STORAGE_S3_ENDPOINT ?? fileStorage?.s3?.endpoint ?? undefined; const storageS3Prefix = process.env.PAPERCLIP_STORAGE_S3_PREFIX ?? fileStorage?.s3?.prefix ?? ""; const storageS3ForcePathStyle = process.env.PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE !== undefined ? process.env.PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE === "true" : (fileStorage?.s3?.forcePathStyle ?? false); return { port: Number(process.env.PORT) || fileConfig?.server.port || 3100, databaseMode: fileDatabaseMode, databaseUrl: process.env.DATABASE_URL ?? fileDbUrl, embeddedPostgresDataDir: resolveHomeAwarePath( fileConfig?.database.embeddedPostgresDataDir ?? resolveDefaultEmbeddedPostgresDir(), ), embeddedPostgresPort: fileConfig?.database.embeddedPostgresPort ?? 54329, serveUi: process.env.SERVE_UI !== undefined ? process.env.SERVE_UI === "true" : fileConfig?.server.serveUi ?? true, uiDevMiddleware: process.env.PAPERCLIP_UI_DEV_MIDDLEWARE === "true", secretsProvider, secretsStrictMode, secretsMasterKeyFilePath: resolveHomeAwarePath( process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE ?? fileSecrets?.localEncrypted.keyFilePath ?? resolveDefaultSecretsKeyFilePath(), ), storageProvider, storageLocalDiskBaseDir, storageS3Bucket, storageS3Region, storageS3Endpoint, storageS3Prefix, storageS3ForcePathStyle, heartbeatSchedulerEnabled: process.env.HEARTBEAT_SCHEDULER_ENABLED !== "false", heartbeatSchedulerIntervalMs: Math.max(10000, Number(process.env.HEARTBEAT_SCHEDULER_INTERVAL_MS) || 30000), }; }