fix(sync-ui): preserve headless child arguments

This commit is contained in:
2026-07-12 23:21:51 -07:00
parent 89e5ac60f6
commit 32d0c79edd
3 changed files with 30 additions and 4 deletions
+1 -1
View File
@@ -2,4 +2,4 @@ type: added
area: sync
- Added a sync window (`subminer sync --ui`, or **Sync Stats & History** in the tray menu) for cross-machine immersion sync: saved devices with per-host direction (two-way/push/pull) and remove, one-click sync with live stage-by-stage progress and merge summaries, connection testing for first-time setup, cancellable runs with a one-click `--force` retry when the running-app guard trips, and manual database snapshots (create/merge/reveal/delete, stored in `/tmp/subminer-db-snapshots/` by default). Hosts with auto-sync enabled are synced in the background on a configurable interval while no mpv session or stats server is writing the database, with results reported as overlay notifications. Hosts synced from the CLI are remembered in `<config dir>/sync-hosts.json` and show up in the window automatically. `subminer sync --ui` launches silently in the background and returns the shell immediately; closing that standalone Sync window shuts down the app.
- Added `subminer sync <host> --check` to test the SSH connection and remote launcher availability without syncing, using bounded noninteractive probes and settling when the check process exits even if an inherited output pipe remains open, without dropping terminal progress that races process exit. Linux AppImage sync commands now run the bundled engine in Node-only mode and exit directly, so remote snapshots never initialize the GUI or require a display server. Added `subminer sync --json` for machine-readable NDJSON progress output (the protocol the sync window consumes).
- Added `subminer sync <host> --check` to test the SSH connection and remote launcher availability without syncing, using bounded noninteractive probes and settling when the check process exits even if an inherited output pipe remains open, without dropping terminal progress that races process exit. Linux AppImage sync commands clear inherited GUI startup argument transport before launching the bundled engine, then exit directly, so sync window checks and remote snapshots never initialize the GUI or require a display server. Added `subminer sync --json` for machine-readable NDJSON progress output (the protocol the sync window consumes).
+18 -1
View File
@@ -2,7 +2,11 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import type { SyncProgressEvent } from '../../shared/sync/sync-events';
import { runSyncLauncher, type SyncLauncherSpawn } from './sync-launcher-client';
import {
runSyncLauncher,
sanitizeSyncLauncherEnv,
type SyncLauncherSpawn,
} from './sync-launcher-client';
class FakeChild extends EventEmitter {
stdout = new EventEmitter();
@@ -214,3 +218,16 @@ test('resolveSyncLauncherCommand self-spawns the app in --sync-cli mode', async
});
assert.deepEqual(dev, ['/usr/bin/electron', '/home/u/SubMiner', '--sync-cli']);
});
test('sanitizeSyncLauncherEnv removes transported parent startup arguments', () => {
const parentEnv = {
PATH: '/usr/bin',
ELECTRON_RUN_AS_NODE: '1',
SUBMINER_APP_ARGC: '2',
SUBMINER_APP_ARG_0: '--start',
SUBMINER_APP_ARG_1: '--background',
};
assert.deepEqual(sanitizeSyncLauncherEnv(parentEnv), { PATH: '/usr/bin' });
assert.equal(parentEnv.SUBMINER_APP_ARGC, '2');
});
+11 -2
View File
@@ -26,6 +26,16 @@ export interface SyncLauncherRunHandle {
done: Promise<SyncLauncherRunResult>;
}
export function sanitizeSyncLauncherEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const env = { ...baseEnv };
delete env.ELECTRON_RUN_AS_NODE;
delete env.SUBMINER_APP_ARGC;
for (const name of Object.keys(env)) {
if (name.startsWith('SUBMINER_APP_ARG_')) delete env[name];
}
return env;
}
// Sync runs in a child copy of this app in headless --sync-cli mode: same
// engine and NDJSON protocol as `subminer sync --json`, with no dependency on
// bun or an installed command-line launcher. In dev runs process.execPath is
@@ -54,8 +64,7 @@ export function runSyncLauncher(options: {
((command, args) => {
// The child must boot as a full Electron app (its entry handles
// --sync-cli); a leaked ELECTRON_RUN_AS_NODE would turn it into node.
const env = { ...process.env };
delete env.ELECTRON_RUN_AS_NODE;
const env = sanitizeSyncLauncherEnv(process.env);
return nodeSpawn(command, args, { stdio: 'pipe', env });
});
const [executable, ...prefixArgs] = options.command;