Multi-Hoster-Upload/docs/remote-diagnostics-setup.md
Administrator 0c6c502aab feat(diagnostics): network bind + fail-closed IP allowlist + host-in-code (Tailscale, like rd-diagnostics-mcp)
Matches the Real-Debrid-Downloader's rd-diagnostics-mcp model so the read-only
diagnostics agent is reachable over Tailscale (or any private tunnel) the same way
the downloader is, instead of requiring an SSH local-forward.

- lib/ip-allowlist.js (NEW): fail-closed IP allowlist — normalizeIp strips
  ::ffff:, loopback is always allowed, an empty allowlist accepts loopback ONLY
  (fail-closed), exact IP + CIDR (incl. the Tailscale CGNAT range 100.64.0.0/10) +
  wildcard rules. The real socket peer IP is the authority (never a forwarded header).
- remote-server.js: rejects non-allowlisted peers at connection (close 4005). Opt-in
  via config.allowlist (the existing remote-control server, which passes none, is
  unaffected). Loopback always passes, so local + SSH-forward use keeps working.
- Two bind modes (config diagnostics.bindMode): "local" -> 127.0.0.1 (default),
  "network" -> 0.0.0.0 but ONLY when a non-empty allowlist is set (else it stays
  loopback, fail-closed). The allowlist + token gate access; the tunnel
  (Tailscale/WireGuard) is the confidentiality layer (transport is still plaintext ws://).
- The connection code now carries the host: mhu1_<base64url{v,h,p,t,n,fp?,s?}>. The
  gateway decode is tolerant of the legacy {port,token,label} keys; connect_server
  takes the host from the code (host arg is an optional override). Proven end-to-end:
  the integration harness now connects with NO host arg and resolves it from the code.
- Renderer: Sichtbarkeit selector (local/network), public-host input with
  suggested-host chips (os.networkInterfaces — the Tailscale IP shows up there),
  allowlist textarea (network mode), and network-requires-allowlist validation.
- main.js: bindMode->host, getSuggestedRemoteHosts, host-in-code, allowlist plumbed
  into startDiagnosticAgent + the diagnostics IPC (get/save/status).
- docs: rewritten for Tailscale (set the allowlist to your tailnet, put the Tailscale
  IP/MagicDNS in the code address — no SSH forward needed).

This supersedes the v3.3.85 hard loopback-lock with the downloader's allowlist model.
Tests: lib/ip-allowlist (8) + remote-server allowlist wiring/loopback (2) + gateway
decode (host short-key + legacy tolerance). 393 app tests + 9 gateway tests + e2e +
host-in-code integration + adversarial all green; lint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:32:29 +02:00

6.5 KiB

Remote Diagnostics Setup (read-only)

This guide explains how to let Claude Code run read-only diagnostics against a Multi-Hoster-Uploader instance running on a remote Windows server, through the local stdio MCP gateway in gateway/.

The gateway is an MCP server to Claude and a WebSocket client to a diagnostic agent inside the app. It never touches the screen, never injects input, and never writes anything on the server.


1. Enable diagnostics on the server and copy the code

On the server (the machine running the app):

  1. Open the app's Settings.
  2. Enable "Diagnose-Zugriff".
  3. Copy the connection code. It looks like mhu1_<base64url...>.

The code carries the host, port and a one-time auth token (mhu1_<base64url{v,h,p,t,n}>). The bridge dials the host from the code, so you usually just hand over the code. Treat the code as a secret: anyone with the code and network reach to the agent can read diagnostics.

Two visibility modes (Settings → Diagnose-Zugriff → Sichtbarkeit):

  • Nur lokal (default): the agent binds to 127.0.0.1. Reach it through a tunnel (next step).
  • Im Netzwerk: the agent binds to 0.0.0.0 but is gated by a fail-closed IP allowlist — only source IPs/CIDRs you list may connect (loopback is always allowed), in addition to the token. An empty allowlist means loopback only. This is the mode to use with Tailscale: set the allowlist to your tailnet (e.g. 100.64.0.0/10) and put the server's Tailscale IP / MagicDNS name into the code address — then the bridge connects straight over the tailnet, no SSH forward needed.

Transport note: the agent speaks plaintext ws://. The token and the diagnostic data are not encrypted on the wire by the agent itself — confidentiality comes from the tunnel (Tailscale/WireGuard/SSH) you reach it through. In network mode the IP allowlist + token are the access gate; only bind to the network behind a private tunnel you trust (a tailnet, a VPN, or a trusted LAN). (A future build may add wss/TLS with cert pinning via an fp field in the code; it is not active today.)


2. Reach the agent over a tunnel

The agent's default port is 9110. Pick the path that matches your setup.

Put every server and your gateway machine on the same tailnet. On each server, set Sichtbarkeit = Im Netzwerk, allowlist your tailnet (100.64.0.0/10, or the specific Tailscale IPs you'll connect from), and set the code address to that server's Tailscale IP or MagicDNS name. The bridge then connects straight to <tailscale-name>:9110 — WireGuard (Tailscale) encrypts the transport, and the allowlist + token gate access. No SSH forward, no per-session tunnel command.

SSH local port-forward (keep the agent loopback-only)

With Sichtbarkeit = Nur lokal:

ssh -L 9110:127.0.0.1:9110 user@server

Leave that session open. Now 127.0.0.1:9110 on your machine is forwarded to the server's loopback. The code address is 127.0.0.1 (your local end of the tunnel).

WireGuard (manual, alternative)

Bring up a WireGuard tunnel and either bind the agent to the network with the peer's WG IP in the allowlist, or forward loopback over the tunnel and connect to 127.0.0.1.

In Nur lokal mode the agent is unreachable except through a tunnel to loopback. In Im Netzwerk mode the fail-closed IP allowlist (plus the token) is the access gate — only bind to the network behind a tunnel/VPN you trust (a tailnet, WireGuard, or a trusted LAN). The transport is plaintext; the tunnel is what encrypts it.


3. Install the gateway and register it with Claude (one time)

On the machine running Claude Code:

cd gateway
npm install

Register the gateway as an MCP server (one time):

claude mcp add --transport stdio mhu-diag -- node "C:\Users\ploet\Desktop\Claude Projekte\multi-hoster-uploader\gateway\index.js"

Adjust the absolute path if your checkout lives elsewhere.


4. Usage

With the tunnel up, tell Claude:

server prod-3 at 127.0.0.1, code mhu1_<...>

Claude will:

  1. call connect_server(code:"mhu1_...", host:"127.0.0.1"),
  2. immediately read the app version via get_system_info,
  3. remember the server under its label in registry.json.

Next time you can just say:

connect_server(label:"prod-3")

with no code — the gateway dials it from the registry. Then ask Claude "what's wrong?" — it will typically start with server_health, the one-shot hub that aggregates errors, queue, rotation and system info in a single call. Other read-only tools: read_log, list_logs, list_errors, get_queue_state, get_history, get_config_redacted, get_rotation_state, get_app_events.


5. Failure → meaning

When a connect or a request fails, the gateway returns a human-readable cause. Quick reference:

Symptom / signal What it means / what to do
ECONNREFUSED app not running / wrong port / inbound firewall closed
ETIMEDOUT firewall DROP / NAT not forwarded / tunnel down
close code 4001 auth timeout, retry
close code 4002 stale or rotated code — re-copy the current code from the server
close code 4003 brute-force lockout, wait 60s
connected but no auth-ok old app version without the diagnostic agent — update that server
wss fingerprint mismatch reserved for the future TLS mode (not active today) — re-copy the code

If you tunnel and still get ECONNREFUSED, check that the SSH session is up and that the agent is actually listening on 127.0.0.1:9110 on the server (Diagnose-Zugriff enabled).


6. What this is — and is not

  • It is READ-ONLY. It reads logs, errors, queue/history, redacted config, rotation and system info. No screen capture. No input injection. No writes or config changes.
  • The code is a secret (it carries the auth token). Don't paste it into public channels.
  • gateway/registry.json stores tokens for remembered servers and is git-ignored — never commit it.