aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-22 18:26:22 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-22 18:26:22 +0200
commit8dc11dc05a35a5d64ba4d2c892ccc01c7bfae3da (patch)
treee172bb4d596bde53c48ce2ca3ef2d59c98968147 /packages/meshbay-client/src
parentb4baa4770d517ea7d1d25bb0ac50fc7c989531d6 (diff)
downloadmeshbay-8dc11dc05a35a5d64ba4d2c892ccc01c7bfae3da.tar.gz
feat(node): systemd service panel on the Node page, and a clean CLI restart
Add a status panel at the top of the Node page — always visible, even before an MNP connection exists — showing the meshbay-node systemd unit's own state (via `systemctl --user show`, main process only) with Start/Stop/Restart controls. This is the piece the rest of the page cannot provide: it has to work while the daemon is stopped or crash- looping, which the MNP-based sections require the daemon to already answer. While touching node lifecycle: `reload` and `restart-daemon` in the CLI shelled out to pgrep + SIGTERM/SIGHUP and respawned the process by hand, logging to a hardcoded /tmp path. That pattern already SIGHUPed a developer's own running node by accident once (see the old test_cli_dispatch.py comment). Both now delegate to `systemctl --user reload|restart meshbay-node`, which the unit already supports correctly (ExecReload=, Restart=on-failure). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016SF6RKNBKg9qejmoMJ9ybA
Diffstat (limited to 'packages/meshbay-client/src')
-rw-r--r--packages/meshbay-client/src/main.js57
-rw-r--r--packages/meshbay-client/src/preload.js7
2 files changed, 64 insertions, 0 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js
index 24c1b6a..2bdf550 100644
--- a/packages/meshbay-client/src/main.js
+++ b/packages/meshbay-client/src/main.js
@@ -738,6 +738,63 @@ function registerBridge() {
return { installed: Boolean(bin) };
});
+ // The systemd unit's own view of the node, for the status panel at the top
+ // of the Node page. Deliberately not `probeNode()`: that asks the daemon's
+ // own HTTP API, which cannot answer while the daemon is stopped or crash-
+ // looping — exactly the states this panel exists to show and act on.
+ ipcMain.handle('node:service-status', async () => {
+ if (process.platform !== 'linux') return { supported: false };
+ return new Promise((resolve) => {
+ execFile('systemctl', ['--user', 'show', 'meshbay-node.service',
+ '--property=LoadState,ActiveState,SubState'], (err, stdout) => {
+ if (err) {
+ resolve({ supported: true, installed: false, activeState: 'unknown',
+ subState: '' });
+ return;
+ }
+ const props = {};
+ for (const line of stdout.split('\n')) {
+ const i = line.indexOf('=');
+ if (i > 0) props[line.slice(0, i)] = line.slice(i + 1);
+ }
+ resolve({
+ supported: true,
+ installed: props.LoadState === 'loaded',
+ activeState: props.ActiveState || 'unknown',
+ subState: props.SubState || '',
+ });
+ });
+ });
+ });
+
+ ipcMain.handle('node:service-stop', async () => {
+ if (process.platform !== 'linux') {
+ throw new Error('Service control is only supported on Linux');
+ }
+ await new Promise((resolve, reject) => {
+ execFile('systemctl', ['--user', 'stop', 'meshbay-node'],
+ (err, _stdout, stderr) => {
+ if (err) return reject(new Error(stderr.trim() || err.message));
+ resolve();
+ });
+ });
+ return { stopped: true };
+ });
+
+ ipcMain.handle('node:service-restart', async () => {
+ if (process.platform !== 'linux') {
+ throw new Error('Service control is only supported on Linux');
+ }
+ await new Promise((resolve, reject) => {
+ execFile('systemctl', ['--user', 'restart', 'meshbay-node'],
+ (err, _stdout, stderr) => {
+ if (err) return reject(new Error(stderr.trim() || err.message));
+ resolve();
+ });
+ });
+ return { restarted: true };
+ });
+
async function probeNode() {
const nc = readNodeConfig();
const dataDir = nc ? nc.dataDir
diff --git a/packages/meshbay-client/src/preload.js b/packages/meshbay-client/src/preload.js
index f240995..7ffdf66 100644
--- a/packages/meshbay-client/src/preload.js
+++ b/packages/meshbay-client/src/preload.js
@@ -91,6 +91,13 @@ contextBridge.exposeInMainWorld('meshbay', {
call: (method, path, body) => ipcRenderer.invoke('node:call', method, path, body),
pairingCode: () => ipcRenderer.invoke('node:pairing-code'),
setPairingCode: (code) => ipcRenderer.invoke('node:set-pairing-code', code),
+ // The systemd unit's own state — reachable even while the daemon itself
+ // is stopped or crash-looping, which `call()` above is not.
+ service: {
+ status: () => ipcRenderer.invoke('node:service-status'),
+ stop: () => ipcRenderer.invoke('node:service-stop'),
+ restart: () => ipcRenderer.invoke('node:service-restart'),
+ },
},
// LAN cast relay. The main process runs a local HTTP server and the