// --------------------------------------------------------------------------- // settings.js — editierbare Admin-Einstellungen (Domains, Webmail-Domain, Brand) // // Liegt als JSON in der DMS-Config (persistent). Beim ersten Start aus den // ENV-Variablen (Deploy-Dialog) geseedet, danach in der Admin-UI editierbar. // --------------------------------------------------------------------------- import { promises as fs } from 'node:fs'; import path from 'node:path'; const CONFIG_DIR = process.env.CONFIG_DIR || '/config'; const FILE = path.join(CONFIG_DIR, 'admin-settings.json'); const isDomain = (s) => /^[^@\s]+\.[^@\s]+$/.test(s); const envDomains = () => (process.env.MAIL_DOMAINS || process.env.MAIL_DOMAIN || '').split(/[\s,]+/).filter(Boolean); function seed() { const domains = envDomains(); const primary = process.env.MAIL_DOMAIN || domains[0] || 'example.com'; return { brand: process.env.BRAND || primary, fqdn: process.env.MAIL_FQDN || `mail.${primary}`, primaryDomain: primary, domains: domains.length ? domains : [primary], webmailFqdn: process.env.WEBMAIL_FQDN || `mail.${primary}`, adminFqdn: process.env.ADMIN_FQDN || `admin.${primary}`, }; } let chain = Promise.resolve(); const withLock = (fn) => { const r = chain.then(fn, fn); chain = r.catch(() => {}); return r; }; const save = (s) => fs.writeFile(FILE, JSON.stringify(s, null, 2) + '\n', 'utf8'); export async function readSettings() { try { return { id: 'settings', ...JSON.parse(await fs.readFile(FILE, 'utf8')) }; } catch (e) { if (e.code === 'ENOENT') { const s = seed(); await save(s); return { id: 'settings', ...s }; } throw e; } } export function writeSettings(patch) { return withLock(async () => { const { id, ...cur } = await readSettings(); const next = { ...cur }; // nur erlaubte Felder for (const k of ['brand', 'fqdn', 'primaryDomain', 'webmailFqdn', 'adminFqdn']) { if (typeof patch[k] === 'string' && patch[k].trim()) next[k] = patch[k].trim(); } if (Array.isArray(patch.domains)) { next.domains = [...new Set(patch.domains.map((d) => String(d).trim()).filter(isDomain))]; } await save(next); return { id: 'settings', ...next }; }); } export function addDomain(domain) { return withLock(async () => { domain = String(domain || '').trim().toLowerCase(); if (!isDomain(domain)) { const e = new Error('Ungültige Domain.'); e.status = 400; throw e; } const { id, ...cur } = await readSettings(); if (!cur.domains.includes(domain)) cur.domains.push(domain); await save(cur); return { id: 'settings', ...cur }; }); } export function removeDomain(domain) { return withLock(async () => { const { id, ...cur } = await readSettings(); cur.domains = cur.domains.filter((d) => d !== domain); await save(cur); return { id: 'settings', ...cur }; }); }