diff --git a/README.md b/README.md index e202f5b..eee33ed 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,11 @@ Details, Prüfschritte und der exakte Sicherungsumfang stehen in Die MCPHub-Installation, Endpunkte und der schrittweise Rückbau der alten Athena-MCPs stehen in [`platform/mcphub/README.md`](platform/mcphub/README.md). +Der eigenständig betreibbare Sonarr-/Radarr-Container einschließlich +Debian-Slim-Installer und Unraid-Template liegt unter +[`services/arr-mcp/`](services/arr-mcp/README.md). Er ist für die schrittweise +Ablösung des bisherigen MCPHub-Prozesses vorbereitet, wird durch Athenas +Standardinstallation aber nicht automatisch gestartet. Für Installation oder Wiederherstellung des Hermes-Gateways auf Unraid liegt unter [`config/unraid-templates/my-Hermes-Agent-Official.xml`](config/unraid-templates/my-Hermes-Agent-Official.xml) ein DockerMan-Template, das unverändert das offizielle Nous-Image verwendet. diff --git a/config/unraid-templates/my-ARR-MCP.xml b/config/unraid-templates/my-ARR-MCP.xml new file mode 100644 index 0000000..8f57bd1 --- /dev/null +++ b/config/unraid-templates/my-ARR-MCP.xml @@ -0,0 +1,23 @@ + + + ARR-MCP + mike-ai/arr-mcp:1.0.0 + bridge + sh + false + https://git.casaderoll.de/michael/AI-Profile-Router + https://git.casaderoll.de/michael/AI-Profile-Router/src/branch/main/services/arr-mcp/README.md + Eigenständiger Sonarr- und Radarr-MCP für Hermes und andere MCP-Clients. Das lokale Image wird reproduzierbar aus dem privaten Git gebaut. Der Container nutzt kompakte, modellfreundliche Werkzeuge, ein begrenztes Codec-Inventar und ticketgebundene Sonarr-Schreibaktionen. Konfiguration und Schlüssel liegen ausschließlich in Unraid-Appdata. + AI: + + false + https://raw.githubusercontent.com/Sonarr/Sonarr/develop/Logo/256.png + --read-only --cap-drop=ALL --security-opt=no-new-privileges --pids-limit=256 --tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m --env-file=/mnt/nvme-storage/appdata/ARR-MCP/arr-mcp.env + + + + + + Das lokale Image mike-ai/arr-mcp:1.0.0 muss vorher mit services/arr-mcp/install-on-unraid.sh gebaut worden sein. + 8207 + diff --git a/dev/test_sonarr_release_grab.py b/dev/test_sonarr_release_grab.py old mode 100644 new mode 100755 index aec38f9..448d23c --- a/dev/test_sonarr_release_grab.py +++ b/dev/test_sonarr_release_grab.py @@ -1,6 +1,46 @@ -import unittest +#!/usr/bin/env python3 +"""Focused offline tests for the model-oriented Sonarr overlay.""" -from arr_mcp.mcp import mcp_sonarr +from __future__ import annotations + +import importlib.util +import sys +import types +import unittest +from pathlib import Path + + +SOURCE = Path(__file__).parents[1] / "platform/mcp/patches/mcp_sonarr.py" + + +def load_module(): + async def run_blocking(function, *args, **kwargs): + kwargs.pop("service", None) + return function(*args, **kwargs) + + def dispatch(client, action, kwargs, **_options): + return getattr(client, action)(**kwargs) + + utilities = types.ModuleType("agent_utilities.mcp_utilities") + utilities.dispatch = dispatch + utilities.run_blocking = run_blocking + sys.modules["agent_utilities"] = types.ModuleType("agent_utilities") + sys.modules["agent_utilities.mcp_utilities"] = utilities + fastmcp = types.ModuleType("fastmcp") + fastmcp.FastMCP = object + sys.modules["fastmcp"] = fastmcp + pydantic = types.ModuleType("pydantic") + pydantic.Field = lambda *args, **kwargs: kwargs.get("default") + sys.modules["pydantic"] = pydantic + auth = types.ModuleType("arr_mcp.auth") + auth.get_sonarr_client = lambda: None + sys.modules["arr_mcp"] = types.ModuleType("arr_mcp") + sys.modules["arr_mcp.auth"] = auth + spec = importlib.util.spec_from_file_location("sonarr_patch", SOURCE) + module = importlib.util.module_from_spec(spec) + assert spec.loader + spec.loader.exec_module(module) + return module class FakeSonarrClient: @@ -29,24 +69,26 @@ class FakeSonarrClient: {"id": 2, "seasonNumber": 7, "episodeNumber": 2, "hasFile": False}, ] - def post_release(self, data): - self.posted.append(data) - return data + def post_release(self, data=None, **kwargs): + payload = data if data is not None else kwargs + self.posted.append(payload) + return payload class ReleaseGrabTests(unittest.IsolatedAsyncioTestCase): def setUp(self) -> None: - mcp_sonarr._APPROVALS.clear() + self.module = load_module() + self.module._APPROVALS.clear() async def test_exact_release_requires_preview_and_ticket(self) -> None: client = FakeSonarrClient() scope = {"series_id": 42, "season_number": 7, "guid": "exact-guid"} - preview = await mcp_sonarr._preview_release_grab(client, scope) + preview = await self.module._preview_release_grab(client, scope) self.assertFalse(preview["download_started"]) self.assertEqual(preview["existing_episode_files_in_season"], 1) self.assertTrue(preview["approval_ticket"]) - result = await mcp_sonarr._grab_release( + result = await self.module._grab_release( client, {**scope, "confirm": True, "approval_ticket": preview["approval_ticket"]}, ) @@ -58,18 +100,18 @@ class ReleaseGrabTests(unittest.IsolatedAsyncioTestCase): async def test_rejected_release_needs_force_in_preview(self) -> None: client = FakeSonarrClient(rejected=True) scope = {"series_id": 42, "season_number": 7, "guid": "exact-guid"} - blocked = await mcp_sonarr._preview_release_grab(client, scope) + blocked = await self.module._preview_release_grab(client, scope) self.assertTrue(blocked["force_required"]) self.assertIsNone(blocked["approval_ticket"]) - approved = await mcp_sonarr._preview_release_grab(client, {**scope, "force": True}) + approved = await self.module._preview_release_grab(client, {**scope, "force": True}) self.assertFalse(approved["force_required"]) self.assertTrue(approved["approval_ticket"]) async def test_guid_must_still_match_current_sonarr_results(self) -> None: client = FakeSonarrClient() with self.assertRaisesRegex(ValueError, "no longer present"): - await mcp_sonarr._preview_release_grab( + await self.module._preview_release_grab( client, {"series_id": 42, "season_number": 7, "guid": "different-guid"}, ) @@ -91,7 +133,7 @@ class ReleaseGrabTests(unittest.IsolatedAsyncioTestCase): "releaseGroup": "HQC", }, ] - result = await mcp_sonarr._search_releases( + result = await self.module._search_releases( client, { "series_id": 42, diff --git a/services/arr-mcp/Dockerfile b/services/arr-mcp/Dockerfile new file mode 100644 index 0000000..6a0e747 --- /dev/null +++ b/services/arr-mcp/Dockerfile @@ -0,0 +1,37 @@ +FROM python:3.13-slim AS builder + +ARG ARR_MCP_VERSION=1.0.1 + +RUN python -m venv /opt/arr-mcp/venv \ + && /opt/arr-mcp/venv/bin/pip install --no-cache-dir --upgrade pip \ + && /opt/arr-mcp/venv/bin/pip install --no-cache-dir "arr-mcp[mcp]==${ARR_MCP_VERSION}" + +COPY platform/mcp/patches/mcp_sonarr.py /tmp/mcp_sonarr.py +COPY platform/mcp/patches/mcp_radarr.py /tmp/mcp_radarr.py + +RUN site_packages="$(/opt/arr-mcp/venv/bin/python -c 'import site; print(site.getsitepackages()[0])')" \ + && install -m 0644 /tmp/mcp_sonarr.py "$site_packages/arr_mcp/mcp/mcp_sonarr.py" \ + && install -m 0644 /tmp/mcp_radarr.py "$site_packages/arr_mcp/mcp/mcp_radarr.py" \ + && /opt/arr-mcp/venv/bin/python -m compileall -q "$site_packages/arr_mcp/mcp" + +FROM python:3.13-slim + +RUN groupadd --system --gid 10001 arrmcp \ + && useradd --system --uid 10001 --gid 10001 --no-create-home arrmcp + +COPY --from=builder /opt/arr-mcp /opt/arr-mcp +COPY services/arr-mcp/run.sh /usr/local/bin/run-arr-mcp + +ENV PATH="/opt/arr-mcp/venv/bin:${PATH}" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +RUN chmod 0755 /usr/local/bin/run-arr-mcp + +USER 10001:10001 +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD python -c "import socket; s=socket.create_connection(('127.0.0.1',8000),3); s.close()" + +ENTRYPOINT ["/usr/local/bin/run-arr-mcp"] diff --git a/services/arr-mcp/README.md b/services/arr-mcp/README.md new file mode 100644 index 0000000..6d9227a --- /dev/null +++ b/services/arr-mcp/README.md @@ -0,0 +1,155 @@ +# ARR-MCP auf Unraid + +Dieser Ordner baut **einen eigenständigen Container nur für Sonarr und Radarr**. +Hermes, Pi Coding und andere MCP-Clients erreichen ihn anschließend über +`http://:8207/mcp`. + +## Warum dieser Fork bleibt + +Geprüft am 27. August 2026: + +- [`Knuckles-Team/arr-mcp`](https://github.com/Knuckles-Team/arr-mcp) 2.1.0 + ist der moderne Upstream unseres Pakets, stellt + in der kompakten Oberfläche aber weiterhin generische `*_action`-Werkzeuge + mit frei wählbaren API-Methoden bereit. Das führte bei lokalen Modellen zu + falschen Aktionen, unnötigen Schemaabfragen und großen Antworten. +- Andere öffentliche ARR-MCPs bieten teilweise mehr Dienste oder eine eigene + Weboberfläche, ersetzen aber nicht unser kompaktes Radarr-Codec-Inventar und + den an eine Vorschau gebundenen Sonarr-Freigabeablauf. + +Wir bleiben deshalb vorläufig bei `arr-mcp` 1.0.1 plus zwei kleinen, +versionierten Patches. Ein späterer Wechsel ist sinnvoll, sobald ein Upstream +diese Eigenschaften ohne lokale Anpassungen anbietet. + +## Besondere Werkzeuge + +- `radarr_movie_codec_inventory`: kompakte, paginierte Liste mit Codec, + Auflösung, Sprachen und Dateigröße; keine riesigen Radarr-Rohantworten. +- `sonarr_action`: begrenzte, kompakte Sonarr-Aktionen wie `find_series`, + `get_season_summary` und `search_releases`. +- Sonarr-Schreibaktionen sind standardmäßig abgeschaltet. Im Schreibmodus + benötigen `start_episode_search` und `grab_release` zuerst eine passende + Vorschau, danach eine ausdrückliche Freigabe und ein kurzlebiges Ticket. + +## Empfohlene Installation auf Unraid + +Im vollständigen Checkout des Repositories als root: + +```bash +./services/arr-mcp/install-on-unraid.sh +``` + +Beim ersten Aufruf entsteht: + +```text +/mnt/nvme-storage/appdata/ARR-MCP/arr-mcp.env +``` + +Dort Sonarr- und Radarr-Schlüssel eintragen. Danach denselben Installationsbefehl +erneut ausführen. Das Skript: + +1. baut das lokale Image `mike-ai/arr-mcp:1.0.0` aus `python:3.13-slim`, +2. startet ausschließlich den Container `ARR-MCP` im normalen Bridge-Netz, +3. wartet auf den Healthcheck und +4. installiert das DockerMan-Template für spätere Bedienung in der Unraid-GUI. + +Es liest oder druckt keine Schlüssel. Eine vorhandene Konfiguration wird nie +überschrieben. + +### Kurzer Auftrag für Hermes/Qwen + +```text +Klone auf Unraid das Repository +ssh://git@192.168.1.2:33/michael/AI-Profile-Router.git, lies vollständig +services/arr-mcp/README.md und installiere ausschließlich nach dem dort +dokumentierten Weg. Verwende services/arr-mcp/install-on-unraid.sh. Zeige keine +API-Schlüssel an und überschreibe keine vorhandene arr-mcp.env. Stoppe, falls +Werte fehlen, und sage mir nur, welche Variablennamen ich eintragen muss. +Entferne den bisherigen ARR-Endpunkt erst nach einem erfolgreichen Healthcheck +und MCP-Handshake des neuen Containers. +``` + +## Installation in einem vorhandenen Debian-Slim-Container + +Nur verwenden, wenn bereits bewusst ein nackter Debian-Slim-Container mit dem +kompletten Git-Checkout läuft: + +```bash +./services/arr-mcp/install-in-debian-slim.sh +``` + +Danach startet `/usr/local/bin/run-arr-mcp` den Server. Dieser Weg funktioniert, +ist aber weniger reproduzierbar als der Dockerfile-Build: Eine Neuerstellung des +nackten Containers entfernt die Installation. Für den Produktivbetrieb deshalb +den ersten Weg verwenden. + +## Hermes oder anderer MCP-Client + +Direkte Registrierung: + +```yaml +mcp_servers: + arr: + url: http://192.168.1.2:8207/mcp + timeout: 600 +``` + +Nach dem Eintragen die MCP-Liste des Clients neu laden beziehungsweise einen +neuen Chat öffnen. Sonarr und Radarr erscheinen als Werkzeuge desselben +Fach-MCPs; sie sind nicht Bestandteil des Hermes-Containers. + +## Schreiben aktivieren + +In `arr-mcp.env`: + +```text +ARR_MCP_WRITE=1 +``` + +Danach nur diesen Container neu starten. Selbst dann erlaubt der Sonarr-Patch +nur die dokumentierten, ticketgebundenen Aktionen. Radarr besitzt derzeit noch +keinen gleichwertigen Freigabeablauf; Änderungen dort nur auf einen eindeutigen +Benutzerauftrag ausführen. + +## Update und Test + +Nach einem Git-Update denselben Befehl erneut ausführen: + +```bash +./services/arr-mcp/install-on-unraid.sh +``` + +Status und Logs: + +```bash +docker inspect --format '{{.State.Health.Status}}' ARR-MCP +docker logs --tail 100 ARR-MCP +``` + +Die Offline-Tests des Forks: + +```bash +python3 dev/test_radarr_patch.py +python3 -m unittest dev/test_sonarr_release_grab.py +``` + +## Deinstallation + +```bash +./services/arr-mcp/install-on-unraid.sh uninstall +``` + +Der Container verschwindet, während die Appdata-Konfiguration als Rückfall +erhalten bleibt. Erst wenn sie wirklich nicht mehr benötigt wird, kann +`/mnt/nvme-storage/appdata/ARR-MCP` separat gelöscht werden. + +## Regeln für Hermes/Qwen + +1. Diese README vollständig lesen. +2. Keine eigene ARR-Implementierung und keinen zweiten Sonarr-/Radarr-Dienst + erstellen. +3. Ausschließlich `install-on-unraid.sh` verwenden; keine Befehle improvisieren. +4. Vorhandene `arr-mcp.env` weder anzeigen noch überschreiben. +5. Nach Installation Healthcheck und MCP-Handshake prüfen. +6. Den alten MCPHub-/Athena-Endpunkt erst entfernen, wenn der neue Endpunkt + nachweislich funktioniert. diff --git a/services/arr-mcp/arr-mcp.env.example b/services/arr-mcp/arr-mcp.env.example new file mode 100644 index 0000000..c259df3 --- /dev/null +++ b/services/arr-mcp/arr-mcp.env.example @@ -0,0 +1,28 @@ +# Nur diese Datei kopieren und die beiden Schlüssel eintragen. Keine echten +# Zugangsdaten in Git committen. + +SONARRTOOL=True +SONARR_BASE_URL=http://192.168.1.2:8989 +SONARR_TOKEN=CHANGE_ME + +RADARRTOOL=True +RADARR_BASE_URL=http://192.168.1.2:7878 +RADARR_TOKEN=CHANGE_ME + +# Andere *arr-Dienste sind in diesem Container absichtlich deaktiviert. +LIDARRTOOL=False +PROWLARRTOOL=False +BAZARRTOOL=False +SEERRTOOL=False +CHAPTARRTOOL=False + +# Zwei kompakte Werkzeuge statt hunderter Einzeltools. +MCP_TOOL_MODE=condensed + +# Standardmäßig rein lesend. Erst nach bewusster Entscheidung auf 1 setzen. +# Auch im Schreibmodus bleiben nur die eng begrenzten, ticketgebundenen +# Sonarr-Aktionen unseres Patches erlaubt. +ARR_MCP_WRITE=0 + +ARR_MCP_HOST=0.0.0.0 +ARR_MCP_PORT=8000 diff --git a/services/arr-mcp/compose.yaml b/services/arr-mcp/compose.yaml new file mode 100644 index 0000000..b8fc558 --- /dev/null +++ b/services/arr-mcp/compose.yaml @@ -0,0 +1,29 @@ +services: + arr-mcp: + build: + context: ../.. + dockerfile: services/arr-mcp/Dockerfile + args: + ARR_MCP_VERSION: "1.0.1" + image: mike-ai/arr-mcp:1.0.0 + container_name: ARR-MCP + restart: unless-stopped + network_mode: bridge + env_file: + - ${ARR_MCP_ENV_FILE:-/mnt/nvme-storage/appdata/ARR-MCP/arr-mcp.env} + ports: + - "${ARR_MCP_PUBLIC_PORT:-8207}:8000" + read_only: true + tmpfs: + - /tmp:rw,noexec,nosuid,nodev,size=64m + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + pids_limit: 256 + healthcheck: + test: ["CMD", "python", "-c", "import socket; s=socket.create_connection(('127.0.0.1',8000),3); s.close()"] + interval: 30s + timeout: 5s + start_period: 20s + retries: 3 diff --git a/services/arr-mcp/install-in-debian-slim.sh b/services/arr-mcp/install-in-debian-slim.sh new file mode 100755 index 0000000..fe6c2e3 --- /dev/null +++ b/services/arr-mcp/install-in-debian-slim.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Installation innerhalb eines bereits laufenden Debian-Slim-Containers. +# Der empfohlene, reproduzierbare Weg bleibt der Dockerfile-Build. +set -Eeuo pipefail + +[[ $EUID -eq 0 ]] || { echo "Bitte im Container als root ausführen." >&2; exit 1; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SONARR_PATCH="$REPO_ROOT/platform/mcp/patches/mcp_sonarr.py" +RADARR_PATCH="$REPO_ROOT/platform/mcp/patches/mcp_radarr.py" + +[[ -f $SONARR_PATCH && -f $RADARR_PATCH ]] || { + echo "Die Installation muss aus einem vollständigen Checkout dieses Repositories laufen." >&2 + exit 2 +} + +need_python_packages=0 +venv_check_dir="$(mktemp -d)" +if ! command -v python3 >/dev/null 2>&1 \ + || ! python3 -m venv "$venv_check_dir/check" >/dev/null 2>&1; then + need_python_packages=1 +fi +rm -rf "$venv_check_dir" + +if (( need_python_packages )); then + command -v apt-get >/dev/null 2>&1 || { + echo "Dieser Installer erwartet Debian/Ubuntu mit apt-get." >&2 + exit 3 + } + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates python3 python3-pip python3-venv + rm -rf /var/lib/apt/lists/* +fi + +python3 -m venv /opt/arr-mcp/venv +/opt/arr-mcp/venv/bin/pip install --no-cache-dir --upgrade pip +/opt/arr-mcp/venv/bin/pip install --no-cache-dir "arr-mcp[mcp]==1.0.1" + +site_packages="$(/opt/arr-mcp/venv/bin/python -c 'import site; print(site.getsitepackages()[0])')" +install -m 0644 "$SONARR_PATCH" "$site_packages/arr_mcp/mcp/mcp_sonarr.py" +install -m 0644 "$RADARR_PATCH" "$site_packages/arr_mcp/mcp/mcp_radarr.py" +install -m 0755 "$SCRIPT_DIR/run.sh" /usr/local/bin/run-arr-mcp + +/opt/arr-mcp/venv/bin/python -m compileall -q "$site_packages/arr_mcp/mcp" +echo "ARR-MCP ist installiert. Start: /usr/local/bin/run-arr-mcp" diff --git a/services/arr-mcp/install-on-unraid.sh b/services/arr-mcp/install-on-unraid.sh new file mode 100755 index 0000000..245c885 --- /dev/null +++ b/services/arr-mcp/install-on-unraid.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Baut und verwaltet ausschließlich den eigenständigen ARR-MCP-Container. +set -Eeuo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +APPDATA_DIR=${ARR_MCP_APPDATA_DIR:-/mnt/nvme-storage/appdata/ARR-MCP} +ENV_FILE=${ARR_MCP_ENV_FILE:-$APPDATA_DIR/arr-mcp.env} +PUBLIC_PORT=${ARR_MCP_PUBLIC_PORT:-8207} +TEMPLATE_SOURCE="$REPO_ROOT/config/unraid-templates/my-ARR-MCP.xml" +TEMPLATE_DEST=${ARR_MCP_TEMPLATE_DEST:-/boot/config/plugins/dockerMan/templates-user/my-ARR-MCP.xml} + +usage() { + cat <<'EOF' +Verwendung: + ./services/arr-mcp/install-on-unraid.sh + ./services/arr-mcp/install-on-unraid.sh uninstall + +Der erste Lauf legt nur dann eine Beispielkonfiguration an, wenn noch keine +existiert. Echte Schlüssel werden niemals überschrieben oder ausgegeben. +EOF +} + +command=${1:-install} +case "$command" in + -h|--help) usage; exit 0 ;; + install|uninstall) ;; + *) usage >&2; exit 2 ;; +esac + +command -v docker >/dev/null 2>&1 || { echo "Docker wurde nicht gefunden." >&2; exit 1; } +docker compose version >/dev/null 2>&1 || { echo "Docker Compose v2 wurde nicht gefunden." >&2; exit 1; } + +compose=(docker compose -f "$SCRIPT_DIR/compose.yaml") + +if [[ $command == uninstall ]]; then + ARR_MCP_ENV_FILE="$ENV_FILE" ARR_MCP_PUBLIC_PORT="$PUBLIC_PORT" \ + "${compose[@]}" down --remove-orphans + echo "ARR-MCP wurde entfernt. Appdata und Konfiguration bleiben erhalten: $APPDATA_DIR" + exit 0 +fi + +install -d -m 0700 "$APPDATA_DIR" +if [[ ! -e $ENV_FILE ]]; then + install -m 0600 "$SCRIPT_DIR/arr-mcp.env.example" "$ENV_FILE" + echo "Beispielkonfiguration angelegt: $ENV_FILE" + echo "Bitte SONARR_TOKEN und RADARR_TOKEN eintragen und denselben Befehl erneut starten." + exit 3 +fi + +# Nur Vollständigkeit prüfen; Werte niemals ausgeben. +for key in SONARR_TOKEN RADARR_TOKEN; do + if ! grep -Eq "^${key}=[^[:space:]]+" "$ENV_FILE" \ + || grep -Eq "^${key}=CHANGE_ME$" "$ENV_FILE"; then + echo "In $ENV_FILE fehlt noch $key." >&2 + exit 3 + fi +done + +docker build \ + --build-arg ARR_MCP_VERSION=1.0.1 \ + -f "$SCRIPT_DIR/Dockerfile" \ + -t mike-ai/arr-mcp:1.0.0 \ + "$REPO_ROOT" + +ARR_MCP_ENV_FILE="$ENV_FILE" ARR_MCP_PUBLIC_PORT="$PUBLIC_PORT" \ + "${compose[@]}" up -d --no-build + +if [[ -d $(dirname "$TEMPLATE_DEST") ]]; then + install -m 0644 "$TEMPLATE_SOURCE" "$TEMPLATE_DEST" +fi + +for _ in {1..30}; do + health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' ARR-MCP 2>/dev/null || true) + [[ $health == healthy ]] && break + sleep 1 +done + +health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' ARR-MCP 2>/dev/null || true) +[[ $health == healthy ]] || { + echo "ARR-MCP wurde gestartet, ist aber noch nicht healthy (Status: ${health:-unbekannt})." >&2 + echo "Prüfen mit: docker logs --tail 100 ARR-MCP" >&2 + exit 1 +} + +echo "ARR-MCP läuft: http://$(hostname -I | awk '{print $1}'):$PUBLIC_PORT/mcp" diff --git a/services/arr-mcp/run.sh b/services/arr-mcp/run.sh new file mode 100755 index 0000000..79b6eaf --- /dev/null +++ b/services/arr-mcp/run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +set -eu + +exec /opt/arr-mcp/venv/bin/arr-mcp \ + --transport streamable-http \ + --host "${ARR_MCP_HOST:-0.0.0.0}" \ + --port "${ARR_MCP_PORT:-8000}" \ + --auth-type none