From d9657a5459548bfb2f439dc27d7b722728dbe5bd Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Fri, 19 Jun 2026 19:48:44 +0200 Subject: [PATCH] Ferndiagnose-Backup: auch Full-Backup-Import schreibt die MCP-Dateien MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Advisor-Fund: der Settings-only-Import stellte die Ferndiagnose-Einstellungen wieder her, der Full-Backup-Import (Download-Liste mitgesichert) aber NICHT — er liess die debug_*-Dateien unangetastet, und der nachfolgende Relaunch bootete mit den alten Werten. Damit waere "MCP-Settings im Backup" auf dem Full-Backup-Pfad still gescheitert. Fix: restoreMcpRemoteFromBackup nimmt jetzt restartNow. Settings-only ruft mit true (laufenden Server sofort neu laden), Full-Backup mit false (nur Dateien schreiben — der Relaunch-Boot liest sie via startDebugServer). Beide Pfade tragen die Sektion jetzt. Zusatz-Tests: normalizeSettings bewahrt backupIncludeMcp (der Dreh- und Angelpunkt — ohne das wuerde der Schalter bei jedem Save auf false zuruecksetzen und das Feature tot sein); Full-Backup-Write-Pfad schreibt debug_host/port/allowlist auf Platte ohne Restart (In-Memory bleibt stale → Boot uebernimmt). Suite 919 gruen, tsc=6. --- src/main/app-controller.ts | 13 +++++++++---- tests/backup-mcp.test.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/app-controller.ts b/src/main/app-controller.ts index 41df20d..10e7450 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -372,17 +372,20 @@ export class AppController { return this.getRemoteDiagnostics(); } - private restoreMcpRemoteFromBackup(section: unknown): void { + private restoreMcpRemoteFromBackup(section: unknown, restartNow: boolean): void { const restore = resolveMcpRemoteRestore(section); if (!restore) { return; } writeDebugServerConfig({ host: restore.host, port: restore.port, allowlist: restore.allowlist }); - void restartDebugServer().catch(() => {}); + if (restartNow) { + void restartDebugServer().catch(() => {}); + } this.audit("INFO", "Ferndiagnose-Einstellungen aus Backup wiederhergestellt", { port: restore.port ?? null, allowlistCount: restore.allowlist?.length ?? 0, - host: restore.host ?? "unveraendert" + host: restore.host ?? "unveraendert", + restartNow }); } @@ -827,7 +830,7 @@ public async checkDebridAccounts(): Promise { this.settings = restoredSettings; saveSettings(this.storagePaths, this.settings); this.manager.setSettings(this.settings, { suppressRetroactiveCleanup: true }); - this.restoreMcpRemoteFromBackup(parsed.mcpRemote); + this.restoreMcpRemoteFromBackup(parsed.mcpRemote, true); this.audit("INFO", "Backup importiert (nur Einstellungen)", { accountSummary: buildAccountSummary(this.settings) }); @@ -864,6 +867,8 @@ public async checkDebridAccounts(): Promise { resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); + this.restoreMcpRemoteFromBackup(parsed.mcpRemote, false); + this.manager.skipShutdownPersist = true; this.manager.blockAllPersistence = true; logger.info("Backup wiederhergestellt — App startet automatisch neu"); diff --git a/tests/backup-mcp.test.ts b/tests/backup-mcp.test.ts index 79963a9..5e868c1 100644 --- a/tests/backup-mcp.test.ts +++ b/tests/backup-mcp.test.ts @@ -7,6 +7,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { buildBackupPayload, resolveMcpRemoteRestore, BackupMcpRemote } from "../src/main/backup-payload"; import { defaultSettings } from "../src/main/constants"; +import { normalizeSettings } from "../src/main/storage"; import { startDebugServer, stopDebugServer, @@ -100,6 +101,14 @@ describe("backup mcpRemote export gating", () => { }); }); +describe("backupIncludeMcp settings persistence", () => { + it("normalizeSettings preserves backupIncludeMcp (the toggle survives save/load)", () => { + expect(normalizeSettings({ backupIncludeMcp: true } as unknown as AppSettings).backupIncludeMcp).toBe(true); + expect(normalizeSettings({ backupIncludeMcp: false } as unknown as AppSettings).backupIncludeMcp).toBe(false); + expect(normalizeSettings({} as unknown as AppSettings).backupIncludeMcp).toBe(false); + }); +}); + describe("resolveMcpRemoteRestore", () => { it("maps network + non-empty allowlist to 0.0.0.0", () => { expect(resolveMcpRemoteRestore({ allowlist: ["10.0.0.5"], port: 9868, hostMode: "network" })) @@ -167,4 +176,24 @@ describe("backup mcpRemote live restore round-trip", () => { await waitForReady(`http://127.0.0.1:${restorePort}/health?token=rt-secret`); }); + + it("full-backup path writes the debug_* files to disk without a restart (boot picks them up)", async () => { + const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-bkmcp2-")); + tempDirs.push(baseDir); + const startPort = await getFreePort(); + fs.writeFileSync(path.join(baseDir, "debug_token.txt"), "rt2", "utf8"); + fs.writeFileSync(path.join(baseDir, "debug_port.txt"), String(startPort), "utf8"); + fs.writeFileSync(path.join(baseDir, "debug_host.txt"), "127.0.0.1", "utf8"); + fs.writeFileSync(path.join(baseDir, "debug_allowlist.txt"), "", "utf8"); + startDebugServer({} as unknown as DownloadManager, baseDir); + await waitForReady(`http://127.0.0.1:${startPort}/health?token=rt2`); + + const restore = resolveMcpRemoteRestore({ allowlist: ["198.51.100.9"], port: 9100, hostMode: "network" }); + writeDebugServerConfig({ host: restore!.host, port: restore!.port, allowlist: restore!.allowlist }); + + expect(fs.readFileSync(path.join(baseDir, "debug_host.txt"), "utf8").trim()).toBe("0.0.0.0"); + expect(fs.readFileSync(path.join(baseDir, "debug_port.txt"), "utf8").trim()).toBe("9100"); + expect(fs.readFileSync(path.join(baseDir, "debug_allowlist.txt"), "utf8")).toContain("198.51.100.9"); + expect(getDebugServerRuntimeStatus().port).toBe(startPort); + }); });