mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat(desktop): stop the daemon when you quit the app (#2454)
Quitting the desktop app now shuts down the daemon it started, so "restart the app" is a complete reset users can act on without first learning what a daemon is. The old default kept it alive; the toggle under Settings > Host still opts back in. Existing installs already persisted `keepRunningAfterQuit: true` from the old default, so a new default alone would only reach fresh installs. A one-time migration resets it, and an explicit toggle afterwards persists the migration flag and is never overridden again. Only a desktop-managed daemon is stopped — one started with `paseo daemon start` is left alone.
This commit is contained in:
@@ -382,5 +382,5 @@ $PASEO_HOME/
|
||||
## Deployment models
|
||||
|
||||
1. **Local daemon** (default): `paseo daemon start` on `127.0.0.1:6767`
|
||||
2. **Managed desktop**: Electron app spawns daemon as subprocess
|
||||
2. **Managed desktop**: Electron app spawns daemon as subprocess, and stops it again on quit so that "restart the app" is a complete reset. Settings > Host > "Keep daemon running after quit" opts out. Only a daemon the desktop started is stopped — a daemon you started yourself with `paseo daemon start` is left alone (`paseo.pid` records `desktopManaged`).
|
||||
3. **Remote + relay**: Daemon behind firewall, relay bridges with E2E encryption
|
||||
|
||||
@@ -43,7 +43,8 @@ This architecture means:
|
||||
|
||||
- The daemon can run on any machine: laptop, VM, remote server
|
||||
- Multiple clients can connect simultaneously
|
||||
- Agents keep running when you close the app
|
||||
- Agents keep running when a client disconnects — the daemon owns them, not the client
|
||||
- Quitting the desktop app stops the daemon it started, so "restart the app" is a real fix; a daemon you run yourself is unaffected
|
||||
|
||||
## Target user
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||
releaseChannel: "stable",
|
||||
daemon: {
|
||||
manageBuiltInDaemon: true,
|
||||
keepRunningAfterQuit: true,
|
||||
keepRunningAfterQuit: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const DEFAULT_DESKTOP: DesktopSettings = {
|
||||
releaseChannel: "stable",
|
||||
daemon: {
|
||||
manageBuiltInDaemon: true,
|
||||
keepRunningAfterQuit: true,
|
||||
keepRunningAfterQuit: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
stopDesktopManagedDaemonOnQuitIfNeeded,
|
||||
} from "./quit-lifecycle";
|
||||
|
||||
const SETTINGS_KEEP_RUNNING = DEFAULT_DESKTOP_SETTINGS;
|
||||
const SETTINGS_STOP_ON_QUIT = {
|
||||
const SETTINGS_STOP_ON_QUIT = DEFAULT_DESKTOP_SETTINGS;
|
||||
const SETTINGS_KEEP_RUNNING = {
|
||||
...DEFAULT_DESKTOP_SETTINGS,
|
||||
daemon: {
|
||||
...DEFAULT_DESKTOP_SETTINGS.daemon,
|
||||
keepRunningAfterQuit: false,
|
||||
keepRunningAfterQuit: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ function waitForQuitLifecycle(): Promise<void> {
|
||||
}
|
||||
|
||||
describe("quit-lifecycle", () => {
|
||||
it("only stops when keepRunningAfterQuit is explicitly disabled", () => {
|
||||
it("stops by default and only keeps running when keepRunningAfterQuit is enabled", () => {
|
||||
expect(shouldStopDesktopManagedDaemonOnQuit(SETTINGS_STOP_ON_QUIT)).toBe(true);
|
||||
expect(shouldStopDesktopManagedDaemonOnQuit(SETTINGS_KEEP_RUNNING)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -162,6 +162,50 @@ describe("desktop-settings", () => {
|
||||
expect(persisted).toBe(raw);
|
||||
});
|
||||
|
||||
it("resets the pre-existing keep-running default so the daemon stops with the app", async () => {
|
||||
const userDataPath = await createTempUserDataDir();
|
||||
directories.add(userDataPath);
|
||||
await writeFile(
|
||||
settingsFilePath(userDataPath),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
settings: {
|
||||
releaseChannel: "stable",
|
||||
daemon: { manageBuiltInDaemon: true, keepRunningAfterQuit: true },
|
||||
},
|
||||
migrations: { legacyRendererSettingsImported: true },
|
||||
}),
|
||||
);
|
||||
const store = createDesktopSettingsStore({ userDataPath });
|
||||
|
||||
const settings = await store.get();
|
||||
|
||||
expect(settings.daemon.keepRunningAfterQuit).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps an explicit keep-running choice across restarts", async () => {
|
||||
const userDataPath = await createTempUserDataDir();
|
||||
directories.add(userDataPath);
|
||||
await writeFile(
|
||||
settingsFilePath(userDataPath),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
settings: {
|
||||
releaseChannel: "stable",
|
||||
daemon: { manageBuiltInDaemon: true, keepRunningAfterQuit: true },
|
||||
},
|
||||
migrations: { legacyRendererSettingsImported: true },
|
||||
}),
|
||||
);
|
||||
await createDesktopSettingsStore({ userDataPath }).patch({
|
||||
daemon: { keepRunningAfterQuit: true },
|
||||
});
|
||||
|
||||
const settings = await createDesktopSettingsStore({ userDataPath }).get();
|
||||
|
||||
expect(settings.daemon.keepRunningAfterQuit).toBe(true);
|
||||
});
|
||||
|
||||
it("migrates desktop-owned values from legacy renderer settings once", async () => {
|
||||
const userDataPath = await createTempUserDataDir();
|
||||
directories.add(userDataPath);
|
||||
|
||||
@@ -22,6 +22,11 @@ interface PersistedDesktopSettingsDocument {
|
||||
settings: DesktopSettings;
|
||||
migrations: {
|
||||
legacyRendererSettingsImported: boolean;
|
||||
// Installs created before the stop-on-quit default persisted the old
|
||||
// `keepRunningAfterQuit: true` default to disk, so the new default alone
|
||||
// would only reach fresh installs. Reset it once; a later explicit toggle
|
||||
// persists this flag and is never overridden again.
|
||||
daemonStopOnQuitDefaultApplied: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +40,7 @@ export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
||||
releaseChannel: "stable",
|
||||
daemon: {
|
||||
manageBuiltInDaemon: true,
|
||||
keepRunningAfterQuit: true,
|
||||
keepRunningAfterQuit: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -72,6 +77,7 @@ function buildDefaultDocument(): PersistedDesktopSettingsDocument {
|
||||
},
|
||||
migrations: {
|
||||
legacyRendererSettingsImported: false,
|
||||
daemonStopOnQuitDefaultApplied: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -180,11 +186,18 @@ function coerceDocument(input: unknown): PersistedDesktopSettingsDocument {
|
||||
const migrations = isRecord(input.migrations)
|
||||
? {
|
||||
legacyRendererSettingsImported: input.migrations.legacyRendererSettingsImported === true,
|
||||
daemonStopOnQuitDefaultApplied: input.migrations.daemonStopOnQuitDefaultApplied === true,
|
||||
}
|
||||
: {
|
||||
legacyRendererSettingsImported: false,
|
||||
daemonStopOnQuitDefaultApplied: false,
|
||||
};
|
||||
|
||||
if (!migrations.daemonStopOnQuitDefaultApplied) {
|
||||
settings.daemon.keepRunningAfterQuit = DEFAULT_DESKTOP_SETTINGS.daemon.keepRunningAfterQuit;
|
||||
migrations.daemonStopOnQuitDefaultApplied = true;
|
||||
}
|
||||
|
||||
return {
|
||||
version: 1,
|
||||
settings,
|
||||
|
||||
Reference in New Issue
Block a user