Neue Funktion, um Diagnose eines laufenden Servers aus der Ferne zu ermoeglichen:
Hilfe -> Remote-Support -> "Ferndiagnose (MCP)". Erzeugt einen Verbindungscode,
den der Assistent nutzt, um Status, Logs, Fehler und Accounts read-only zu lesen.
App-Seite:
- Live (re)startbarer Debug-Server ohne App-Neustart (restartDebugServer wartet auf
'close' + closeAllConnections, behandelt EADDRINUSE).
- IP-Allowlist (debug_allowlist.txt, exakte IP + CIDR), erzwungen VOR der Auth.
Fail-closed: Netzwerk-Bind (0.0.0.0) ohne Allowlist akzeptiert nur Loopback.
- One-Click Aktivieren/Aktualisieren/Deaktivieren + Token-Rotation (alter Code sofort
ungueltig). Sichtbarkeit waehlbar: "Nur lokal" (Tunnel-Empfehlung) vs "Im Netzwerk".
- Verbindungscode rddiag:v1:base64url({v,h,p,t,n?,fp?,s?}); oeffentlicher Host frei
waehlbar, Netzwerk-IPs als Schnellauswahl.
- Neue IPC: get/enable/disable/rotate Remote-Diagnostics; Controller-Methoden; Typen.
Bridge (tools/rd-diagnostics-mcp, standalone, KEINE App-Dependency):
- stdio MCP-Server (@modelcontextprotocol/sdk) mit 14 Tools, proxyt die bestehende
HTTP-Debug-API. Multi-Server ueber code/server/RDDIAG_CODE/RDDIAG_SERVERS.
- TLS-Fingerprint-Pinning auf secureConnect (vor Token-Versand), falls https genutzt.
- test/harness.mjs: faehrt einen Fake-Debug-Server hoch und treibt die Bridge als
echten stdio-Child per JSON-RPC -> voller Protokollpfad gruen.
Sicherheit (Audit): keine persistenten Secrets in den Logs der Debug-API (Passwoerter
redigiert, keine Debrid-Keys/aufgeloesten Download-URLs geloggt; settings/accounts
redigiert). Empfohlener Transport: Loopback + privater Tunnel; Direkt-Bind nur mit
Allowlist in vertrauenswuerdigen Netzen.
Tests: connection-code-Cross-Check (App-Encoder <-> Bridge-Decoder), Allowlist-Matrix
(Loopback, exakt, CIDR, fail-closed, Live-Restart). Volle Suite 905 gruen, tsc=6.
43 lines
1.8 KiB
TypeScript
43 lines
1.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { encodeConnectionCode } from "../src/main/connection-code";
|
|
import { decodeConnectionCode } from "../tools/rd-diagnostics-mcp/src/code.mjs";
|
|
|
|
describe("connection-code", () => {
|
|
it("round-trips through the bridge decoder", () => {
|
|
const code = encodeConnectionCode({ host: "203.0.113.5", port: 9868, token: "deadbeef", name: "server-1" });
|
|
expect(code.startsWith("rddiag:v1:")).toBe(true);
|
|
const decoded = decodeConnectionCode(code);
|
|
expect(decoded.host).toBe("203.0.113.5");
|
|
expect(decoded.port).toBe(9868);
|
|
expect(decoded.token).toBe("deadbeef");
|
|
expect(decoded.name).toBe("server-1");
|
|
expect(decoded.scheme).toBe("http");
|
|
});
|
|
|
|
it("carries https scheme and fingerprint when set", () => {
|
|
const code = encodeConnectionCode({
|
|
host: "diag.example.com",
|
|
port: 8443,
|
|
token: "abc",
|
|
scheme: "https",
|
|
fingerprint: "AA:BB:CC"
|
|
});
|
|
const decoded = decodeConnectionCode(code);
|
|
expect(decoded.scheme).toBe("https");
|
|
expect(decoded.fingerprint).toBe("AA:BB:CC");
|
|
});
|
|
|
|
it("omits scheme key for plain http (default)", () => {
|
|
const code = encodeConnectionCode({ host: "10.0.0.2", port: 9868, token: "t" });
|
|
const json = JSON.parse(Buffer.from(code.slice("rddiag:v1:".length).replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
|
|
expect(json.s).toBeUndefined();
|
|
expect(json).toMatchObject({ v: 1, h: "10.0.0.2", p: 9868, t: "t" });
|
|
});
|
|
|
|
it("rejects invalid input", () => {
|
|
expect(() => encodeConnectionCode({ host: "", port: 9868, token: "t" })).toThrow();
|
|
expect(() => encodeConnectionCode({ host: "h", port: 0, token: "t" })).toThrow();
|
|
expect(() => encodeConnectionCode({ host: "h", port: 9868, token: "" })).toThrow();
|
|
});
|
|
});
|