diff --git a/docs/SELF_HOSTED_REMOTE_DAEMON.md b/docs/SELF_HOSTED_REMOTE_DAEMON.md index 5e874305..612b23f3 100644 --- a/docs/SELF_HOSTED_REMOTE_DAEMON.md +++ b/docs/SELF_HOSTED_REMOTE_DAEMON.md @@ -62,13 +62,17 @@ pane --remote-setup --label "VM" ``` On displayless Linux hosts, prefer `runpane install daemon`; the npm and PyPI -wrappers set Electron's required headless environment automatically. For a -direct packaged launch, set it explicitly: +wrappers pass Electron's required headless flags automatically. For a direct +packaged launch, pass them explicitly: ```bash -ELECTRON_OZONE_PLATFORM_HINT=headless pane --remote-setup --label "VM" +pane --ozone-platform=headless --disable-gpu --remote-setup --label "VM" ``` +These must be command-line arguments: Electron picks its Ozone platform before +the app starts, so the environment variable `ELECTRON_OZONE_PLATFORM_HINT` no +longer works (removed in Electron 38, ignored from 39 on). + The wrappers are lightweight installers and configurators. They currently download the packaged Pane runtime because the daemon is not distributed as a separate binary. AppImage installs therefore require FUSE; on Debian-based @@ -247,9 +251,11 @@ the security tradeoff. ### Remote setup exits because X11 or `$DISPLAY` is missing -Use `runpane install daemon`, or prefix a direct packaged launch with -`ELECTRON_OZONE_PLATFORM_HINT=headless`. This failure occurs before Tailscale -setup and does not mean the daemon requires a graphical session. +Use `runpane install daemon`, or add `--ozone-platform=headless --disable-gpu` +to a direct packaged launch. This failure occurs before Tailscale setup and does +not mean the daemon requires a graphical session. If you previously relied on +`ELECTRON_OZONE_PLATFORM_HINT=headless`, switch to the flags: Electron removed +that variable in 38 and ignores it from 39 on. ### The headless daemon starts but remote connect fails diff --git a/main/src/daemon/setupRemoteHost.ts b/main/src/daemon/setupRemoteHost.ts index cea62693..6010dac6 100644 --- a/main/src/daemon/setupRemoteHost.ts +++ b/main/src/daemon/setupRemoteHost.ts @@ -573,21 +573,25 @@ function buildHeadlessDaemonCommand(paneDir: string): string { if (process.platform === 'win32') { return `cd /d ${quoteForWindows(sourceRoot)} && set "PANE_DIR=${paneDir}" && pnpm daemon:headless -- --pane-dir ${quoteForWindows(paneDir)}`; } - return `cd ${quoteForPosix(sourceRoot)} && ${buildPosixHeadlessEnvironment(paneDir)} pnpm daemon:headless -- --pane-dir ${quoteForPosix(paneDir)}`; + return `cd ${quoteForPosix(sourceRoot)} && ${buildPosixHeadlessEnvironment(paneDir)} pnpm daemon:headless --${buildLinuxHeadlessFlags()} --pane-dir ${quoteForPosix(paneDir)}`; } if (process.platform === 'win32') { return `${quoteForWindows(process.execPath)} --daemon-headless --pane-dir ${quoteForWindows(paneDir)}`; } - return `${buildPosixHeadlessEnvironment(paneDir)} ${quoteForPosix(process.execPath)} --daemon-headless --pane-dir ${quoteForPosix(paneDir)}`; + return `${buildPosixHeadlessEnvironment(paneDir)} ${quoteForPosix(process.execPath)}${buildLinuxHeadlessFlags()} --daemon-headless --pane-dir ${quoteForPosix(paneDir)}`; +} + +// Ozone selects its platform before the app's main script runs, so this must be +// passed as argv — app.commandLine.appendSwitch() is too late, and +// ELECTRON_OZONE_PLATFORM_HINT was removed in Electron 38 (no-op since 39). +function buildLinuxHeadlessFlags(): string { + return process.platform === 'linux' ? ' --ozone-platform=headless --disable-gpu' : ''; } function buildPosixHeadlessEnvironment(paneDir: string): string { const entries = [`PANE_DIR=${quoteForPosix(paneDir)}`]; - if (process.platform === 'linux') { - entries.push('ELECTRON_OZONE_PLATFORM_HINT=headless'); - } return entries.join(' '); } diff --git a/main/src/index.ts b/main/src/index.ts index 28130624..ed8cfb9d 100644 --- a/main/src/index.ts +++ b/main/src/index.ts @@ -37,9 +37,12 @@ if (process.platform === 'linux') { app.commandLine.appendSwitch('gtk-version', '3'); if (launchHeadlessDaemon || launchRemoteSetup) { - // Best-effort fallback. Packaged no-display Linux launches need - // ELECTRON_OZONE_PLATFORM_HINT=headless in the parent environment. - app.commandLine.appendSwitch('ozone-platform', 'headless'); + // Ozone has already picked its platform by the time this script runs, so + // appending --ozone-platform here cannot select headless: no-display Linux + // launches must pass `--ozone-platform=headless` as argv. (Before Electron + // 38, ELECTRON_OZONE_PLATFORM_HINT=headless did that for us; the variable + // was removed in 38 and is a no-op from 39 on.) --disable-gpu is still read + // late enough to take effect here. app.commandLine.appendSwitch('disable-gpu'); } } diff --git a/packages/runpane-py/src/runpane/installers.py b/packages/runpane-py/src/runpane/installers.py index 8a152a9c..4b0880b8 100644 --- a/packages/runpane-py/src/runpane/installers.py +++ b/packages/runpane-py/src/runpane/installers.py @@ -75,12 +75,25 @@ def install_pane_artifact( def spawn_pane(executable_path: str, args: List[str]) -> int: try: - return subprocess.call([executable_path, *args], env=build_pane_daemon_environment()) + return subprocess.call( + [executable_path, *build_pane_daemon_args(args)], + env=build_pane_daemon_environment(), + ) except OSError as error: print(f"Failed to launch Pane: {error}") return 1 +def build_pane_daemon_args(args: List[str], system_name: Optional[str] = None) -> List[str]: + """Ozone resolves its platform before the app's main script runs, so headless + selection must arrive as argv. ELECTRON_OZONE_PLATFORM_HINT (below) was removed + in Electron 38 and is a no-op from 39 on; it is kept only so older Pane builds + keep working.""" + if (system_name or platform.system()).lower() != "linux": + return list(args) + return ["--ozone-platform=headless", "--disable-gpu", *args] + + def build_pane_daemon_environment( system_name: Optional[str] = None, base_environment: Optional[dict] = None, diff --git a/packages/runpane/src/installers.ts b/packages/runpane/src/installers.ts index 19a2576a..05e5f6fd 100644 --- a/packages/runpane/src/installers.ts +++ b/packages/runpane/src/installers.ts @@ -59,7 +59,7 @@ export async function installPaneArtifact( export function spawnPane(executablePath: string, args: string[]): Promise { return new Promise((resolve) => { - const child = childProcess.spawn(executablePath, args, { + const child = childProcess.spawn(executablePath, buildPaneDaemonArgs(args), { stdio: 'inherit', shell: false, env: buildPaneDaemonEnvironment() @@ -72,6 +72,18 @@ export function spawnPane(executablePath: string, args: string[]): Promise