diff options
Diffstat (limited to 'packages/meshbay-client/src/main.js')
| -rw-r--r-- | packages/meshbay-client/src/main.js | 87 |
1 files changed, 75 insertions, 12 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index b0505a9..15c3cc0 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -850,6 +850,38 @@ function registerBridge() { }); } + // ── Windows: the opt-in Scheduled Task "service mode" ────────────────────── + // Set up once, elevated, at install time (build/installer.nsh + packaging/win + // /service.ps1 + /service-mode.ps1) or via `meshbay-node service install` + // from an elevated prompt — this process never creates or deletes it, only + // queries and drives an existing one, which needs no elevation (Task + // Scheduler grants the owning user that much itself). Kept in step with + // meshbay_node.platform.TASK_NAME / service_status(). + const WIN_SERVICE_TASK = 'MeshBay Node'; + + function winServiceTaskStatus() { + return new Promise((resolve) => { + execFile('schtasks', ['/query', '/tn', WIN_SERVICE_TASK, '/fo', 'list'], + (err, stdout) => { + if (err) return resolve({ installed: false, state: '' }); + const m = (stdout || '').split(/\r?\n/).find((l) => /^status:/i.test(l.trim())); + resolve({ installed: true, state: m ? m.split(':')[1].trim() : '' }); + }); + }); + } + + function winServiceTaskRun() { + return new Promise((resolve) => { + execFile('schtasks', ['/run', '/tn', WIN_SERVICE_TASK], () => resolve()); + }); + } + + function winServiceTaskEnd() { + return new Promise((resolve) => { + execFile('schtasks', ['/end', '/tn', WIN_SERVICE_TASK], () => resolve()); + }); + } + async function spawnNodeDetached() { const bin = await findNodeBinary(); if (!bin) throw new Error('meshbay-node not found on PATH'); @@ -875,8 +907,12 @@ function registerBridge() { ipcMain.handle('node:installed', async () => { if (process.platform === 'win32') { - const bin = await findNodeBinary(); - return { installed: Boolean(bin), autostart: winAutostartInstalled() }; + const [bin, svc] = await Promise.all([findNodeBinary(), winServiceTaskStatus()]); + return { + installed: Boolean(bin), + autostart: winAutostartInstalled(), + service: svc.installed, + }; } if (process.platform !== 'linux') return { installed: false }; const unit = await new Promise((resolve) => { @@ -897,16 +933,29 @@ function registerBridge() { // looping — exactly the states this panel exists to show and act on. ipcMain.handle('node:service-status', async () => { if (process.platform === 'win32') { - // No Task Scheduler to ask "is it running" — probe the daemon itself. - // `installed` used to be winAutostartInstalled(), which is wrong: it - // answers "does the Startup launcher exist", not "is there a daemon to - // manage". The Node page's Stop/Restart buttons are gated on - // `installed`, so with no autostart configured they silently vanished - // — the daemon was perfectly manageable, just not launchable at - // sign-in. `autostart` carries that state as its own field instead. + const svc = await winServiceTaskStatus(); + if (svc.installed) { + // Service mode: Task Scheduler already tracks running/not, directly — + // no need to probe the daemon's own API for this panel. + const running = /running/i.test(svc.state); + return { + supported: true, + mode: 'service', + installed: true, + activeState: running ? 'active' : 'inactive', + subState: svc.state, + }; + } + // Per-user Startup mode. `installed` used to be winAutostartInstalled(), + // which is wrong: it answers "does the Startup launcher exist", not "is + // there a daemon to manage". The Node page's Stop/Restart buttons are + // gated on `installed`, so with no autostart configured they silently + // vanished — the daemon was perfectly manageable, just not launchable + // at sign-in. `autostart` carries that state as its own field instead. const [p, bin] = await Promise.all([probeNode(), findNodeBinary()]); return { supported: true, + mode: 'startup', installed: Boolean(bin), autostart: winAutostartInstalled(), activeState: p ? 'active' : 'inactive', @@ -939,7 +988,10 @@ function registerBridge() { ipcMain.handle('node:service-stop', async () => { if (process.platform === 'win32') { - await killNodeProcesses(); // hard kill — no CTRL_CLOSE handler yet + const svc = await winServiceTaskStatus(); + if (svc.installed) await winServiceTaskEnd(); + await killNodeProcesses(); // hard kill — no CTRL_CLOSE handler yet; + // also the belt-and-suspenders in case /end left the process running return { stopped: true }; } if (process.platform !== 'linux') { @@ -957,8 +1009,14 @@ function registerBridge() { ipcMain.handle('node:service-restart', async () => { if (process.platform === 'win32') { + const svc = await winServiceTaskStatus(); + if (svc.installed) await winServiceTaskEnd(); await killNodeProcesses(); - await spawnNodeDetached(); + if (svc.installed) { + await winServiceTaskRun(); + } else { + await spawnNodeDetached(); + } const p = await waitForNode(Date.now() + 30000); if (!p) throw new Error('node did not come back up within 30s'); return { restarted: true, ...p }; @@ -1064,7 +1122,12 @@ function registerBridge() { if (process.platform === 'win32') { if (opts && opts.hubUrl && opts.username) provisionNode(opts.hubUrl, opts.username); await killNodeProcesses(); // clear a crash-looping one - await spawnNodeDetached(); + const svc = await winServiceTaskStatus(); + if (svc.installed) { + await winServiceTaskRun(); + } else { + await spawnNodeDetached(); + } const p = await waitForNode(Date.now() + 60000); if (!p) throw new Error('the node did not start within 60s — run it from a ' + 'terminal (`meshbay-node`) to see why'); |