release r007 security hardening and redesigned UI

This commit is contained in:
Mikei386
2026-08-21 07:43:39 +02:00
parent 514310912c
commit 65922b7f57
12 changed files with 485 additions and 346 deletions
+132 -265
View File
@@ -1,306 +1,173 @@
---
Menu="OtherSettings"
Menu="Utilities"
Type="xmenu"
Title="MUA"
Icon="cubes"
---
<?php
/**
* MUA - Mikes Unraid Agent
* WebGUI-Tab (mua.page)
* Unraid-Standard .page-Format
*
* Einstellungen:
* A) API-Key generieren (POST /config {generate:true})
* B) Tools per Checkbox aktivieren/deaktivieren (POST /config {enabledTools:[...]})
*
* Config-Server: 127.0.0.1:3013 (nur localhost, kein Auth nötig —
* WebGUI läuft auf demselben Host und ist selbst authifiziert).
*/
$plugin_dir = '/usr/local/emhttp/plugins/mua';
/** MUA WebGUI — sicherheitsorientierte Verwaltung. */
$port = 3002;
$config_port = 3013;
$pidfile = '/var/run/mua.pid';
$binary = '/usr/local/bin/mua';
$endpoint = 'http://' . ($_SERVER['SERVER_ADDR'] ?? '127.0.0.1') . ':' . $port . '/mcp';
$admin_token_file = '/var/run/mua-admin.token';
$admin_token = is_readable($admin_token_file) ? trim(file_get_contents($admin_token_file)) : '';
$host_ip = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1';
$endpoint = 'http://' . $host_ip . ':' . $port . '/mcp';
function mua_api($method, $path, $payload, $token, $port) {
if (!$token) return [0, null];
$ch = curl_init('http://127.0.0.1:' . $port . $path);
$headers = ['X-MUA-Admin-Token: ' . $token];
$options = [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_CONNECTTIMEOUT => 2, CURLOPT_HTTPHEADER => $headers];
if ($method === 'POST') {
$options[CURLOPT_POST] = true;
if ($payload !== null) {
$options[CURLOPT_POSTFIELDS] = json_encode($payload);
$headers[] = 'Content-Type: application/json';
$options[CURLOPT_HTTPHEADER] = $headers;
}
}
curl_setopt_array($ch, $options);
$raw = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [$code, $raw !== false ? json_decode($raw, true) : null];
}
function csrf_value($token, $offset = 0) {
return hash_hmac('sha256', gmdate('Y-m-d-H', time() + $offset * 3600), $token);
}
function mua_health($port) {
$ch = curl_init('http://127.0.0.1:' . $port . '/health');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3, CURLOPT_CONNECTTIMEOUT => 2]);
$raw = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $code === 200 && $raw !== false ? json_decode($raw, true) : null;
}
// ── Status bestimmen ────────────────────────────────────────────────────
$running = false;
$pid = null;
if (file_exists($pidfile)) {
$pid = trim(file_get_contents($pidfile));
if ($pid && is_numeric($pid) && @posix_kill((int)$pid, 0)) {
$running = true;
}
$running = $pid && is_numeric($pid) && @posix_kill((int)$pid, 0);
}
// ── Health-Check (MCP-Server, Port 3002) ────────────────────────────────
$health = null;
$health_code = 0;
$ch = curl_init('http://127.0.0.1:' . $port . '/health');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$health = curl_exec($ch);
$health_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$health_ok = ($health_code === 200);
// Version aus /health lesen (dynamisch, kein hartkodiertes rNNN)
$running_version = '';
if ($health_ok && $health !== false) {
$h = json_decode($health, true);
$running_version = $h['version'] ?? '';
}
// ── Config vom Config-Server laden (Port 3003) ──────────────────────────
$config = null;
$ch = curl_init('http://127.0.0.1:' . $config_port . '/config');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$config_raw = curl_exec($ch);
$config_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($config_code === 200 && $config_raw !== false) {
$config = json_decode($config_raw, true);
}
$api_key = $config['apiKey'] ?? '';
$enabled_tools = $config['enabledTools'] ?? [];
$all_tools = $config['allTools'] ?? [];
$config_ok = ($config !== null);
// ── POST-Handling (Einstellungen speichern) ─────────────────────────────
$health_data = mua_health($port);
$health_ok = is_array($health_data) && ($health_data['status'] ?? '') === 'ok';
$running_version = $health_data['version'] ?? '';
[$config_code, $config] = mua_api('GET', '/config', null, $admin_token, $config_port);
$config_ok = $config_code === 200 && is_array($config);
$flash_msg = '';
$flash_type = '';
$new_api_key = '';
$csrf = csrf_value($admin_token);
$readonly = [
'unraid_docker_list', 'unraid_docker_inspect', 'unraid_docker_logs',
'unraid_docker_analyze_logs', 'unraid_docker_processes', 'unraid_docker_stats',
'unraid_docker_info', 'unraid_network_inventory', 'unraid_network_list',
'unraid_network_inspect', 'unraid_network_host_state', 'unraid_system_connection_test',
];
$operator = array_merge($readonly, [
'unraid_network_audit_tcp', 'unraid_network_lan_probe',
'unraid_docker_start', 'unraid_docker_stop', 'unraid_docker_restart',
]);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$payload = [];
// A) API-Key generieren
if (isset($_POST['action']) && $_POST['action'] === 'generate_key') {
$payload['generate'] = true;
}
// B) Tool-Checkboxen
if (isset($_POST['action']) && $_POST['action'] === 'save_tools') {
$selected = [];
foreach ($all_tools as $tool) {
if (isset($_POST['tool_' . $tool])) {
$selected[] = $tool;
$posted_csrf = $_POST['mua_csrf'] ?? '';
$csrf_ok = $admin_token && (hash_equals(csrf_value($admin_token), $posted_csrf) || hash_equals(csrf_value($admin_token, -1), $posted_csrf));
if (!$csrf_ok) {
$flash_msg = 'Sicherheitsprüfung fehlgeschlagen. Seite neu laden und erneut versuchen.';
$flash_type = 'err';
} else {
$action = $_POST['action'] ?? '';
$payload = null;
if ($action === 'generate_key') $payload = ['generate' => true];
if ($action === 'preset_readonly') $payload = ['enabledTools' => $readonly, 'allToolsEnabled' => false];
if ($action === 'preset_operator') $payload = ['enabledTools' => $operator, 'allToolsEnabled' => false];
if ($action === 'preset_none') $payload = ['enabledTools' => [], 'allToolsEnabled' => false];
if ($action === 'preset_all') $payload = ['enabledTools' => [], 'allToolsEnabled' => true];
if ($action === 'save_tools') {
$selected = [];
foreach (($config['allTools'] ?? []) as $tool) {
$name = is_array($tool) ? ($tool['name'] ?? '') : $tool;
if ($name && isset($_POST['tool_' . $name])) $selected[] = $name;
}
$payload = ['enabledTools' => $selected, 'allToolsEnabled' => false];
}
$payload['enabledTools'] = $selected;
}
// C) Service neu starten
if (isset($_POST['action']) && $_POST['action'] === 'restart') {
$ch = curl_init('http://127.0.0.1:' . $config_port . '/restart');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_POST => true,
]);
$resp_raw = curl_exec($ch);
$resp_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($resp_code === 200) {
$flash_msg = 'MUA wird neu gestartet (Service restart).';
$flash_type = 'ok';
} else {
$flash_msg = 'Fehler beim Neustart (HTTP ' . $resp_code . ')';
$flash_type = 'err';
}
}
if (!empty($payload)) {
$ch = curl_init('http://127.0.0.1:' . $config_port . '/config');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$resp_raw = curl_exec($ch);
$resp_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$resp = json_decode($resp_raw, true);
if ($resp_code === 200 && isset($resp['ok'])) {
// Config neu laden
$ch = curl_init('http://127.0.0.1:' . $config_port . '/config');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$config_raw = curl_exec($ch);
curl_close($ch);
$config = json_decode($config_raw, true);
$api_key = $config['apiKey'] ?? '';
$enabled_tools = $config['enabledTools'] ?? [];
if ($payload['generate'] ?? false) {
$flash_msg = 'Neuer API-Key generiert.';
if ($action === 'restart') {
[$code, $result] = mua_api('POST', '/restart', null, $admin_token, $config_port);
$flash_msg = $code === 200 ? 'MUA wird neu gestartet.' : 'Neustart fehlgeschlagen (HTTP ' . $code . ').';
$flash_type = $code === 200 ? 'ok' : 'err';
} elseif ($payload !== null) {
[$code, $result] = mua_api('POST', '/config', $payload, $admin_token, $config_port);
if ($code === 200 && is_array($result) && ($result['ok'] ?? false)) {
$new_api_key = $result['generatedApiKey'] ?? '';
$flash_msg = $new_api_key ? 'Neuer API-Key erzeugt. Jetzt kopieren – er wird nur dieses eine Mal angezeigt.' : 'Sicherheitseinstellungen gespeichert.';
$flash_type = 'ok';
[$config_code, $config] = mua_api('GET', '/config', null, $admin_token, $config_port);
$config_ok = $config_code === 200 && is_array($config);
} else {
$flash_msg = 'Tool-Einstellungen gespeichert (' . count($selected) . ' von ' . count($all_tools) . ' aktiv).';
$flash_type = 'ok';
$flash_msg = 'Speichern fehlgeschlagen (HTTP ' . $code . ').';
$flash_type = 'err';
}
} else {
$flash_msg = 'Fehler beim Speichern (HTTP ' . $resp_code . ')';
$flash_type = 'err';
}
}
}
$binary_exists = file_exists($binary);
$host_ip = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1';
// Tool-Gruppierung für die Checkbox-Liste
$tool_groups = [
'Docker' => [
'unraid_docker_list', 'unraid_docker_inspect', 'unraid_docker_logs',
'unraid_docker_analyze_logs', 'unraid_docker_processes', 'unraid_docker_stats',
'unraid_docker_info', 'unraid_docker_start', 'unraid_docker_stop',
'unraid_docker_restart', 'unraid_docker_create', 'unraid_docker_modify',
'unraid_docker_update', 'unraid_docker_rebuild',
],
'Netzwerk' => [
'unraid_network_inventory', 'unraid_network_list', 'unraid_network_inspect',
'unraid_network_host_state', 'unraid_network_audit_tcp', 'unraid_network_lan_probe',
],
'System' => [
'unraid_system_connection_test',
'unraid_system_shell',
],
$enabled_tools = $config['enabledTools'] ?? [];
$all_tools_enabled = (bool)($config['allToolsEnabled'] ?? false);
$all_tools = $config['allTools'] ?? [];
$risk_groups = ['read' => [], 'active' => [], 'write' => [], 'critical' => []];
foreach ($all_tools as $tool) {
if (!is_array($tool)) $tool = ['name' => $tool, 'description' => '', 'risk' => 'read'];
$risk = $tool['risk'] ?? 'read';
if (!isset($risk_groups[$risk])) $risk = 'read';
$risk_groups[$risk][] = $tool;
}
$risk_labels = [
'read' => ['Nur Lesen', 'Liest Status und Diagnoseinformationen.', 'safe'],
'active' => ['Aktive Prüfungen', 'Baut gezielt Netzwerkverbindungen für Tests auf.', 'notice'],
'write' => ['Betrieb steuern', 'Startet, stoppt oder startet Container neu.', 'warn'],
'critical' => ['Kritischer Zugriff', 'Verändert Container oder führt uneingeschränkte Root-Befehle aus.', 'danger'],
];
$active_count = $all_tools_enabled ? count($all_tools) : count($enabled_tools);
?>
<style>
.mua-status { padding: 12px 16px; border-radius: 6px; margin: 12px 0; font-size: 0.95em; }
.mua-status.ok { background: #e8f5e9; border: 1px solid #4caf50; color: #2e7d32; }
.mua-status.err { background: #ffebee; border: 1px solid #f44336; color: #c62828; }
.mua-status.warn { background: #fff3e0; border: 1px solid #ff9800; color: #e65100; }
.mua-endpoint { font-size: 1.1em; color: #4a90d9; font-weight: 600; }
.mua-key { font-size: 1.05em; word-break: break-all; background: #f5f5f5; padding: 8px 12px; border-radius: 4px; border: 1px solid #ddd; }
.mua-tool-group { margin: 12px 0; }
.mua-tool-group h3 { margin: 8px 0 4px 0; font-size: 1em; color: #555; }
.mua-tool-item { display: inline-block; margin: 4px 12px 4px 0; font-size: 0.9em; }
.mua-tool-item code { font-size: 0.85em; }
.mua-btn { padding: 6px 16px; border-radius: 4px; border: 1px solid #ccc; background: #f5f5f5; cursor: pointer; font-size: 0.9em; }
.mua-btn:hover { background: #e0e0e0; }
.mua-btn-primary { background: #4a90d9; color: white; border-color: #3a7bc0; }
.mua-btn-primary:hover { background: #3a7bc0; }
.mua-wrap{max-width:1280px}.mua-hero{display:flex;justify-content:space-between;align-items:flex-start;gap:18px;margin:12px 0 20px}.mua-title h2{margin:0 0 5px;font-size:1.55rem}.mua-muted{color:#777}.mua-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(285px,1fr));gap:14px;margin:14px 0}.mua-card{border:1px solid #d8d8d8;border-radius:9px;padding:16px;background:rgba(255,255,255,.03)}.mua-card h3{margin:0 0 8px}.mua-status{padding:11px 14px;border-radius:7px;margin:10px 0;border:1px solid}.mua-status.ok,.safe{border-color:#43a047;background:rgba(67,160,71,.11)}.mua-status.err,.danger{border-color:#e53935;background:rgba(229,57,53,.11)}.mua-status.warn,.warn{border-color:#fb8c00;background:rgba(251,140,0,.12)}.notice{border-color:#1e88e5;background:rgba(30,136,229,.10)}.mua-pill{display:inline-block;padding:3px 9px;border-radius:20px;font-size:.82em;font-weight:600;border:1px solid currentColor}.mua-endpoint{font-size:1.05em;word-break:break-all}.mua-key{display:block;padding:12px;border:1px solid #43a047;border-radius:6px;word-break:break-all;font-size:1.04em;background:rgba(67,160,71,.08)}.mua-actions{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0}.mua-btn{padding:8px 14px;border-radius:5px;border:1px solid #aaa;cursor:pointer}.mua-btn-primary{background:#168bd2;color:#fff;border-color:#168bd2}.mua-btn-danger{background:#c62828;color:#fff;border-color:#c62828}.mua-tool{display:grid;grid-template-columns:22px 1fr;gap:8px;padding:9px 0;border-top:1px solid rgba(128,128,128,.2)}.mua-tool:first-of-type{border-top:0}.mua-tool code{font-size:.86em}.mua-tool small{display:block;color:#777;margin-top:3px;line-height:1.35}.mua-section{margin-top:24px}.mua-summary{font-size:1.08em}.mua-preset{margin:0}.mua-details td:first-child{width:190px;font-weight:600}@media(max-width:700px){.mua-hero{display:block}.mua-actions{display:grid}.mua-btn{width:100%}}
</style>
<div class="mua-wrap">
<div class="mua-hero"><div class="mua-title"><h2>MUA · Mikes Unraid Agent</h2><div class="mua-muted">Sicherer MCP-Zugriff auf Docker, Netzwerk und Unraid-Systemfunktionen</div></div><div><span class="mua-pill <?= $running && $health_ok ? 'safe' : 'danger' ?>"><?= $running && $health_ok ? '● Dienst bereit' : '● Dienst gestört' ?></span></div></div>
<?php if ($running): ?>
<div class="mua-status ok">✅ <strong>MCP-Server läuft</strong> (PID <?= $pid ?>, Port <?= $port ?>)</div>
<?php else: ?>
<div class="mua-status err">❌ <strong>MCP-Server läuft nicht</strong> — starte mit <code>/etc/rc.d/rc.mua start</code></div>
<?php endif; ?>
<?php if ($flash_msg): ?><div class="mua-status <?= htmlspecialchars($flash_type) ?>"><?= htmlspecialchars($flash_msg) ?></div><?php endif; ?>
<?php if ($new_api_key): ?><div class="mua-status ok"><strong>Neuer API-Key – einmalige Anzeige</strong><code class="mua-key" id="mua-new-key"><?= htmlspecialchars($new_api_key) ?></code><button type="button" class="mua-btn" onclick="navigator.clipboard.writeText(document.getElementById('mua-new-key').textContent)">In Zwischenablage kopieren</button></div><?php endif; ?>
<?php if ($health_ok): ?>
<div class="mua-status ok">✅ Health-Check: OK</div>
<?php elseif ($health !== false): ?>
<div class="mua-status warn">⚠️ Health-Check: HTTP <?= $health_code ?></div>
<?php else: ?>
<div class="mua-status warn">⚠️ Health-Check: nicht erreichbar</div>
<?php endif; ?>
<div class="mua-grid">
<div class="mua-card"><h3>Dienst</h3><p class="mua-summary"><?= $health_ok ? '✅ MCP-Server antwortet' : '❌ Health-Check fehlgeschlagen' ?></p><div class="mua-muted">Version <?= htmlspecialchars($running_version ?: '—') ?> · PID <?= htmlspecialchars((string)($pid ?: '—')) ?></div></div>
<div class="mua-card"><h3>Zugriffsprofil</h3><p class="mua-summary"><strong><?= $active_count ?></strong> von <?= count($all_tools) ?> Werkzeugen aktiv</p><div class="mua-muted"><?= $all_tools_enabled ? 'Vollzugriff – einschließlich kritischer Werkzeuge' : ($active_count === 0 ? 'Komplett gesperrt' : 'Benutzerdefinierte Freigabe') ?></div></div>
<div class="mua-card"><h3>Authentifizierung</h3><p class="mua-summary"><?= $config_ok ? '✅ Bearer-Key aktiv' : '❌ Verwaltung nicht erreichbar' ?></p><div class="mua-muted"><?= htmlspecialchars($config['apiKeyMasked'] ?? 'nicht verfügbar') ?></div></div>
</div>
<?php if ($flash_msg): ?>
<div class="mua-status <?= $flash_type ?>"><?= htmlspecialchars($flash_msg) ?></div>
<?php endif; ?>
<div class="mua-card"><h3>MCP-Endpunkt</h3><code class="mua-endpoint"><?= htmlspecialchars($endpoint) ?></code><p class="mua-muted">Streamable HTTP · JSON-RPC 2.0 · Bearer-Authentifizierung erforderlich</p></div>
<h2>MCP-Endpunkt</h2>
<p class="mua-endpoint"><?= $endpoint ?></p>
<p>Transport: <code>Streamable HTTP</code> (POST-only) · JSON-RPC 2.0</p>
<div class="mua-section"><h2>Schnelle Sicherheitsprofile</h2><p class="mua-muted">„Nur Lesen“ ist die empfohlene Dauerstellung. Weitergehende Rechte nur bei konkretem Bedarf freigeben.</p><div class="mua-actions">
<?php foreach ([['preset_readonly','Nur Lesen (empfohlen)',''],['preset_operator','Betrieb + Diagnose',''],['preset_none','Alles sperren',''],['preset_all','Vollzugriff','danger']] as $preset): ?>
<form method="post" class="mua-preset" onsubmit="<?= $preset[0] === 'preset_all' ? "return confirm('Vollzugriff aktiviert auch Container-Umbau und uneingeschränkte Root-Shell. Wirklich freigeben?');" : '' ?>"><input type="hidden" name="mua_csrf" value="<?= htmlspecialchars($csrf) ?>"><input type="hidden" name="action" value="<?= $preset[0] ?>"><button class="mua-btn <?= $preset[2] === 'danger' ? 'mua-btn-danger' : ($preset[0] === 'preset_readonly' ? 'mua-btn-primary' : '') ?>" type="submit"><?= $preset[1] ?></button></form>
<?php endforeach; ?></div></div>
<h2>API-Key (Authentifizierung)</h2>
<?php if ($config_ok && $api_key): ?>
<div class="mua-status ok">✅ API-Key aktiv — Clients müssen diesen als Bearer-Token senden</div>
<p><strong>API-Key:*** <code class="mua-key"><?= htmlspecialchars($api_key) ?></code></p>
<p><strong>Authorization-Header:</strong> <code>Authorization: Bearer *** htmlspecialchars($api_key) ?></code></p>
<p style="color:#666; font-size:0.9em;">
Beispiel (curl):<br>
<code>curl -X POST <?= $endpoint ?> -H "Authorization: Bearer *** htmlspecialchars($api_key) ?>" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'</code>
</p>
<form method="post" style="margin-top:8px;">
<input type="hidden" name="action" value="generate_key">
<button type="submit" class="mua-btn mua-btn-primary" onclick="return confirm('Neuen API-Key generieren? Der alte Key wird ungültig!')">🔄 Neuen API-Key generieren</button>
</form>
<?php elseif ($config_ok): ?>
<div class="mua-status warn">⚠️ Kein API-Key gesetzt. Generiere einen, um den MCP-Endpunkt zu schützen.</div>
<form method="post" style="margin-top:8px;">
<input type="hidden" name="action" value="generate_key">
<button type="submit" class="mua-btn mua-btn-primary">🔄 API-Key generieren</button>
</form>
<?php else: ?>
<div class="mua-status warn">⚠️ Config-Server nicht erreichbar (Port <?= $config_port ?>). Starte den MCP-Server, um die Einstellungen zu verwalten.</div>
<?php endif; ?>
<div class="mua-section"><h2>Werkzeuge im Detail</h2><form method="post"><input type="hidden" name="mua_csrf" value="<?= htmlspecialchars($csrf) ?>"><input type="hidden" name="action" value="save_tools"><div class="mua-grid">
<?php foreach ($risk_groups as $risk => $tools): $meta=$risk_labels[$risk]; ?>
<div class="mua-card <?= $meta[2] ?>"><h3><?= $meta[0] ?> <span class="mua-pill"><?= count($tools) ?></span></h3><p class="mua-muted"><?= $meta[1] ?></p>
<?php foreach ($tools as $tool): $name=$tool['name']; $checked=$all_tools_enabled || in_array($name,$enabled_tools,true); ?>
<label class="mua-tool"><input type="checkbox" name="tool_<?= htmlspecialchars($name) ?>" <?= $checked ? 'checked' : '' ?>><span><code><?= htmlspecialchars($name) ?></code><small><?= htmlspecialchars($tool['description'] ?? '') ?></small></span></label>
<?php endforeach; ?></div><?php endforeach; ?>
</div><button type="submit" class="mua-btn mua-btn-primary">Benutzerdefinierte Auswahl speichern</button></form></div>
<h2>Tools (aktivieren / deaktivieren)</h2>
<?php if ($config_ok && !empty($all_tools)): ?>
<p style="color:#666; font-size:0.9em;">
<?= count($enabled_tools) === 0 ? 'Alle Tools aktiv' : count($enabled_tools) . ' von ' . count($all_tools) . ' Tools aktiv' ?>.
Deaktivierte Tools werden nicht in <code>tools/list</code> angezeigt und bei <code>tools/call</code> abgelehnt.
</p>
<form method="post">
<input type="hidden" name="action" value="save_tools">
<?php foreach ($tool_groups as $group_name => $group_tools): ?>
<div class="mua-tool-group">
<h3><?= htmlspecialchars($group_name) ?> (<?= count($group_tools) ?>)</h3>
<?php foreach ($group_tools as $tool): ?>
<?php $checked = (count($enabled_tools) === 0 || in_array($tool, $enabled_tools)) ? 'checked' : ''; ?>
<label class="mua-tool-item">
<input type="checkbox" name="tool_<?= htmlspecialchars($tool) ?>" value="1" <?= $checked ?>>
<code><?= htmlspecialchars($tool) ?></code>
</label>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<div style="margin-top:12px;">
<button type="submit" class="mua-btn mua-btn-primary">💾 Einstellungen speichern</button>
</div>
</form>
<?php else: ?>
<div class="mua-status warn">⚠️ Tool-Liste nicht verfügbar (Config-Server nicht erreichbar).</div>
<?php endif; ?>
<div class="mua-section"><h2>API-Key</h2><div class="mua-card"><p>Der vollständige Schlüssel wird nicht dauerhaft angezeigt. Beim Erzeugen erscheint er genau einmal; danach ist nur noch die Endung sichtbar.</p><form method="post" onsubmit="return confirm('Der bisherige Schlüssel wird sofort ungültig. Neuen Schlüssel erzeugen?');"><input type="hidden" name="mua_csrf" value="<?= htmlspecialchars($csrf) ?>"><input type="hidden" name="action" value="generate_key"><button type="submit" class="mua-btn">Neuen API-Key erzeugen</button></form></div></div>
<h2>Details</h2>
<table>
<tr><th>Plugin</th><td>MUA (Mikes Unraid Agent)</td></tr>
<tr><th>Version</th><td><?php echo $running_version ? htmlspecialchars($running_version) : '—'; ?></td></tr>
<tr><th>Runtime</th><td>TypeScript (Bun Runtime, kompiliertes Binary)</td></tr>
<tr><th>Binary</th><td><code><?= $binary ?></code> <?= $binary_exists ? '✅' : '❌ fehlt' ?></td></tr>
<tr><th>Port (MCP)</th><td><?= $port ?></td></tr>
<tr><th>Port (Config)</th><td><?= $config_port ?> (nur localhost)</td></tr>
<tr><th>Host-IP</th><td><?= $host_ip ?></td></tr>
<tr><th>Tools</th><td><?= count($all_tools) ?: 22 ?> (Docker: 14, Netzwerk: 6, System: 2)</td></tr>
<tr><th>Plugin-Verzeichnis</th><td><code><?= $plugin_dir ?></code></td></tr>
<tr><th>Service</th><td><code>/etc/rc.d/rc.mua</code> (SysVinit)</td></tr>
</table>
<h2>Verwaltung</h2>
<table>
<tr><th>Aktion</th><th>Befehl</th></tr>
<tr><td>Starten</td><td><code>/etc/rc.d/rc.mua start</code></td></tr>
<tr><td>Stoppen</td><td><code>/etc/rc.d/rc.mua stop</code></td></tr>
<tr><td>Neu starten</td><td><code>/etc/rc.d/rc.mua restart</code></td></tr>
<tr><td>Status</td><td><code>/etc/rc.d/rc.mua status</code></td></tr>
<tr><td>Logs</td><td><code>tail -f /var/log/plugins/mua.log</code></td></tr>
</table>
<h2>Service neu starten</h2>
<p>Startet den MCP-Service neu (z.&nbsp;B. nach einem Plugin-Update, wenn sich das Binary geändert hat).</p>
<form method="post" onsubmit="return confirm('MUA-Service neu starten?');">
<input type="hidden" name="action" value="restart">
<button type="submit" class="btn btn-primary">🔄 MUA neu starten</button>
</form>
<div class="mua-section"><h2>Technische Details</h2><table class="mua-details"><tr><td>Version</td><td><?= htmlspecialchars($running_version ?: '—') ?></td></tr><tr><td>MCP-Port</td><td><?= $port ?></td></tr><tr><td>Verwaltung</td><td>127.0.0.1:<?= $config_port ?> · separater Admin-Token</td></tr><tr><td>Konfiguration</td><td><code>/boot/config/plugins/mua/mua.conf</code></td></tr><tr><td>Audit-Protokoll</td><td><code>/var/log/plugins/mua-audit.log</code> (keine Argumente oder Inhalte)</td></tr></table><form method="post" onsubmit="return confirm('MUA-Service neu starten?');"><input type="hidden" name="mua_csrf" value="<?= htmlspecialchars($csrf) ?>"><input type="hidden" name="action" value="restart"><button type="submit" class="mua-btn">Dienst neu starten</button></form></div>
</div>
+9 -8
View File
@@ -34,12 +34,11 @@ echo " MUA - Paket-Builder"
echo " Version: ${VERSION}"
echo "===================================================="
# 1. Binary bauen (falls nicht vorhanden)
if [ ! -f "${DIST_DIR}/mua" ]; then
echo " Kompiliere Binary..."
cd "${ROOT}"
bun build src/index.ts --compile --target=bun-linux-x64 --outfile "${DIST_DIR}/mua"
fi
# 1. Binary immer frisch bauen. Ein vorhandenes Binary darf niemals
# versehentlich in ein neues Sicherheits-Release übernommen werden.
echo " Kompiliere Binary..."
cd "${ROOT}"
bun build src/index.ts --compile --target=bun-linux-x64 --outfile "${DIST_DIR}/mua"
chmod +x "${DIST_DIR}/mua"
echo " Binary: $(du -h "${DIST_DIR}/mua" | cut -f1)"
@@ -74,6 +73,8 @@ cat > "${BUILD_DIR}/install/doinst.sh" << 'DOEOF'
# Verzeichnisse
mkdir -p /var/log/plugins
touch /var/log/plugins/mua.log
touch /var/log/plugins/mua-audit.log
chmod 600 /var/log/plugins/mua.log /var/log/plugins/mua-audit.log
mkdir -p /boot/config/plugins/mua
# Service starten
@@ -99,12 +100,12 @@ cat > "${BUILD_DIR}/install/slack-desc" << DESCEOF
|
|${PKG_NAME} - Mikes Unraid Agent
|MCP over HTTP (Streamable HTTP) Server für Unraid.
|21 Tools: Docker (14), Netzwerk (6), System (1).
|22 Tools: Docker (14), Netzwerk (6), System (2).
|Port: 3002, Endpunkt: /mcp
|
|Runtime: TypeScript (Bun Runtime, kompiliertes Binary)
|Service: /etc/rc.d/rc.mua (SysVinit)
|WebGUI: Settings > MUA
|WebGUI: Settings > User Utilities > MUA
|
|Version: ${VERSION}
|Arch: ${ARCH}
+2
View File
@@ -47,6 +47,7 @@ stop() {
echo " MUA gestoppt (PID $pid)"
fi
rm -f "$PIDFILE"
rm -f /var/run/mua-admin.token
else
# Fallback: per Port suchen
local pid=$(lsof -ti :$PORT 2>/dev/null)
@@ -56,6 +57,7 @@ stop() {
else
echo " MUA läuft nicht"
fi
rm -f /var/run/mua-admin.token
fi
}