mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-10 22:04:11 +00:00
feat(config): real config reset/repair + migration framework (drop manual-delete) (#38)
Eliminates the fragile "delete the config folder before upgrading" tester
instruction, which was wrong-by-OS because the userData folder name was
derived inconsistently per platform (fee[dB]ack on macOS, slopsmith-desktop
on Linux/Windows).
A. Deterministic paths + migration framework
- Pin the userData name on every OS via app.setName('feedback-desktop') +
build.extraMetadata.name; brand (productName 'fee[dB]ack') unchanged.
- One-time userData migration copies a legacy folder into the new one so
upgraded users don't start fresh (atomic copy-then-rename, fail-soft).
Runs before the single-instance lock / crashReporter, which would otherwise
create userData and defeat the "new dir doesn't exist" gate.
- config-migrations.ts: versioned, ordered, idempotent, fail-soft migration
runner stamped in CONFIG_DIR/config_version.json; logs the active CONFIG_DIR
at startup (closes the Linux ~/.local/share/slopsmith shared-config gap).
B. In-app "Reset / repair configuration" (Settings panel)
- Granular options: reset app settings & caches, clear plugin state & cached
Python deps, and full reset with default-OFF opt-ins for installed plugins /
song library / ML caches.
- config-paths.ts is the single source of truth for per-OS path enumeration;
the song library, installed plugins and ML caches are structurally confined
to optInExtras and never wiped by the safe/full categories.
- Reset stops the backend, deletes immediate paths, includes SQLite WAL/SHM
sidecars + the migration stamp on full reset, and defers Chromium/Crashpad
state to next launch (consumed before any window reopens it). ML caches honor
TORCH_HOME/HF_HOME. Empty selection is a no-op (backend left running).
- SECURITY: destructive resets require a native main-process confirmation
dialog — the renderer bridge is reachable by plugin scripts, so a
renderer-only confirm is not a sufficient gate.
Tests: node:test suites for path enumeration (per-OS + library/plugins
preserved), migration idempotency/fail-soft, reset delete pipeline guarantees,
userData migration, and deferred-deletion schedule/consume. `npm test` green
(adds a test script). codex review --base origin/main clean.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cc0aceb365
commit
5188aab938
+60
-4
@@ -60,6 +60,39 @@ import { app, BrowserWindow, ipcMain, dialog, shell, session, crashReporter, pow
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { execFileSync } from 'child_process';
|
||||
import { migrateUserDataIfNeeded, consumePendingResetIfNeeded } from './config-bootstrap';
|
||||
|
||||
// Pin the userData folder name on every OS. Before this it was derived from the
|
||||
// build name and differed per-OS ('fee[dB]ack' on macOS, 'slopsmith-desktop' on
|
||||
// Linux/Windows), so a single "delete the config folder" instruction could never
|
||||
// be right everywhere. setName() must run before ANY app.getPath()/crashReporter/
|
||||
// whenReady so the deterministic path (<appData>/feedback-desktop) is used
|
||||
// throughout. It does NOT change the user-facing brand (productName 'fee[dB]ack');
|
||||
// with the name pinned, the path-hostile brackets never reach the filesystem.
|
||||
app.setName('feedback-desktop');
|
||||
|
||||
// One-time copy of an existing legacy userData folder into the new one, so
|
||||
// testers don't start fresh after the rename. MUST run before BOTH crashReporter
|
||||
// (which creates <userData>/Crashpad) AND requestSingleInstanceLock() (which
|
||||
// writes a SingletonLock into userData) — the migration gate is "new userData
|
||||
// doesn't exist yet", so anything that creates it first would silently skip the
|
||||
// migration and start the upgraded user fresh.
|
||||
migrateUserDataIfNeeded();
|
||||
|
||||
// Acquire the single-instance lock now so the primary-only deferred-reset cleanup
|
||||
// runs ONLY in the instance that will actually boot: a losing second instance
|
||||
// must not consume the pending-reset manifest while the primary still holds
|
||||
// Chromium / Crashpad files open. requestSingleInstanceLock() must be called
|
||||
// exactly once; the lock branch at the bottom of this file reuses this result.
|
||||
// SLOPSMITH_ALLOW_MULTIPLE=1 opts out (two builds side-by-side).
|
||||
const allowMultipleInstances = process.env.SLOPSMITH_ALLOW_MULTIPLE === '1';
|
||||
const hasSingleInstanceLock = allowMultipleInstances || app.requestSingleInstanceLock();
|
||||
if (hasSingleInstanceLock) {
|
||||
// Apply any reset deletions deferred from a previous "Reset configuration"
|
||||
// run, before crashReporter / any BrowserWindow reopens Chromium state &
|
||||
// Crashpad, so those held-open paths can actually be removed.
|
||||
consumePendingResetIfNeeded();
|
||||
}
|
||||
|
||||
// Enable Electron's Crashpad to capture native crashes (incl. VST/JUCE C++
|
||||
// access violations) into <userData>/Crashpad/reports/ as .dmp files. Must
|
||||
@@ -72,7 +105,9 @@ crashReporter.start({
|
||||
uploadToServer: false,
|
||||
compress: false,
|
||||
});
|
||||
import { startPython, stopPython, waitForPython, getPythonPort, StartupStatus, restartPython, getLanUrls } from './python';
|
||||
import { startPython, stopPython, waitForPython, getPythonPort, StartupStatus, restartPython, getLanUrls, getConfigDir } from './python';
|
||||
import { runConfigMigrations } from './config-migrations';
|
||||
import { registerMaintenanceHandlers } from './config-reset';
|
||||
import {
|
||||
IPC_STARTUP_STATUS,
|
||||
IPC_STARTUP_GET_STATUS,
|
||||
@@ -907,6 +942,25 @@ async function startup(): Promise<void> {
|
||||
createSplashWindow();
|
||||
publishStartupStatus({ message: 'Starting backend service...', phase: 'booting', running: true });
|
||||
|
||||
// Run config-schema migrations against the active backend CONFIG_DIR before
|
||||
// the backend starts. This replaces "delete the config folder before
|
||||
// upgrading" with targeted, idempotent, fail-soft migrations. Logging the
|
||||
// resolved CONFIG_DIR here also closes the visibility gap around the silent
|
||||
// Linux ~/.local/share/slopsmith shared-config override (python.ts getConfigDir).
|
||||
try {
|
||||
const activeConfigDir = getConfigDir();
|
||||
console.log(`[main] Active CONFIG_DIR: ${activeConfigDir}`);
|
||||
runConfigMigrations(activeConfigDir, app.getVersion(), new Date().toISOString());
|
||||
} catch (err) {
|
||||
console.warn('[main] config migrations failed (continuing):', err);
|
||||
}
|
||||
|
||||
// Register the "Reset / repair configuration" IPC handlers (Settings panel).
|
||||
// Pass the window getter so the destructive-reset confirmation is a native,
|
||||
// main-process modal (the renderer bridge is reachable by plugin scripts, so
|
||||
// a renderer-only confirm is not a sufficient gate).
|
||||
registerMaintenanceHandlers(() => mainWindow);
|
||||
|
||||
// Start Python server (Slopsmith backend)
|
||||
startPython();
|
||||
|
||||
@@ -1076,9 +1130,11 @@ async function startup(): Promise<void> {
|
||||
// another instance already owns it, surface that window (via 'second-instance'
|
||||
// on the primary) and quit THIS one before startup() boots a competing backend
|
||||
// or window. `SLOPSMITH_ALLOW_MULTIPLE=1` opts out so two builds can run
|
||||
// side-by-side (e.g. A/B testing different versions).
|
||||
const allowMultipleInstances = process.env.SLOPSMITH_ALLOW_MULTIPLE === '1';
|
||||
if (!allowMultipleInstances && !app.requestSingleInstanceLock()) {
|
||||
// side-by-side (e.g. A/B testing different versions). The lock was already
|
||||
// acquired at the top of this file (hasSingleInstanceLock) so primary-only reset
|
||||
// side effects could run before crashReporter — reuse that result here rather
|
||||
// than calling requestSingleInstanceLock() a second time.
|
||||
if (!hasSingleInstanceLock) {
|
||||
app.quit();
|
||||
} else {
|
||||
if (!allowMultipleInstances) {
|
||||
|
||||
Reference in New Issue
Block a user