Ferndiagnose-Backup: auch Full-Backup-Import schreibt die MCP-Dateien

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.
This commit is contained in:
Sucukdeluxe 2026-06-19 19:48:44 +02:00
parent a35ddf68b2
commit d9657a5459
2 changed files with 38 additions and 4 deletions

View File

@ -372,17 +372,20 @@ export class AppController {
return this.getRemoteDiagnostics(); return this.getRemoteDiagnostics();
} }
private restoreMcpRemoteFromBackup(section: unknown): void { private restoreMcpRemoteFromBackup(section: unknown, restartNow: boolean): void {
const restore = resolveMcpRemoteRestore(section); const restore = resolveMcpRemoteRestore(section);
if (!restore) { if (!restore) {
return; return;
} }
writeDebugServerConfig({ host: restore.host, port: restore.port, allowlist: restore.allowlist }); 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", { this.audit("INFO", "Ferndiagnose-Einstellungen aus Backup wiederhergestellt", {
port: restore.port ?? null, port: restore.port ?? null,
allowlistCount: restore.allowlist?.length ?? 0, allowlistCount: restore.allowlist?.length ?? 0,
host: restore.host ?? "unveraendert" host: restore.host ?? "unveraendert",
restartNow
}); });
} }
@ -827,7 +830,7 @@ public async checkDebridAccounts(): Promise<DebridAccountStatus[]> {
this.settings = restoredSettings; this.settings = restoredSettings;
saveSettings(this.storagePaths, this.settings); saveSettings(this.storagePaths, this.settings);
this.manager.setSettings(this.settings, { suppressRetroactiveCleanup: true }); this.manager.setSettings(this.settings, { suppressRetroactiveCleanup: true });
this.restoreMcpRemoteFromBackup(parsed.mcpRemote); this.restoreMcpRemoteFromBackup(parsed.mcpRemote, true);
this.audit("INFO", "Backup importiert (nur Einstellungen)", { this.audit("INFO", "Backup importiert (nur Einstellungen)", {
accountSummary: buildAccountSummary(this.settings) accountSummary: buildAccountSummary(this.settings)
}); });
@ -864,6 +867,8 @@ public async checkDebridAccounts(): Promise<DebridAccountStatus[]> {
resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
this.restoreMcpRemoteFromBackup(parsed.mcpRemote, false);
this.manager.skipShutdownPersist = true; this.manager.skipShutdownPersist = true;
this.manager.blockAllPersistence = true; this.manager.blockAllPersistence = true;
logger.info("Backup wiederhergestellt — App startet automatisch neu"); logger.info("Backup wiederhergestellt — App startet automatisch neu");

View File

@ -7,6 +7,7 @@ import { afterEach, describe, expect, it } from "vitest";
import { buildBackupPayload, resolveMcpRemoteRestore, BackupMcpRemote } from "../src/main/backup-payload"; import { buildBackupPayload, resolveMcpRemoteRestore, BackupMcpRemote } from "../src/main/backup-payload";
import { defaultSettings } from "../src/main/constants"; import { defaultSettings } from "../src/main/constants";
import { normalizeSettings } from "../src/main/storage";
import { import {
startDebugServer, startDebugServer,
stopDebugServer, 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", () => { describe("resolveMcpRemoteRestore", () => {
it("maps network + non-empty allowlist to 0.0.0.0", () => { it("maps network + non-empty allowlist to 0.0.0.0", () => {
expect(resolveMcpRemoteRestore({ allowlist: ["10.0.0.5"], port: 9868, hostMode: "network" })) 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`); 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);
});
}); });