Spaces:
Sleeping
Sleeping
| import { randomBytes } from 'node:crypto' | |
| import fs from 'node:fs' | |
| import path from 'node:path' | |
| import { config, ensureDirExists } from './config' | |
| import { logger } from './logger' | |
| function getGeneratedFilePath(): string { | |
| return path.join(config.dataDir, '.auto-generated') | |
| } | |
| interface PersistedValues { | |
| AUTH_SECRET?: string | |
| API_KEY?: string | |
| } | |
| function readPersisted(): PersistedValues { | |
| try { | |
| if (!fs.existsSync(getGeneratedFilePath())) return {} | |
| const raw = fs.readFileSync(getGeneratedFilePath(), 'utf8') | |
| const values: PersistedValues = {} | |
| for (const line of raw.split('\n')) { | |
| const trimmed = line.trim() | |
| if (!trimmed || trimmed.startsWith('#')) continue | |
| const eqIdx = trimmed.indexOf('=') | |
| if (eqIdx < 0) continue | |
| const key = trimmed.slice(0, eqIdx).trim() | |
| const value = trimmed.slice(eqIdx + 1).trim() | |
| if (key === 'AUTH_SECRET' || key === 'API_KEY') { | |
| values[key] = value | |
| } | |
| } | |
| return values | |
| } catch { | |
| return {} | |
| } | |
| } | |
| function writePersisted(values: PersistedValues): void { | |
| try { | |
| ensureDirExists(config.dataDir) | |
| const lines = [ | |
| '# Auto-generated values. Overridden by env vars when set.', | |
| ] | |
| if (values.AUTH_SECRET) lines.push(`AUTH_SECRET=${values.AUTH_SECRET}`) | |
| if (values.API_KEY) lines.push(`API_KEY=${values.API_KEY}`) | |
| fs.writeFileSync(getGeneratedFilePath(), lines.join('\n') + '\n', { mode: 0o600 }) | |
| } catch (err) { | |
| logger.warn({ err }, 'Failed to persist auto-generated values') | |
| } | |
| } | |
| function generate(): string { | |
| return randomBytes(32).toString('hex') | |
| } | |
| // Known placeholder values from .env.example that should be replaced | |
| const PLACEHOLDER_AUTH_SECRETS = new Set([ | |
| 'random-secret-for-legacy-cookies', | |
| ]) | |
| const PLACEHOLDER_API_KEYS = new Set([ | |
| 'generate-a-random-key', | |
| ]) | |
| /** | |
| * Ensure AUTH_SECRET and API_KEY are available. | |
| * Priority: env var > persisted file > auto-generate + persist. | |
| * Sets process.env so downstream code picks them up. | |
| */ | |
| export function ensureAutoGeneratedCredentials(): void { | |
| if (process.env.NEXT_PHASE === 'phase-production-build') return | |
| const persisted = readPersisted() | |
| let dirty = false | |
| // AUTH_SECRET | |
| const currentAuthSecret = (process.env.AUTH_SECRET || '').trim() | |
| if (!currentAuthSecret || PLACEHOLDER_AUTH_SECRETS.has(currentAuthSecret)) { | |
| if (persisted.AUTH_SECRET) { | |
| process.env.AUTH_SECRET = persisted.AUTH_SECRET | |
| } else { | |
| const val = generate() | |
| process.env.AUTH_SECRET = val | |
| persisted.AUTH_SECRET = val | |
| dirty = true | |
| logger.info('Auto-generated AUTH_SECRET (persisted to .data/.auto-generated)') | |
| } | |
| } | |
| // API_KEY | |
| const currentApiKey = (process.env.API_KEY || '').trim() | |
| if (!currentApiKey || PLACEHOLDER_API_KEYS.has(currentApiKey)) { | |
| if (persisted.API_KEY) { | |
| process.env.API_KEY = persisted.API_KEY | |
| } else { | |
| const val = generate() | |
| process.env.API_KEY = val | |
| persisted.API_KEY = val | |
| dirty = true | |
| logger.info('Auto-generated API_KEY (persisted to .data/.auto-generated)') | |
| } | |
| } | |
| if (dirty) { | |
| writePersisted(persisted) | |
| } | |
| } | |