From a2bac1a94cea4bfe488be385d89a835a1c229bed Mon Sep 17 00:00:00 2001 From: Patrick Roza Date: Mon, 27 Jul 2026 08:59:10 +0200 Subject: [PATCH] fix(desktop): restore Linux secure storage startup --- .../src/electron/DesktopEarlyStartup.test.ts | 70 +++++++++++++++++++ .../src/electron/DesktopEarlyStartup.ts | 40 +++++++++++ apps/desktop/src/main.ts | 8 +++ 3 files changed, 118 insertions(+) create mode 100644 apps/desktop/src/electron/DesktopEarlyStartup.test.ts create mode 100644 apps/desktop/src/electron/DesktopEarlyStartup.ts diff --git a/apps/desktop/src/electron/DesktopEarlyStartup.test.ts b/apps/desktop/src/electron/DesktopEarlyStartup.test.ts new file mode 100644 index 00000000000..11ff0c72b6c --- /dev/null +++ b/apps/desktop/src/electron/DesktopEarlyStartup.test.ts @@ -0,0 +1,70 @@ +import { assert, describe, it } from "@effect/vitest"; +import { vi } from "vite-plus/test"; + +import { + type DesktopEarlyStartupApp, + configureDesktopEarlyStartup, +} from "./DesktopEarlyStartup.ts"; + +function makeApp() { + const appendSwitch = vi.fn(); + const exit = vi.fn(); + const getVersion = vi.fn(() => "1.2.3"); + const app: DesktopEarlyStartupApp = { + commandLine: { appendSwitch }, + exit, + getVersion, + }; + + return { app, appendSwitch, exit, getVersion }; +} + +describe("configureDesktopEarlyStartup", () => { + it("forces the libsecret password store on Linux", () => { + const { app, appendSwitch } = makeApp(); + + configureDesktopEarlyStartup({ + app, + argv: ["t3code"], + platform: "linux", + writeStdout: vi.fn(), + }); + + assert.deepStrictEqual(appendSwitch.mock.calls, [["password-store", "gnome-libsecret"]]); + }); + + it.each(["darwin", "win32"] as const)( + "does not override the password store on %s", + (platform: "darwin" | "win32") => { + const { app, appendSwitch } = makeApp(); + + configureDesktopEarlyStartup({ + app, + argv: ["t3code"], + platform, + writeStdout: vi.fn(), + }); + + assert.strictEqual(appendSwitch.mock.calls.length, 0); + }, + ); + + it.each(["--version", "-v"] as const)( + "prints the packaged version for %s", + (flag: "--version" | "-v") => { + const { app, exit, getVersion } = makeApp(); + const writeStdout = vi.fn(); + + configureDesktopEarlyStartup({ + app, + argv: ["t3code", flag], + platform: "linux", + writeStdout, + }); + + assert.strictEqual(getVersion.mock.calls.length, 1); + assert.deepStrictEqual(writeStdout.mock.calls, [["1.2.3\n"]]); + assert.deepStrictEqual(exit.mock.calls, [[0]]); + }, + ); +}); diff --git a/apps/desktop/src/electron/DesktopEarlyStartup.ts b/apps/desktop/src/electron/DesktopEarlyStartup.ts new file mode 100644 index 00000000000..55dd473dca9 --- /dev/null +++ b/apps/desktop/src/electron/DesktopEarlyStartup.ts @@ -0,0 +1,40 @@ +export interface DesktopEarlyStartupApp { + readonly commandLine: { + readonly appendSwitch: (switchName: string, value?: string) => void; + }; + readonly exit: (exitCode?: number) => void; + readonly getVersion: () => string; +} + +export interface ConfigureDesktopEarlyStartupOptions { + readonly app: DesktopEarlyStartupApp; + readonly argv: ReadonlyArray; + readonly platform: NodeJS.Platform; + readonly writeStdout: (value: string) => unknown; +} + +/** + * Applies command-line behavior that Electron must receive before `app.whenReady()`. + * + * Keep this explicit: Electron's automatic password-store selection is unreliable + * in Linux desktop sessions such as Niri/UWSM, where Secret Service is available + * but Chromium can otherwise select an unusable backend. + */ +export function configureDesktopEarlyStartup({ + app, + argv, + platform, + writeStdout, +}: ConfigureDesktopEarlyStartupOptions): void { + if (argv.includes("--version") || argv.includes("-v")) { + try { + writeStdout(`${app.getVersion()}\n`); + } finally { + app.exit(0); + } + } + + if (platform === "linux") { + app.commandLine.appendSwitch("password-store", "gnome-libsecret"); + } +} diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 7e0e4523595..ee00eb6751d 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -20,6 +20,7 @@ import { resolveRemoteT3CliPackageSpec } from "@t3tools/ssh/command"; import type { RemoteT3RunnerOptions } from "@t3tools/ssh/tunnel"; import serverPackageJson from "../../server/package.json" with { type: "json" }; +import { configureDesktopEarlyStartup } from "./electron/DesktopEarlyStartup.ts"; import * as DesktopIpc from "./ipc/DesktopIpc.ts"; import * as ElectronApp from "./electron/ElectronApp.ts"; import * as ElectronDialog from "./electron/ElectronDialog.ts"; @@ -62,6 +63,13 @@ import * as DesktopWindow from "./window/DesktopWindow.ts"; import * as DesktopWslBackend from "./wsl/DesktopWslBackend.ts"; import * as DesktopWslEnvironment from "./wsl/DesktopWslEnvironment.ts"; +configureDesktopEarlyStartup({ + app: Electron.app, + argv: process.argv, + platform: Effect.runSync(HostProcessPlatform), + writeStdout: (value) => process.stdout.write(value), +}); + const desktopEnvironmentLayer = Layer.unwrap( Effect.gen(function* () { const metadata = yield* Effect.service(ElectronApp.ElectronApp).pipe(