/** * portal/config.mjs — 포털 실행 설정. 배포 때 정해지는 값만 환경 변수로 받고 나머지는 data/config.json(운영자 설정 화면)에서 읽는다. * 비밀값(서비스 키·SSO 비밀·AriMail 키)은 config 가 아니라 vault 에 둔다. */ import { readFileSync, existsSync, writeFileSync, mkdirSync, renameSync } from 'node:fs'; import { join } from 'node:path'; export function loadConfig() { const dataDir = process.env.ARIWEB_PORTAL_DATA || process.env.DATA_DIR || join(process.cwd(), 'data'); mkdirSync(dataDir, { recursive: true, mode: 0o700 }); const file = join(dataDir, 'config.json'); let values = {}; try { values = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')).values || {} : {}; } catch (_) { values = {}; } const cfg = { dataDir, file, port: Number(process.env.PORT || 8910), host: process.env.HOST || '127.0.0.1', publicUrl: values.publicUrl || process.env.PUBLIC_URL || 'https://my.ariweb.net', halUrl: values.halUrl || process.env.HAL_URL || 'https://hal.ariweb.net', webmakerUrl: values.webmakerUrl || process.env.WEBMAKER_URL || 'https://webmaker.ariweb.net', webmakerInternalUrl: values.webmakerInternalUrl || process.env.WEBMAKER_INTERNAL_URL || 'http://127.0.0.1:3080', arimailUrl: values.arimailUrl || process.env.ARIMAIL_URL || 'https://webmail.ariweb.net', arimailInternalUrl: values.arimailInternalUrl || process.env.ARIMAIL_INTERNAL_URL || 'http://127.0.0.1:8790', mailFrom: values.mailFrom || process.env.MAIL_FROM || 'no-reply@ariweb.net', operatorEmail: values.operatorEmail || process.env.OPERATOR_EMAIL || '', values, /** 설정 저장(운영자 화면). 비밀값은 거부. */ set(patch, by = 'ops') { for (const k of Object.keys(patch)) if (/key|secret|token|pass/i.test(k)) throw new Error(`비밀값(${k})은 설정 파일에 둘 수 없습니다 — 보관소를 쓰세요`); Object.assign(values, patch); const tmp = file + '.tmp'; writeFileSync(tmp, JSON.stringify({ version: 1, updatedAt: new Date().toISOString(), updatedBy: by, values }, null, 1), { mode: 0o600 }); renameSync(tmp, file); Object.assign(cfg, patch); }, }; return cfg; }