Simplify Athena stack and recovery
This commit is contained in:
@@ -29,7 +29,7 @@ class Filter:
|
||||
"unraid": "server:mcp:mua-readonly-local",
|
||||
"unraid_admin": "server:mcp:mua",
|
||||
"navidrome": "server:mcp:navidrome-local",
|
||||
"platform": "server:mcp:athena-platform",
|
||||
"platform": "server:mcp:athena-operator-local",
|
||||
"operator": "server:mcp:athena-operator-local",
|
||||
"web": "server:mcp:web-general-local",
|
||||
}
|
||||
@@ -300,8 +300,7 @@ class Filter:
|
||||
(
|
||||
r"\bathena\b", r"\bmikeai\b", r"\bki[- ]host\b",
|
||||
r"\bai[- ]profile[- ]router\b", r"\bprofil[- ]router\b",
|
||||
r"\brecovery[- ](?:koffer|bundle|skript)\b",
|
||||
r"\b(?:disaster|bare metal)[- ]recovery\b",
|
||||
r"\b(?:backup|restore|wiederherstellung|neuinstallation)\b",
|
||||
r"\b(?:installations?|reinstall|setup)[- ]skript\b",
|
||||
r"\bplattform(?:wissen|dokumentation)?\b",
|
||||
),
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Synchronise OpenWebUI functions and MCPs from versioned sources.
|
||||
set -Eeuo pipefail
|
||||
umask 077
|
||||
|
||||
CONTAINER=${OPENWEBUI_CONTAINER:-mike-ai-open-webui}
|
||||
VOLUME=${OPENWEBUI_VOLUME:-mike-ai_open-webui-data}
|
||||
FILTER_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/filters" && pwd)
|
||||
ACTION_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/actions" && pwd)
|
||||
MUA_MCP_ENV_FILE=${MUA_MCP_ENV_FILE:-/etc/mike-ai/mua-mcp.env}
|
||||
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||
FILTER_DIR="$ROOT/platform/openwebui/filters"
|
||||
ACTION_DIR="$ROOT/platform/openwebui/actions"
|
||||
|
||||
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
|
||||
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
|
||||
for file in reasoning_default_off.py thinking.py auto_tool_selector.py stability_guard.py secret_redaction.py spoken_tool_status.py local_performance_metrics.py; do
|
||||
[[ -s $FILTER_DIR/$file ]] || die "Filterdatei fehlt: $file"
|
||||
done
|
||||
[[ -s $ACTION_DIR/quick_actions.py ]] || die "Actiondatei fehlt: quick_actions.py"
|
||||
[[ -s $ACTION_DIR/quick_actions.py ]] || die "Actiondatei fehlt."
|
||||
|
||||
volume_path=$(docker volume inspect -f '{{.Mountpoint}}' "$VOLUME")
|
||||
db=$volume_path/webui.db
|
||||
@@ -24,136 +25,67 @@ if [[ $(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true)
|
||||
was_running=true
|
||||
docker stop "$CONTAINER" >/dev/null
|
||||
fi
|
||||
restart_on_exit() {
|
||||
if [[ $was_running == true ]]; then
|
||||
docker start "$CONTAINER" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
restart_on_exit() { [[ $was_running == false ]] || docker start "$CONTAINER" >/dev/null 2>&1 || true; }
|
||||
trap restart_on_exit EXIT
|
||||
|
||||
stamp=$(date +%Y%m%d-%H%M%S)
|
||||
backup=$volume_path/webui.db.before-filter-install-$stamp
|
||||
backup=$volume_path/webui.db.before-managed-sync-$stamp
|
||||
cp -a "$db" "$backup"
|
||||
rm -f "$volume_path/webui.db-wal" "$volume_path/webui.db-shm"
|
||||
|
||||
navidrome_enabled=false
|
||||
[[ -s /etc/mike-ai/navidrome-mcp.env ]] && navidrome_enabled=true
|
||||
deemix_enabled=false
|
||||
[[ -s /etc/mike-ai/deemix-mcp.env ]] && deemix_enabled=true
|
||||
github_enabled=false
|
||||
if [[ -s /etc/mike-ai/github-mcp.env ]] && \
|
||||
grep -Eq '^GITHUB_PERSONAL_ACCESS_TOKEN=.+$' /etc/mike-ai/github-mcp.env; then
|
||||
github_enabled=true
|
||||
fi
|
||||
python3 - "$db" "$FILTER_DIR" "$ACTION_DIR" "${OPENWEBUI_FILTER_OWNER_ID:-}" "$navidrome_enabled" "$github_enabled" "$deemix_enabled" "$MUA_MCP_ENV_FILE" <<'PY'
|
||||
python3 - "$db" "$FILTER_DIR" "$ACTION_DIR" "${OPENWEBUI_FILTER_OWNER_ID:-}" <<'PY'
|
||||
import json
|
||||
import copy
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import sys
|
||||
import time
|
||||
|
||||
(
|
||||
db, filter_dir, action_dir, requested_owner, navidrome_enabled_raw,
|
||||
github_enabled_raw, deemix_enabled_raw, mua_mcp_env_file,
|
||||
) = sys.argv[1:]
|
||||
navidrome_enabled = navidrome_enabled_raw.lower() == "true"
|
||||
github_enabled = github_enabled_raw.lower() == "true"
|
||||
deemix_enabled = deemix_enabled_raw.lower() == "true"
|
||||
db, filter_dir, action_dir, owner = sys.argv[1:]
|
||||
con = sqlite3.connect(db)
|
||||
columns = {row[1] for row in con.execute("pragma table_info(function)")}
|
||||
required = {
|
||||
"id", "user_id", "name", "type", "content", "meta", "valves",
|
||||
"is_active", "is_global", "updated_at", "created_at",
|
||||
}
|
||||
required = {"id", "user_id", "name", "type", "content", "meta", "valves", "is_active", "is_global", "updated_at", "created_at"}
|
||||
if not required <= columns:
|
||||
raise SystemExit("Unbekanntes OpenWebUI-Function-Schema; keine Änderung vorgenommen.")
|
||||
config_columns = {row[1] for row in con.execute("pragma table_info(config)")}
|
||||
if not {"key", "value", "updated_at"} <= config_columns:
|
||||
raise SystemExit("Unbekanntes OpenWebUI-Config-Schema; keine Änderung vorgenommen.")
|
||||
|
||||
owner = requested_owner
|
||||
raise SystemExit("Unknown OpenWebUI function schema")
|
||||
if not owner:
|
||||
existing = con.execute(
|
||||
"select user_id from function where id in (?, ?, ?, ?, ?, ?, ?, ?) order by id limit 1",
|
||||
(
|
||||
"reasoning_default_off", "thinking", "auto_tool_selector", "stability_guard",
|
||||
"secret_redaction", "spoken_tool_status", "local_performance_metrics",
|
||||
"quick_actions",
|
||||
),
|
||||
).fetchone()
|
||||
existing = con.execute("select user_id from function where id='reasoning_default_off'").fetchone()
|
||||
if existing:
|
||||
owner = existing[0]
|
||||
if not owner:
|
||||
admins = con.execute("select id from user where role='admin'").fetchall()
|
||||
if len(admins) != 1:
|
||||
raise SystemExit(
|
||||
"Filter-Eigentümer ist nicht eindeutig; OPENWEBUI_FILTER_OWNER_ID setzen."
|
||||
)
|
||||
raise SystemExit("OPENWEBUI_FILTER_OWNER_ID is not unambiguous")
|
||||
owner = admins[0][0]
|
||||
|
||||
now = int(time.time())
|
||||
functions = [
|
||||
("reasoning_default_off", "Reasoning Default Off", "filter", 10, filter_dir, ""),
|
||||
("thinking", "Thinking", "filter", 20, filter_dir, ""),
|
||||
(
|
||||
"auto_tool_selector", "MikeAI Auto Tool Selector", "filter", 25, filter_dir,
|
||||
"Hält die allgemeine Websuche verfügbar und ergänzt automatisch alle fachlich passenden MCP-Domänen. "
|
||||
"Die Auswahl ist Komfort und keine Schranke oder Freigabe für schreibende Aktionen.",
|
||||
),
|
||||
("auto_tool_selector", "MikeAI Auto Tool Selector", "filter", 25, filter_dir, "Keeps general web search available and selects relevant MCP domains."),
|
||||
("stability_guard", "MikeAI Stability Guard", "filter", 30, filter_dir, ""),
|
||||
("secret_redaction", "MikeAI Secret Redaction", "filter", 40, filter_dir, ""),
|
||||
(
|
||||
"spoken_tool_status", "MikeAI Spoken Tool Status", "filter", 80, filter_dir,
|
||||
"Spielt bei aktiver automatischer Sprachausgabe einmalig eine kurze lokale Ansage, "
|
||||
"sobald ein echtes Werkzeug gestartet wird.",
|
||||
),
|
||||
("spoken_tool_status", "MikeAI Spoken Tool Status", "filter", 80, filter_dir, "Plays one short local status phrase when a real tool starts."),
|
||||
("local_performance_metrics", "MikeAI Local Performance Metrics", "filter", 90, filter_dir, ""),
|
||||
(
|
||||
"quick_actions", "MikeAI Quick Actions", "action", 100, action_dir,
|
||||
"Lokale, geprüfte Aktionen für Zusammenfassung, Diagnose, Quellen und Markdown.",
|
||||
),
|
||||
("quick_actions", "MikeAI Quick Actions", "action", 100, action_dir, "Local actions for summaries, diagnosis, sources and Markdown."),
|
||||
]
|
||||
with con:
|
||||
for function_id, name, function_type, priority, source_dir, description in functions:
|
||||
content = pathlib.Path(source_dir, f"{function_id}.py").read_text()
|
||||
for function_id, name, kind, priority, source_dir, description in functions:
|
||||
content = pathlib.Path(source_dir, function_id + ".py").read_text()
|
||||
con.execute(
|
||||
"""
|
||||
insert into function
|
||||
(id,user_id,name,type,content,meta,valves,is_active,is_global,updated_at,created_at)
|
||||
values (?,?,?,?,?,?,?,?,?,?,?)
|
||||
on conflict(id) do update set
|
||||
name=excluded.name,
|
||||
type=excluded.type,
|
||||
content=excluded.content,
|
||||
meta=excluded.meta,
|
||||
valves=excluded.valves,
|
||||
is_active=excluded.is_active,
|
||||
is_global=excluded.is_global,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
(
|
||||
function_id, owner, name, function_type, content,
|
||||
json.dumps({"description": description}),
|
||||
json.dumps({"priority": priority}), True, True, now, now,
|
||||
),
|
||||
"""insert into function
|
||||
(id,user_id,name,type,content,meta,valves,is_active,is_global,updated_at,created_at)
|
||||
values (?,?,?,?,?,?,?,?,?,?,?)
|
||||
on conflict(id) do update set name=excluded.name,type=excluded.type,
|
||||
content=excluded.content,meta=excluded.meta,valves=excluded.valves,
|
||||
is_active=excluded.is_active,is_global=excluded.is_global,updated_at=excluded.updated_at""",
|
||||
(function_id, owner, name, kind, content, json.dumps({"description": description}),
|
||||
json.dumps({"priority": priority}), True, True, now, now),
|
||||
)
|
||||
con.execute(
|
||||
"""
|
||||
insert into config (key,value,updated_at) values (?,?,?)
|
||||
on conflict(key) do update set
|
||||
value=excluded.value,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
("task.follow_up.enable", "false", now),
|
||||
)
|
||||
for key, value in (
|
||||
("task.follow_up.enable", False),
|
||||
("audio.tts.engine", "openai"),
|
||||
("audio.tts.model", "piper"),
|
||||
("audio.tts.voice", "alloy"),
|
||||
("audio.tts.openai.api_base_url", "http://router:8081/v1"),
|
||||
# Reproduce the working keyless native web search after a fresh install
|
||||
# or database restore. No query or result content is stored here.
|
||||
("web.search.enable", True),
|
||||
("web.search.engine", "duckduckgo"),
|
||||
("web.search.ddgs_backend", "duckduckgo"),
|
||||
@@ -162,392 +94,17 @@ with con:
|
||||
("web.search.confirmation.enable", False),
|
||||
):
|
||||
con.execute(
|
||||
"""
|
||||
insert into config (key,value,updated_at) values (?,?,?)
|
||||
on conflict(key) do update set
|
||||
value=excluded.value,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
"""insert into config (key,value,updated_at) values (?,?,?)
|
||||
on conflict(key) do update set value=excluded.value,updated_at=excluded.updated_at""",
|
||||
(key, json.dumps(value), now),
|
||||
)
|
||||
|
||||
# Improve model-side tool selection without touching URLs, credentials,
|
||||
# access grants or enable flags from a restored Open WebUI database.
|
||||
row = con.execute(
|
||||
"select value from config where key=?",
|
||||
("tool_server.connections",),
|
||||
).fetchone()
|
||||
if row:
|
||||
connections = json.loads(row[0])
|
||||
if not isinstance(connections, list):
|
||||
raise SystemExit("Unbekanntes Format in tool_server.connections.")
|
||||
before_count = len(connections)
|
||||
connections = [
|
||||
connection for connection in connections
|
||||
if not (
|
||||
isinstance(connection, dict)
|
||||
and (
|
||||
str((connection.get("info") or {}).get("id", "")).lower()
|
||||
in {"athena-terminal-local", "unraid-readonly-local", "web-local"}
|
||||
or "mike-ai-mcp-athena-terminal" in str(connection.get("url", "")).lower()
|
||||
or "mike-ai-mcp-unraid-official" in str(connection.get("url", "")).lower()
|
||||
or "mike-ai-mcp-web" in str(connection.get("url", "")).lower()
|
||||
)
|
||||
)
|
||||
]
|
||||
descriptions = {
|
||||
"web-general-local": (
|
||||
"Allgemeines Web (TinySearch)",
|
||||
"Breite, portable Websuche und Seitenabruf für beliebige öffentliche Websites. "
|
||||
"In OpenWebUI ist native search_web/fetch_url standardmäßig verfügbar; dieser "
|
||||
"Upstream-MCP ist die portable Alternative für Hermes, Pi und manuelle Nutzung. "
|
||||
"Kurze, gezielte Resultate anfordern und niemals private Dateiinhalte senden.",
|
||||
),
|
||||
"homeassistant-local": (
|
||||
"Home Assistant (lokal)",
|
||||
"Für Home-Assistant-Entitäten, Zustände, Historie, Automationen, Dashboards, "
|
||||
"HA-Diagnose und freigegebene YAML-Dateien. YAML-Lesen ist begrenzt; Änderungen "
|
||||
"benötigen serverseitige Vorschau, explizite Freigabe, Sicherung und Validierung. "
|
||||
"Nicht für Unraid, Sonarr/Radarr oder allgemeine Websuche.",
|
||||
),
|
||||
"arr-local": (
|
||||
"Sonarr und Radarr (lokal)",
|
||||
"Nur für verwaltete Serien/Filme, fehlende Episoden, Queue und Suche über "
|
||||
"konfigurierte Indexer. Keine allgemeine Websuche; Schreibaktionen benötigen "
|
||||
"Vorschau und Freigabe.",
|
||||
),
|
||||
"mua-readonly-local": (
|
||||
"MUA · Unraid-Diagnose (read-only)",
|
||||
"Automatisch verwendbarer, serverseitig in Open WebUI auf reine Lese- und "
|
||||
"Diagnosewerkzeuge begrenzter MUA-Zugang. Für Containerbestand, Logs, System, "
|
||||
"Storage, Shares, kompakte Datei-/Medieninventare und Netzwerkstatus. "
|
||||
"Für Bibliotheksprüfungen unraid_files_inventory statt wiederholter "
|
||||
"ls/find-Aufrufe verwenden. Keine Start/Stop-, Installations-, "
|
||||
"Änderungs- oder freie Shell-Funktion. Bei einer ausdrücklich verlangten "
|
||||
"Änderung stellt die automatische Auswahl zusätzlich MUA-Verwaltung bereit.",
|
||||
),
|
||||
"mua": (
|
||||
"MUA (Unraid-Verwaltung)",
|
||||
"Nur für vom Benutzer in der aktuellen Nachricht ausdrücklich verlangte "
|
||||
"Unraid-Verwaltungsaktionen. Gemeinsam mit MUA read-only als geordnete "
|
||||
"Kette verwenden: Zustand prüfen, engste Änderung ausführen, Ergebnis "
|
||||
"read-only verifizieren. Für mehrere bestätigte Image-Updates immer den "
|
||||
"gebündelten, zustandserhaltenden Updateablauf verwenden.",
|
||||
),
|
||||
"navidrome-local": (
|
||||
"Navidrome (Musikbibliothek)",
|
||||
"Nur für die persönliche Navidrome-Musikbibliothek: Titel, Alben, Künstler, "
|
||||
"Playlists, Favoriten und Hörverlauf. Nicht für Sonarr/Radarr, allgemeine "
|
||||
"Websuche oder Audioausgabe auf dem KI-Host. Wegen des großen Werkzeugkatalogs "
|
||||
"nur bei Musikaufgaben aktivieren.",
|
||||
),
|
||||
"deemix-local": (
|
||||
"Deemix (bestehende Unraid-Instanz)",
|
||||
"Durchsucht Deezer über die vorhandene Deemix-Instanz auf Unraid und "
|
||||
"verwaltet deren Download-Queue. Keine zweite Deemix-Instanz. Schreibende "
|
||||
"Queue-Aktionen nur auf ausdrücklichen Benutzerauftrag; Status und Suche "
|
||||
"sind read-only.",
|
||||
),
|
||||
"github-local": (
|
||||
"GitHub Repository (offiziell, read-only)",
|
||||
"Für Repository-Suche, echte Datei-Inhalte und gezielte "
|
||||
"Code-Suche auf GitHub. Bei Fragen zu Implementierung, README, API-Routen oder "
|
||||
"Quellcode dieses Werkzeug statt allgemeiner Websuche verwenden. Keine Issues, "
|
||||
"Pull Requests, Actions, rekursiven Komplettbäume oder Schreibzugriffe. Bei einem "
|
||||
"konkreten Repository zuerst README beziehungsweise Wurzel einmal lesen, danach "
|
||||
"höchstens drei gezielte Code-Suchen und nur relevante Trefferdateien öffnen.",
|
||||
),
|
||||
"athena-operator-local": (
|
||||
"Athena Operator",
|
||||
"Zentrale Bedienebene für Athenas KI-Plattform: MCPs entwickeln und deployen, "
|
||||
"Docker-Dienste verwalten, Modelle laden und testen, Profile/OpenWebUI ändern, "
|
||||
"prüfen, dokumentieren, versionieren und Recovery erzeugen. Enthält außerdem ein "
|
||||
"breites, ausgabebegrenztes Terminal als Ausweg für neue Aufgaben einschließlich "
|
||||
"Docker, Git, HTTP und SSH zu konfigurierten Zielsystemen. Stromversorgung sowie "
|
||||
"Athenas SSH, LAN, WireGuard, Firewall, Boot, Kernel, Mounts und Partitionen bleiben "
|
||||
"blockiert, damit der entfernte Host erreichbar bleibt.",
|
||||
),
|
||||
}
|
||||
changed = len(connections) != before_count
|
||||
for connection in connections:
|
||||
if not isinstance(connection, dict):
|
||||
continue
|
||||
info = connection.get("info")
|
||||
if not isinstance(info, dict):
|
||||
info = {}
|
||||
connection["info"] = info
|
||||
identity = str(info.get("id", "")).lower()
|
||||
url = str(connection.get("url", "")).lower()
|
||||
if identity in descriptions:
|
||||
match = identity
|
||||
elif "192.168.1.2:3002" in url:
|
||||
match = "mua"
|
||||
elif "tinysearch:8000" in url:
|
||||
match = "web-general-local"
|
||||
elif "mike-ai-mcp-homeassistant" in url:
|
||||
match = "homeassistant-local"
|
||||
elif "mike-ai-mcp-arr" in url:
|
||||
match = "arr-local"
|
||||
elif "mike-ai-mcp-navidrome" in url:
|
||||
match = "navidrome-local"
|
||||
elif "mike-ai-mcp-github" in url:
|
||||
match = "github-local"
|
||||
elif "mike-ai-mcp-deemix" in url:
|
||||
match = "deemix-local"
|
||||
elif "mike-ai-mcp-athena-operator" in url:
|
||||
match = "athena-operator-local"
|
||||
else:
|
||||
continue
|
||||
name, description = descriptions[match]
|
||||
if info.get("name") != name or info.get("description") != description:
|
||||
info["name"] = name
|
||||
info["description"] = description
|
||||
changed = True
|
||||
if match == "github-local":
|
||||
bounded_config = dict(connection.get("config") or {})
|
||||
bounded_config["enable"] = True
|
||||
bounded_config["function_name_filter_list"] = (
|
||||
"search_repositories,get_file_contents,search_code"
|
||||
)
|
||||
bounded_config.setdefault("access_grants", [])
|
||||
if connection.get("config") != bounded_config:
|
||||
connection["config"] = bounded_config
|
||||
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 None and pathlib.Path(mua_mcp_env_file).is_file():
|
||||
mua_env = {}
|
||||
for raw_line in pathlib.Path(mua_mcp_env_file).read_text().splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
mua_env[key.strip()] = value.strip().strip('"').strip("'")
|
||||
mua_url = mua_env.get("MUA_MCP_URL", "")
|
||||
mua_token = mua_env.get("MUA_MCP_BEARER_TOKEN", "")
|
||||
if mua_url and mua_token:
|
||||
name, description = descriptions["mua"]
|
||||
mua_source = {
|
||||
"url": mua_url,
|
||||
"path": "",
|
||||
"type": "mcp",
|
||||
"auth_type": "bearer",
|
||||
"headers": None,
|
||||
"key": mua_token,
|
||||
"config": {"enable": True, "access_grants": []},
|
||||
"info": {"id": "mua", "name": name, "description": description},
|
||||
}
|
||||
connections.append(mua_source)
|
||||
changed = True
|
||||
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_files_inventory",
|
||||
"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(
|
||||
isinstance(connection, dict)
|
||||
and (
|
||||
str(connection.get("url", "")).lower()
|
||||
== "http://mike-ai-mcp-navidrome:3000/mcp"
|
||||
or str((connection.get("info") or {}).get("id", "")).lower()
|
||||
== "navidrome-local"
|
||||
)
|
||||
for connection in connections
|
||||
):
|
||||
name, description = descriptions["navidrome-local"]
|
||||
connections.append(
|
||||
{
|
||||
"url": "http://mike-ai-mcp-navidrome:3000/mcp",
|
||||
"path": "",
|
||||
"type": "mcp",
|
||||
"auth_type": "none",
|
||||
"headers": None,
|
||||
"key": "",
|
||||
"config": {"enable": True, "access_grants": []},
|
||||
"info": {
|
||||
"id": "navidrome-local",
|
||||
"name": name,
|
||||
"description": description,
|
||||
},
|
||||
}
|
||||
)
|
||||
changed = True
|
||||
if deemix_enabled and not any(
|
||||
isinstance(connection, dict)
|
||||
and (
|
||||
str(connection.get("url", "")).lower()
|
||||
== "http://mike-ai-mcp-deemix:8000/mcp"
|
||||
or str((connection.get("info") or {}).get("id", "")).lower()
|
||||
== "deemix-local"
|
||||
)
|
||||
for connection in connections
|
||||
):
|
||||
name, description = descriptions["deemix-local"]
|
||||
connections.append(
|
||||
{
|
||||
"url": "http://mike-ai-mcp-deemix:8000/mcp",
|
||||
"path": "",
|
||||
"type": "mcp",
|
||||
"auth_type": "none",
|
||||
"headers": None,
|
||||
"key": "",
|
||||
"config": {"enable": True, "access_grants": []},
|
||||
"info": {"id": "deemix-local", "name": name, "description": description},
|
||||
}
|
||||
)
|
||||
changed = True
|
||||
if not any(
|
||||
isinstance(connection, dict)
|
||||
and (
|
||||
str(connection.get("url", "")).lower() == "http://tinysearch:8000/mcp"
|
||||
or str((connection.get("info") or {}).get("id", "")).lower()
|
||||
== "web-general-local"
|
||||
)
|
||||
for connection in connections
|
||||
):
|
||||
name, description = descriptions["web-general-local"]
|
||||
connections.append(
|
||||
{
|
||||
"url": "http://tinysearch:8000/mcp",
|
||||
"path": "",
|
||||
"type": "mcp",
|
||||
"auth_type": "none",
|
||||
"headers": None,
|
||||
"key": "",
|
||||
"config": {"enable": True, "access_grants": []},
|
||||
"info": {
|
||||
"id": "web-general-local",
|
||||
"name": name,
|
||||
"description": description,
|
||||
},
|
||||
}
|
||||
)
|
||||
changed = True
|
||||
if not any(
|
||||
isinstance(connection, dict)
|
||||
and (
|
||||
str(connection.get("url", "")).lower()
|
||||
== "http://mike-ai-mcp-athena-operator:8000/mcp"
|
||||
or str((connection.get("info") or {}).get("id", "")).lower()
|
||||
== "athena-operator-local"
|
||||
)
|
||||
for connection in connections
|
||||
):
|
||||
name, description = descriptions["athena-operator-local"]
|
||||
connections.append(
|
||||
{
|
||||
"url": "http://mike-ai-mcp-athena-operator:8000/mcp",
|
||||
"path": "",
|
||||
"type": "mcp",
|
||||
"auth_type": "none",
|
||||
"headers": None,
|
||||
"key": "",
|
||||
"config": {"enable": True, "access_grants": []},
|
||||
"info": {
|
||||
"id": "athena-operator-local",
|
||||
"name": name,
|
||||
"description": description,
|
||||
},
|
||||
}
|
||||
)
|
||||
changed = True
|
||||
if github_enabled and not any(
|
||||
isinstance(connection, dict)
|
||||
and (
|
||||
str(connection.get("url", "")).lower()
|
||||
== "http://mike-ai-mcp-github:8000/mcp"
|
||||
or str((connection.get("info") or {}).get("id", "")).lower()
|
||||
== "github-local"
|
||||
)
|
||||
for connection in connections
|
||||
):
|
||||
name, description = descriptions["github-local"]
|
||||
connections.append(
|
||||
{
|
||||
"url": "http://mike-ai-mcp-github:8000/mcp",
|
||||
"path": "",
|
||||
"type": "mcp",
|
||||
"auth_type": "none",
|
||||
"headers": None,
|
||||
"key": "",
|
||||
"config": {"enable": True, "access_grants": []},
|
||||
"info": {
|
||||
"id": "github-local",
|
||||
"name": name,
|
||||
"description": description,
|
||||
},
|
||||
}
|
||||
)
|
||||
changed = True
|
||||
if changed:
|
||||
con.execute(
|
||||
"""
|
||||
insert into config (key,value,updated_at) values (?,?,?)
|
||||
on conflict(key) do update set
|
||||
value=excluded.value,
|
||||
updated_at=excluded.updated_at
|
||||
""",
|
||||
(
|
||||
"tool_server.connections",
|
||||
json.dumps(connections, ensure_ascii=False),
|
||||
now,
|
||||
),
|
||||
)
|
||||
print(
|
||||
"OpenWebUI konfiguriert: Default Off=10, Thinking=20, Auto Tool Selector=25, "
|
||||
"Stability Guard=30, Secret Redaction=40, Spoken Tool Status=80, Local Metrics=90, "
|
||||
"Quick Actions=100, Folgefragen=aus, Piper-TTS=aktiv, Werkzeugwahl=optimiert"
|
||||
)
|
||||
con.close()
|
||||
PY
|
||||
|
||||
python3 "$ROOT/platform/mcp/sync-clients.py" \
|
||||
--registry "$ROOT/config/mcp-registry.json" \
|
||||
--openwebui-db "$db"
|
||||
|
||||
if [[ $was_running == true ]]; then
|
||||
docker start "$CONTAINER" >/dev/null
|
||||
deadline=$((SECONDS + 180))
|
||||
@@ -557,4 +114,4 @@ if [[ $was_running == true ]]; then
|
||||
done
|
||||
fi
|
||||
trap - EXIT
|
||||
printf 'Datenbanksicherung: %s\n' "$backup"
|
||||
printf 'OPENWEBUI_SYNC_OK backup=%s\n' "$backup"
|
||||
|
||||
Reference in New Issue
Block a user