Route automatic Unraid diagnostics through read-only MUA
This commit is contained in:
1 parent
feb99293b5
commit
b723f820bf
6 files changed
+79
-7
No files matched your search
@@ -12,7 +12,7 @@
|
|||||||
"port": 5001,
|
"port": 5001,
|
||||||
"protocol": "https",
|
"protocol": "https",
|
||||||
"probe": "tcp",
|
"probe": "tcp",
|
||||||
"specialist_tool": "unraid-readonly-local",
|
"specialist_tool": "mua-readonly-local",
|
||||||
"purpose": "Existing NAS, Docker host and storage platform. Inspect it before planning any replacement service on Athena."
|
"purpose": "Existing NAS, Docker host and storage platform. Inspect it before planning any replacement service on Athena."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
"probe": "http-head",
|
"probe": "http-head",
|
||||||
"probe_path": "/",
|
"probe_path": "/",
|
||||||
"specialist_tool": "unraid-readonly-local",
|
"specialist_tool": "mua-readonly-local",
|
||||||
"purpose": "Existing Deemix backend and download location. An Athena MCP must integrate this instance over WireGuard; it must not create a second Deemix unless the user explicitly requests migration or replacement."
|
"purpose": "Existing Deemix backend and download location. An Athena MCP must integrate this instance over WireGuard; it must not create a second Deemix unless the user explicitly requests migration or replacement."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"Welche Docker-Container laufen aktuell auf Unraid?"
|
"Welche Docker-Container laufen aktuell auf Unraid?"
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result["tool_ids"], ["server:mcp:unraid-readonly-local"]
|
result["tool_ids"], ["server:mcp:mua-readonly-local"]
|
||||||
)
|
)
|
||||||
self.assertNotIn("server:mcp:mua", result["tool_ids"])
|
self.assertNotIn("server:mcp:mua", result["tool_ids"])
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"Welche Dacher Contäner laufen aktuell auf dem Anrate Server?"
|
"Welche Dacher Contäner laufen aktuell auf dem Anrate Server?"
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result["tool_ids"], ["server:mcp:unraid-readonly-local"]
|
result["tool_ids"], ["server:mcp:mua-readonly-local"]
|
||||||
)
|
)
|
||||||
|
|
||||||
async def test_navidrome_is_selected(self):
|
async def test_navidrome_is_selected(self):
|
||||||
@@ -193,7 +193,7 @@ class AutoToolSelectorTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result["tool_ids"],
|
result["tool_ids"],
|
||||||
["server:mcp:github-local", "server:mcp:unraid-readonly-local"],
|
["server:mcp:github-local", "server:mcp:mua-readonly-local"],
|
||||||
)
|
)
|
||||||
self.assertIn("Never compensate", result["messages"][0]["content"])
|
self.assertIn("Never compensate", result["messages"][0]["content"])
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ Deemix läuft bereits als Container auf dem Unraid-HomeServer. Eine künftige
|
|||||||
Deemix-MCP-Integration auf Athena verwendet dieses Backend über WireGuard und
|
Deemix-MCP-Integration auf Athena verwendet dieses Backend über WireGuard und
|
||||||
erzeugt nicht ungefragt eine zweite Deemix-Instanz.
|
erzeugt nicht ungefragt eine zweite Deemix-Instanz.
|
||||||
|
|
||||||
|
Automatische Unraid-Abfragen verwenden einen eigenen MUA-Read-only-Zugang, der
|
||||||
|
in Open WebUI ausschließlich Diagnosewerkzeuge sichtbar macht. Der vollständige
|
||||||
|
MUA-Verwaltungszugang bleibt davon getrennt und muss bewusst gewählt werden.
|
||||||
|
|
||||||
Open WebUI und Router veröffentlichen keinen normalen Host-Port. Der
|
Open WebUI und Router veröffentlichen keinen normalen Host-Port. Der
|
||||||
WireGuard-Gateway-Container stellt nur die vorgesehenen VPN-Endpunkte bereit.
|
WireGuard-Gateway-Container stellt nur die vorgesehenen VPN-Endpunkte bereit.
|
||||||
Anwendungscontainer erreichen Heimnetz und Internet fail-closed über WireGuard;
|
Anwendungscontainer erreichen Heimnetz und Internet fail-closed über WireGuard;
|
||||||
|
|||||||
@@ -242,6 +242,12 @@ Netzzugriff. Kein MCP-Port wird am Host veröffentlicht.
|
|||||||
| Unraid | Host-, Docker-, Array-, Netzwerk- und Logdiagnose | read-only Standard |
|
| Unraid | Host-, Docker-, Array-, Netzwerk- und Logdiagnose | read-only Standard |
|
||||||
| MUA/Admin | eng definierte Unraid-Verwaltung | bewusst aktivieren |
|
| MUA/Admin | eng definierte Unraid-Verwaltung | bewusst aktivieren |
|
||||||
|
|
||||||
|
Für automatische Unraid-Diagnose existiert in Open WebUI zusätzlich
|
||||||
|
`mua-readonly-local`. Diese Verbindung nutzt denselben lokalen MUA-Endpunkt,
|
||||||
|
blendet aber Start/Stop, Installation, Änderungen und die freie Root-Shell
|
||||||
|
serverseitig in Open WebUI aus. Die vollständige Verbindung `mua` wird niemals
|
||||||
|
automatisch bereitgestellt.
|
||||||
|
|
||||||
Der vorhandene Deemix-Dienst läuft auf Unraid und ist als externe Abhängigkeit
|
Der vorhandene Deemix-Dienst läuft auf Unraid und ist als externe Abhängigkeit
|
||||||
im Diensteverzeichnis eingetragen. Für ein Deemix-MCP wird standardmäßig nur
|
im Diensteverzeichnis eingetragen. Für ein Deemix-MCP wird standardmäßig nur
|
||||||
ein Relay auf Athena gebaut; ein zweites Deemix-Backend erfordert einen
|
ein Relay auf Athena gebaut; ein zweites Deemix-Backend erfordert einen
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class Filter:
|
|||||||
"github": "server:mcp:github-local",
|
"github": "server:mcp:github-local",
|
||||||
"homeassistant": "server:mcp:homeassistant-local",
|
"homeassistant": "server:mcp:homeassistant-local",
|
||||||
"arr": "server:mcp:arr-local",
|
"arr": "server:mcp:arr-local",
|
||||||
"unraid": "server:mcp:unraid-readonly-local",
|
"unraid": "server:mcp:mua-readonly-local",
|
||||||
"navidrome": "server:mcp:navidrome-local",
|
"navidrome": "server:mcp:navidrome-local",
|
||||||
"platform": "server:mcp:athena-platform",
|
"platform": "server:mcp:athena-platform",
|
||||||
"operator": "server:mcp:athena-operator-local",
|
"operator": "server:mcp:athena-operator-local",
|
||||||
@@ -35,7 +35,7 @@ class Filter:
|
|||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"homeassistant": "Home Assistant",
|
"homeassistant": "Home Assistant",
|
||||||
"arr": "Sonarr/Radarr",
|
"arr": "Sonarr/Radarr",
|
||||||
"unraid": "Unraid-Diagnose",
|
"unraid": "Unraid-Diagnose (MUA read-only)",
|
||||||
"navidrome": "Navidrome",
|
"navidrome": "Navidrome",
|
||||||
"platform": "Athena-Plattformwissen",
|
"platform": "Athena-Plattformwissen",
|
||||||
"operator": "Athena Operator",
|
"operator": "Athena Operator",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ if [[ -s /etc/mike-ai/github-mcp.env ]] && \
|
|||||||
fi
|
fi
|
||||||
python3 - "$db" "$FILTER_DIR" "$ACTION_DIR" "${OPENWEBUI_FILTER_OWNER_ID:-}" "$navidrome_enabled" "$github_enabled" <<'PY'
|
python3 - "$db" "$FILTER_DIR" "$ACTION_DIR" "${OPENWEBUI_FILTER_OWNER_ID:-}" "$navidrome_enabled" "$github_enabled" <<'PY'
|
||||||
import json
|
import json
|
||||||
|
import copy
|
||||||
import pathlib
|
import pathlib
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
@@ -204,6 +205,13 @@ with con:
|
|||||||
"Docker, Shares, Netzwerk, UPS und Logs. Für dieselbe Anfrage nicht zusätzlich "
|
"Docker, Shares, Netzwerk, UPS und Logs. Für dieselbe Anfrage nicht zusätzlich "
|
||||||
"MUA aufrufen; MUA nur für dessen spezielle oder freigegebene Verwaltungsaktionen.",
|
"MUA aufrufen; MUA nur für dessen spezielle oder freigegebene Verwaltungsaktionen.",
|
||||||
),
|
),
|
||||||
|
"mua-readonly-local": (
|
||||||
|
"Unraid-Diagnose (MUA read-only)",
|
||||||
|
"Automatisch verwendbarer, serverseitig in Open WebUI auf reine Lese- und "
|
||||||
|
"Diagnosewerkzeuge begrenzter MUA-Zugang. Für Containerbestand, Logs, System, "
|
||||||
|
"Storage, Shares und Netzwerkstatus. Keine Start/Stop-, Installations-, "
|
||||||
|
"Änderungs- oder freie Shell-Funktion. Für bewusste Verwaltung MUA separat wählen.",
|
||||||
|
),
|
||||||
"mua": (
|
"mua": (
|
||||||
"MUA (Unraid-Verwaltung)",
|
"MUA (Unraid-Verwaltung)",
|
||||||
"Nur für ausdrücklich benötigte MUA-spezifische oder freigegebene Unraid-"
|
"Nur für ausdrücklich benötigte MUA-spezifische oder freigegebene Unraid-"
|
||||||
@@ -268,6 +276,60 @@ with con:
|
|||||||
info["name"] = name
|
info["name"] = name
|
||||||
info["description"] = description
|
info["description"] = description
|
||||||
changed = True
|
changed = True
|
||||||
|
# Clone the existing authenticated MUA connection into a second
|
||||||
|
# OpenWebUI connection whose exposed function list is strictly
|
||||||
|
# read-only. The bearer value remains in the database and is neither
|
||||||
|
# printed nor copied into Git. Automatic routing uses only this clone;
|
||||||
|
# the original MUA connection remains available for deliberate admin.
|
||||||
|
mua_source = next(
|
||||||
|
(
|
||||||
|
connection for connection in connections
|
||||||
|
if isinstance(connection, dict)
|
||||||
|
and str((connection.get("info") or {}).get("id", "")).lower() == "mua"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if mua_source is not None:
|
||||||
|
readonly_functions = ",".join((
|
||||||
|
"unraid_docker_list", "unraid_docker_inspect", "unraid_docker_logs",
|
||||||
|
"unraid_docker_analyze_logs", "unraid_docker_processes",
|
||||||
|
"unraid_docker_stats", "unraid_docker_info",
|
||||||
|
"unraid_docker_update_status", "unraid_ca_search",
|
||||||
|
"unraid_network_inventory", "unraid_network_list",
|
||||||
|
"unraid_network_inspect", "unraid_network_host_state",
|
||||||
|
"unraid_network_audit_tcp", "unraid_network_lan_probe",
|
||||||
|
"unraid_system_health", "unraid_storage_status",
|
||||||
|
"unraid_disk_health", "unraid_notifications_list",
|
||||||
|
"unraid_shares_list", "unraid_share_inspect",
|
||||||
|
"unraid_system_connection_test", "unraid_system_shell_readonly",
|
||||||
|
))
|
||||||
|
readonly = copy.deepcopy(mua_source)
|
||||||
|
readonly["config"] = {
|
||||||
|
"enable": True,
|
||||||
|
"function_name_filter_list": readonly_functions,
|
||||||
|
"access_grants": [],
|
||||||
|
}
|
||||||
|
name, description = descriptions["mua-readonly-local"]
|
||||||
|
readonly["info"] = {
|
||||||
|
"id": "mua-readonly-local",
|
||||||
|
"name": name,
|
||||||
|
"description": description,
|
||||||
|
}
|
||||||
|
existing_index = next(
|
||||||
|
(
|
||||||
|
index for index, connection in enumerate(connections)
|
||||||
|
if isinstance(connection, dict)
|
||||||
|
and str((connection.get("info") or {}).get("id", "")).lower()
|
||||||
|
== "mua-readonly-local"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if existing_index is None:
|
||||||
|
connections.append(readonly)
|
||||||
|
changed = True
|
||||||
|
elif connections[existing_index] != readonly:
|
||||||
|
connections[existing_index] = readonly
|
||||||
|
changed = True
|
||||||
if navidrome_enabled and not any(
|
if navidrome_enabled and not any(
|
||||||
isinstance(connection, dict)
|
isinstance(connection, dict)
|
||||||
and (
|
and (
|
||||||
|
|||||||
Reference in new issue
Block a user