Add web server, certificates and Samba/AD management tabs, a web server config generator, toggleable feature menus and an animated start page background
- Web server tab (nginx, Apache, Caddy, PHP-FPM): service control with autostart, config test and live logs, sites list with enable/disable/edit/delete, new site wizard (static, reverse proxy, PHP) and a code editor for all config files; every change is checked (nginx -t, apachectl configtest, caddy validate, php-fpm -t) before reloading and rolled back if the check fails - Config generator: all options grouped and searchable (reverse proxy with load balancing and WebSockets, HTTPS/TLS, HSTS, security headers, IP allow list, basic auth, rate limit, gzip, caching, upload size, PHP, redirects, logs, custom directives), presets, live preview for nginx/Apache/Caddy; save as a site (needed Apache modules are enabled) or open in the editor first - Certificates tab: Let's Encrypt via certbot (request with nginx/Apache/webroot/standalone/DNS challenge, renew, dry run, delete) in a terminal tab, acme.sh certificates, expiry of all found certificate files, auto-renew timer, self-signed certificates and a TLS endpoint check from this computer - Samba/AD tab: shares (smb.conf edited section by section, checked with testparm), Samba users, services and connected clients; create a new AD domain (samba-tool domain provision), join or leave a domain (realmd/sssd) and manage domain users, groups, members and computers; passwords are passed via stdin - Settings: feature menus (Docker, firewall, network, web server, certificates, Samba/AD) can be enabled or disabled for the host menu, terminal toolbar and quick search - Start page: slowly drifting, blurred colour glows in the theme colours behind the vault (can be turned off in the design settings, respects reduced motion) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
// Gemeinsame Basis für Verwaltungs-Tabs über SSH (Webserver, Zertifikate, Samba/AD): Verbindung, root-Rechte
|
||||
// (direkt als root oder per sudo), Skripte als root, Konfigurationsdateien mit Backup, Prüfung und automatischem Zurückrollen.
|
||||
// Unterklassen setzen NAME (für Fehlermeldungen), OPS (aufrufbare Operationen) und implementieren read(id).
|
||||
const i18n = require('../i18n');
|
||||
const { execOn } = require('./docker');
|
||||
const { acquireSudo } = require('./sudo');
|
||||
|
||||
const lastLine = (s) => String(s || '').split('\n').map((l) => l.trim()).filter(Boolean).slice(-1)[0] || '';
|
||||
const b64 = (s) => Buffer.from(String(s)).toString('base64');
|
||||
const q = (s) => `'${String(s).replace(/'/g, "'\\''")}'`; // Shell-Quoting in einfachen Anführungszeichen
|
||||
const check = (v, rx, what) => { if (!rx.test(String(v ?? ''))) throw new Error(i18n.t('Invalid value for {field}: {value}', { field: what, value: v })); return String(v); };
|
||||
|
||||
// Ausgabe mit @@ABSCHNITT-Markern in { ABSCHNITT: text } zerlegen
|
||||
function sections(out) {
|
||||
const res = {};
|
||||
let cur = null;
|
||||
for (const line of String(out).split('\n')) {
|
||||
const m = line.match(/^@@(\w+)$/);
|
||||
if (m) { cur = m[1]; res[cur] = []; continue; }
|
||||
if (cur) res[cur].push(line);
|
||||
}
|
||||
return Object.fromEntries(Object.entries(res).map(([k, v]) => [k, v.join('\n').trim()]));
|
||||
}
|
||||
|
||||
// Paketverwaltung des Hosts erkennen (für Installationsbefehle im Terminal)
|
||||
const PKG_DETECT = 'if command -v apt-get >/dev/null 2>&1; then echo apt; elif command -v dnf >/dev/null 2>&1; then echo dnf; elif command -v yum >/dev/null 2>&1; then echo yum; elif command -v pacman >/dev/null 2>&1; then echo pacman; elif command -v zypper >/dev/null 2>&1; then echo zypper; elif command -v apk >/dev/null 2>&1; then echo apk; fi';
|
||||
function installCmd(pkg, packages) {
|
||||
const p = packages.join(' ');
|
||||
return { apt: `apt-get update && apt-get install -y ${p}`, dnf: `dnf install -y ${p}`, yum: `yum install -y ${p}`, pacman: `pacman -S --needed --noconfirm ${p}`, zypper: `zypper install -y ${p}`, apk: `apk add ${p}` }[pkg] || '';
|
||||
}
|
||||
|
||||
class AdminManager {
|
||||
constructor(ssh) {
|
||||
this.ssh = ssh;
|
||||
this.sessions = new Map(); // id -> { conn, jumps, host, sudo, sudoPw, ... }
|
||||
}
|
||||
|
||||
get(id) {
|
||||
const s = this.sessions.get(id);
|
||||
if (!s) throw new Error(i18n.t('Session not found'));
|
||||
return s;
|
||||
}
|
||||
|
||||
// Shell-Skript als root ausführen (base64-kodiert, damit kein Quoting-Problem entsteht).
|
||||
// stdin: weitere Eingabe für das Skript (z. B. Passwörter, die nicht in der Kommandozeile stehen sollen)
|
||||
root(s, script, stdin = '') {
|
||||
const cmd = `sh -c "$(printf %s ${b64(`export PATH=$PATH:/usr/sbin:/sbin:/usr/local/sbin\n${script}`)} | base64 -d)"`;
|
||||
if (!s.sudo) return execOn(s.conn, cmd, stdin);
|
||||
return execOn(s.conn, `sudo -S -p '' ${cmd}`, `${s.sudoPw || ''}\n${stdin}`);
|
||||
}
|
||||
async rootOk(s, script, stdin) {
|
||||
const r = await this.root(s, script, stdin);
|
||||
if (r.code) throw new Error(lastLine(r.err) || lastLine(r.out) || i18n.t('Command failed with code {code}', { code: r.code }));
|
||||
return r.out;
|
||||
}
|
||||
|
||||
async open(id, host, onClose) {
|
||||
const { conn, jumps } = await this.ssh.connect(host, id);
|
||||
const s = { conn, jumps, host, sudo: false };
|
||||
this.sessions.set(id, s);
|
||||
conn.on('close', () => { if (this.sessions.get(id) === s) { this.close(id); onClose(); } });
|
||||
conn.on('error', () => {});
|
||||
if ((await execOn(conn, 'id -u')).out.trim() !== '0') {
|
||||
s.sudo = true;
|
||||
s.sudoPw = await acquireSudo(this.ssh, id, host, (c, stdin) => execOn(conn, c, stdin));
|
||||
if (s.sudoPw == null) throw new Error(i18n.t('Root privileges are required. Log in as root or enter the password of a user with sudo rights.'));
|
||||
}
|
||||
s.pkg = (await this.root(s, PKG_DETECT)).out.trim();
|
||||
return this.read(id);
|
||||
}
|
||||
|
||||
// Präfix für Befehle, die im Terminal-Tab laufen (dort fragt sudo selbst nach dem Passwort)
|
||||
termPrefix(s) { return s.sudo ? 'sudo ' : ''; }
|
||||
termCmd(s, cmd) { return s.sudo ? `sudo sh -c ${q(cmd)}` : cmd; }
|
||||
|
||||
// Datei schreiben mit Backup; validate (Shell) muss erfolgreich sein, sonst wird der alte Stand wiederhergestellt.
|
||||
// after: Befehl nach erfolgreicher Prüfung (z. B. reload). Liefert die Ausgabe der Prüfung.
|
||||
async writeChecked(s, path, content, { validate = 'true', after = '', mode = '' } = {}) {
|
||||
const r = await this.root(s, `f=${q(path)}
|
||||
had=0; [ -e "$f" ] && { cp -a "$f" "$f.mrterm-bak" || exit 1; had=1; }
|
||||
mkdir -p "$(dirname "$f")" || exit 1
|
||||
printf %s ${b64(content)} | base64 -d > "$f" || exit 1
|
||||
${mode ? `chmod ${mode} "$f"` : ''}
|
||||
if ! out=$( (${validate}) 2>&1 ); then
|
||||
if [ $had = 1 ]; then mv -f "$f.mrterm-bak" "$f"; else rm -f "$f"; fi
|
||||
printf '%s\\n' "$out" >&2; exit 4
|
||||
fi
|
||||
printf '%s\\n' "$out"
|
||||
${after ? `(${after}) 2>&1 || exit 6` : ''}
|
||||
exit 0`);
|
||||
if (r.code === 4) throw new Error(i18n.t('The configuration is invalid, the previous version was restored: {err}', { err: r.err.trim().split('\n').slice(-6).join('\n') }));
|
||||
if (r.code === 6) throw new Error(i18n.t('Saved, but reloading failed: {err}', { err: lastLine(r.out) }));
|
||||
if (r.code) throw new Error(lastLine(r.err) || i18n.t('Command failed with code {code}', { code: r.code }));
|
||||
return r.out.trim();
|
||||
}
|
||||
|
||||
// Generischer Einstieg für die Oberfläche: nur Operationen aus OPS sind erlaubt
|
||||
op(id, name, ...args) {
|
||||
if (!this.constructor.OPS.includes(name)) throw new Error('Invalid operation');
|
||||
return this[name](this.get(id), ...args);
|
||||
}
|
||||
|
||||
close(id) {
|
||||
const s = this.sessions.get(id);
|
||||
if (!s) return;
|
||||
this.sessions.delete(id);
|
||||
try { s.conn.end(); } catch {}
|
||||
s.jumps?.forEach((c) => { try { c.end(); } catch {} });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { AdminManager, sections, lastLine, b64, q, check, installCmd };
|
||||
Reference in New Issue
Block a user