diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-05 19:48:10 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-05 19:48:10 +0200 |
| commit | fff1974edf19cf1186e0f49da5f8a4d237bcb13e (patch) | |
| tree | 3c623c6253cd005773546b207f4749e98151a7b5 /packages | |
| parent | 5b4c4df56bcc795be6a8416987e2d91f91ea14ba (diff) | |
| download | meshbay-fff1974edf19cf1186e0f49da5f8a4d237bcb13e.tar.gz | |
fix(win): finish the desktop setup flow — node-key link + service task
Two independent breaks in the Windows first-run path:
- node:start's win32 branch never linked the node's Ed25519 key to the hub
account, so the daemon sat at waiting_for_account and the Create Group
wizard span on "Detecting local node…" for ever — the only way through
was pasting the key by hand on the Profile page. The Linux branch has
always done this inline; factor it into linkNodeKeyAndAwaitRunning() and
call it from win32 too. PUT /v1/users/me/node_key overwrites, so this
also recovers an account still carrying a previous machine's node key.
- service.ps1's install branch did `$action = New-ScheduledTaskAction`,
shadowing its own [ValidateSet(...)][string]$Action parameter (PowerShell
variable names are case-insensitive). The CimInstance was coerced to the
string "MSFT_TaskExecAction", Register-ScheduledTask -Action rejected it,
and "background service" mode never created the task — reproduced live.
Rename the locals to $taskAction / $bootTrigger / $taskPrincipal.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/meshbay-client/src/main.js | 52 | ||||
| -rw-r--r-- | packages/meshbay-node/tests/test_packaging_win.py | 37 |
2 files changed, 88 insertions, 1 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index 74c8203..de8de8f 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -1091,6 +1091,43 @@ function registerBridge() { return null; } + // The daemon is reachable but sitting at 'waiting_for_node_key' / + // 'waiting_for_account': its Ed25519 key is not linked to the hub account it + // runs as (a fresh node, or that account still carries a previous machine's + // node key). Link it with the signed-in user's token, then wait out the + // daemon's own 5s hub-auth retry until it reports 'running'. `PUT + // /me/node_key` overwrites unconditionally, so this also recovers an account + // whose linked key belongs to a node that is gone. + // + // The Linux branch of `node:start` does the same thing inline; Windows went + // without it, so the daemon never left 'waiting_for_account' and the Create + // Group wizard spun on "Detecting local node…" for ever. + async function linkNodeKeyAndAwaitRunning(opts, deadline) { + let linked = false; + let last = null; + while (Date.now() < deadline) { + last = await probeNode(); + if (last && last.status === 'running') return last; + if (last && !linked && opts && opts.token && opts.hubUrl + && last.pk_node_ed25519 + && (last.status === 'waiting_for_node_key' + || last.status === 'waiting_for_account')) { + try { + const r = await fetch(`${opts.hubUrl}/v1/users/me/node_key`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${opts.token}` }, + body: JSON.stringify({ pk_node_ed25519: last.pk_node_ed25519 }), + signal: AbortSignal.timeout(5000), + }); + if (r.ok) linked = true; + } catch { /* transient — retried on the next tick */ } + } + await new Promise((r) => setTimeout(r, 500)); + } + return last; + } + ipcMain.handle('node:installed', async () => { if (process.platform === 'win32') { const [bin, svc] = await Promise.all([findNodeBinary(), winServiceTaskStatus()]); @@ -1350,7 +1387,20 @@ function registerBridge() { 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'); - return { started: true, ...p }; + // Up, but almost never 'running' on a first launch: link the node key to + // the hub account and wait for the daemon to authenticate. Without this + // it stays at 'waiting_for_account' and nothing here ever tells the hub + // about the node. + const ready = p.status === 'running' + ? p + : await linkNodeKeyAndAwaitRunning(opts, Date.now() + 45000); + if (!ready || ready.status !== 'running') { + throw new Error( + 'the node started but could not link to your hub account. Open the ' + + 'Node page and use "Link this node", or check you are signed in to ' + + 'the hub this node is configured for.'); + } + return { started: true, ...ready }; } if (process.platform !== 'linux') { diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py index df5a24f..b52e13a 100644 --- a/packages/meshbay-node/tests/test_packaging_win.py +++ b/packages/meshbay-node/tests/test_packaging_win.py @@ -318,6 +318,16 @@ def test_service_install_uses_s4u_not_a_stored_password(): assert "schtasks" not in install_block, ( "the install branch must not fall back to schtasks /create") + # The install branch must not assign to `$action` (any case): that name is + # this script's own [ValidateSet(...)][string]$Action parameter, and + # `$action = New-ScheduledTaskAction ...` coerces the CimInstance to the + # string "MSFT_TaskExecAction", which Register-ScheduledTask -Action then + # refuses -- reproduced live 2026-09-05, the install path never created the + # task at all until this was renamed. + assert not re.search(r"(?im)^\s*\$action\s*=", install_block), ( + "do not assign to $action in service.ps1 -- it shadows the [string]" + "$Action parameter and Register-ScheduledTask -Action then gets a string") + def test_service_task_name_is_the_same_everywhere(): """One name, three places: meshbay_node.platform.TASK_NAME (the CLI), @@ -457,6 +467,33 @@ def test_node_start_provisions_before_it_ever_touches_the_service_task(): "exists for it to read") +def test_node_start_links_the_node_key_on_windows_not_only_linux(): + """ + The daemon comes up at 'waiting_for_account' until its Ed25519 key is + linked to the hub account it runs as. The Linux branch of node:start does + that link inline (`PUT /v1/users/me/node_key`); the win32 branch used to + just return the first reachable status, so the Create Group wizard span on + "Detecting local node…" for ever and the only way through was linking the + key by hand on the Profile page. + + Pin that the win32 branch now performs the same link before it reports the + node started. + """ + main_js = (CLIENT / "src" / "main.js").read_text(encoding="utf-8") + body = main_js.split("ipcMain.handle('node:start'", 1)[1] + body = body[:body.index("ipcMain.handle(")] + win_branch = body.split("process.platform === 'win32'", 1)[1] + win_branch = win_branch[:win_branch.index("process.platform !== 'linux'")] + assert "linkNodeKeyAndAwaitRunning" in win_branch, ( + "node:start's win32 branch never links the node key — the daemon will " + "sit at waiting_for_account and the wizard will hang") + + helper = main_js.split("async function linkNodeKeyAndAwaitRunning", 1)[1] + helper = helper[:2000] + assert "/v1/users/me/node_key" in helper + assert "waiting_for_account" in helper and "waiting_for_node_key" in helper + + def test_firewall_ps1_targets_both_executables_and_is_idempotent(): """One script, both rules — so installer.nsh only ever has to name it once on the way in and once on the way out.""" |