summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-15 17:42:30 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-15 17:45:41 +0200
commit0d082ab4d19412079516b281aff76e8c3f70d9ba (patch)
tree46bc48f67663c4049384578c594b8558bce2db6c /packages
parent41d03f0a35a95daa7d54e7f29ab924776bedf6e4 (diff)
downloadmeshbay-0d082ab4d19412079516b281aff76e8c3f70d9ba.tar.gz
fix(packaging): setup ends a command-line client run from its install
A `meshbay-cli chat --follow` left open holds node-runtime's DLLs, so the previous version's uninstaller failed ("Failed to uninstall old application files"), a silent update included, and left the node it had stopped not running. stop-node.ps1 now ends a meshbay-cli.exe run from this install (-InstallDir, setup's $INSTDIR), and no other copy. Found through CIM, not Get-Process: setup runs the 32-bit PowerShell, which reads no path for a 64-bit process, and the first version of the stop matched nothing. The test runs the script in that PowerShell. Checked live: an update over a running follower now ends it, replaces every file and restarts the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-client/build/installer.nsh6
-rw-r--r--packages/meshbay-client/build/stop-node.ps127
-rw-r--r--packages/meshbay-node/tests/test_packaging_win.py50
3 files changed, 78 insertions, 5 deletions
diff --git a/packages/meshbay-client/build/installer.nsh b/packages/meshbay-client/build/installer.nsh
index 2337088..ce1e1ea 100644
--- a/packages/meshbay-client/build/installer.nsh
+++ b/packages/meshbay-client/build/installer.nsh
@@ -95,11 +95,11 @@ Var pid
InitPluginsDir
File "/oname=$PLUGINSDIR\mb-stop-node.ps1" "${BUILD_RESOURCES_DIR}\stop-node.ps1"
mb_stop_node:
- DetailPrint "Stopping the MeshBay node..."
- nsExec::Exec `"${MB_PWSH}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\mb-stop-node.ps1"`
+ DetailPrint "Stopping the MeshBay node and command-line client..."
+ nsExec::Exec `"${MB_PWSH}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\mb-stop-node.ps1" -InstallDir "$INSTDIR"`
Pop $0
${If} $0 != 0
- MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "The MeshBay node is still running and holds files that setup must replace. Stop it (meshbay-node service stop, or end meshbay-node.exe in Task Manager), then click Retry." /SD IDCANCEL IDRETRY mb_stop_node
+ MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "The MeshBay node or command-line client is still running and holds files that setup must replace. Stop it (meshbay-node service stop, or end meshbay-node.exe and meshbay-cli.exe in Task Manager), then click Retry." /SD IDCANCEL IDRETRY mb_stop_node
Quit
${EndIf}
!insertmacro IS_POWERSHELL_AVAILABLE
diff --git a/packages/meshbay-client/build/stop-node.ps1 b/packages/meshbay-client/build/stop-node.ps1
index cfaf2f8..dcb0ea6 100644
--- a/packages/meshbay-client/build/stop-node.ps1
+++ b/packages/meshbay-client/build/stop-node.ps1
@@ -10,9 +10,34 @@
# 2. Task Scheduler for a background-service node (the owner may end the task;
# taskkill from here gets "Access is denied" in its session).
# 3. taskkill for anything left in this session.
-# Exit 0 once no meshbay-node.exe is left, 1 otherwise.
+#
+# Before all that, the command-line client (-InstallDir, setup's $INSTDIR): it
+# runs from the same node-runtime folder and holds its Python DLLs, so one left
+# open -- a `chat --follow`, a `stream` -- made the previous version's
+# uninstaller fail ("Failed to uninstall old application files"), silent
+# updates included. It is ended like the app is; only copies of this install.
+# Exit 0 once neither is left, 1 otherwise.
+param([string]$InstallDir = "")
$ErrorActionPreference = "SilentlyContinue"
+if ($InstallDir) {
+ $runtime = Join-Path $InstallDir "resources\node-runtime\"
+ # Through CIM, not Get-Process: setup runs the 32-bit PowerShell ($SYSDIR is
+ # SysWOW64 there), which reads no path for a 64-bit process -- the filter
+ # matched nothing and the update failed as before.
+ function CliLeft {
+ @(Get-CimInstance Win32_Process -Filter "Name='meshbay-cli.exe'" `
+ -ErrorAction SilentlyContinue | Where-Object {
+ $_.ExecutablePath -and
+ $_.ExecutablePath.StartsWith($runtime, [StringComparison]::OrdinalIgnoreCase)
+ })
+ }
+ CliLeft | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
+ $deadline = (Get-Date).AddSeconds(10)
+ while ((CliLeft).Count -and (Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 250 }
+ if ((CliLeft).Count) { exit 1 }
+}
+
function NodeLeft { [bool](Get-Process -Name meshbay-node -ErrorAction SilentlyContinue) }
function WaitGone([int]$Seconds) {
$deadline = (Get-Date).AddSeconds($Seconds)
diff --git a/packages/meshbay-node/tests/test_packaging_win.py b/packages/meshbay-node/tests/test_packaging_win.py
index ed8104f..6ee4891 100644
--- a/packages/meshbay-node/tests/test_packaging_win.py
+++ b/packages/meshbay-node/tests/test_packaging_win.py
@@ -285,7 +285,7 @@ def test_the_installer_asks_the_node_to_stop_before_forcing_it():
src = STOP_NODE_PS1.read_text(encoding="utf-8")
i_api = src.index("/api/shutdown")
i_task = src.index("schtasks /end /tn \"MeshBay Node\"")
- i_kill = src.index("Stop-Process -Force")
+ i_kill = src.index("Stop-Process -Force", src.index("Get-Process -Name meshbay-node "))
assert i_api < i_task < i_kill, (
"graceful through the control API, then Task Scheduler, then taskkill")
assert "ui_port" in src and r"data\ui-token" in src
@@ -426,6 +426,54 @@ def _dummy_node(tmp_path, name):
@pytest.mark.skipif(sys.platform != "win32", reason="runs the installer's PowerShell for real")
+def test_setup_ends_the_command_line_client_of_this_install_only(tmp_path):
+ """A `meshbay-cli chat --follow` left open holds node-runtime's DLLs, and the
+ previous version's uninstaller then failed the update, silent ones too
+ ("Failed to uninstall old application files", seen live). Setup ends a
+ client run from its own install, and no other copy of the same name."""
+ import shutil
+ import subprocess
+
+ name = "mbclistoptest"
+ install = tmp_path / "MeshBay"
+ runtime = install / "resources" / "node-runtime"
+ runtime.mkdir(parents=True)
+ elsewhere = tmp_path / "elsewhere"
+ elsewhere.mkdir()
+ procs = []
+ for where in (runtime, elsewhere):
+ exe = where / f"{name}.exe"
+ shutil.copy(r"C:\Windows\System32\PING.EXE", exe)
+ procs.append(subprocess.Popen([str(exe), "-n", "300", "127.0.0.1"],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL))
+ ours, other = procs
+ script = (STOP_NODE_PS1.read_text(encoding="utf-8")
+ .replace('"MeshBay Node"', '"MeshBay Node stop-test"')
+ .replace("meshbay-node", "mbnodestoptest-none")
+ .replace("meshbay-cli", name))
+ copy = tmp_path / "stop-node-test.ps1"
+ copy.write_text(script, encoding="utf-8")
+ # The 32-bit PowerShell, as setup runs it ($SYSDIR is SysWOW64 in a 32-bit
+ # installer): from there Get-Process reads no path of a 64-bit process, and
+ # the first version of this stop matched nothing, live.
+ windir = os.environ.get("SystemRoot", r"C:\Windows")
+ pwsh = os.path.join(windir, "SysWOW64", "WindowsPowerShell", "v1.0", "powershell.exe")
+ if not os.path.exists(pwsh):
+ pwsh = shutil.which("powershell")
+ try:
+ r = subprocess.run([pwsh, "-NoProfile", "-ExecutionPolicy", "Bypass", "-File",
+ str(copy), "-InstallDir", str(install)],
+ capture_output=True, text=True, timeout=90)
+ assert r.returncode == 0, r.stdout + r.stderr
+ assert ours.wait(timeout=10) is not None
+ assert other.poll() is None, "a copy outside this install was ended"
+ finally:
+ for p in procs:
+ if p.poll() is None:
+ p.kill()
+
+
+@pytest.mark.skipif(sys.platform != "win32", reason="runs the installer's PowerShell for real")
def test_setup_stops_a_node_that_answers_through_its_control_api(tmp_path):
"""The graceful path: the script finds port and token where the node keeps
them, POSTs /api/shutdown, and waits for the process to go -- here a stand-in