Files
DOCKERMAILSERVER-LXC/stack/api/lib/settings.js
T
karim 1d3818e725 docker-mailserver LXC für Proxmox: Stack + Admin-UI + Webmail + Hardening
- dms-lxc.sh: Proxmox-Host-Installer (unprivilegierter LXC, Debian 13, Docker),
  curl-Self-Download, Multi-Domain-DKIM, SnappyMail-Provisionierung, PVE-Firewall
- Stack: docker-mailserver, Node-Admin-API (Supabase-Auth), React-Admin-UI
  (OPENBUREAU-Look), SnappyMail (Shibui-Theme), Rspamd-Web-UI, docker-socket-proxy
- Admin: Postfächer/Aliase/Catch-all/Quota, editierbare Domains+Settings,
  Server (Quota/Queue über abgesicherte Bridge), Status & DNS
- Hardening: no-new-privileges, Whitelisted exec-Bridge, Rspamd-Passwort,
  .env chmod 600, PVE-CT-Firewall, generisch/teilbar (keine festen Domains)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 02:26:28 +02:00

78 lines
2.8 KiB
JavaScript

// ---------------------------------------------------------------------------
// 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 };
});
}