aboutsummaryrefslogtreecommitdiffstats
path: root/packaging
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-04 14:51:08 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-04 14:51:08 +0200
commitd04b915580c8ba05beb0943a6fab9b04e12294e4 (patch)
tree0fc0dd05ded2e0450602e06fa370c623c65dd2dc /packaging
parentb53298e339b47c6f0b53ab9fd0cfa3fb7741fc28 (diff)
downloadmeshbay-d04b915580c8ba05beb0943a6fab9b04e12294e4.tar.gz
feat(packaging): stamp the frozen node exe, document Windows networking
meshbay-node.spec now builds a VSVersionInfo (ProductName "MeshBay Node", version read from the installed package so it tracks pyproject) and passes it to EXE(version=...). The Windows Firewall prompt, Task Manager and the file's Properties then show "MeshBay Node" instead of a bare exe name — the prompt the operator has to answer on first run. packaging/win/README.md gains a Networking section: the first-run firewall prompt (allow Private AND Public — a VM adapter is Public), that a flat LAN needs nothing else, what a routed/multi-subnet LAN additionally needs, the libvirt-NAT caveat, and a pointer to the packaged Linux firewall profiles. test_packaging_win.py covers the version resource. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'packaging')
-rw-r--r--packaging/win/README.md41
-rw-r--r--packaging/win/meshbay-node.spec39
2 files changed, 80 insertions, 0 deletions
diff --git a/packaging/win/README.md b/packaging/win/README.md
index 0ba18ca..8c29785 100644
--- a/packaging/win/README.md
+++ b/packaging/win/README.md
@@ -80,6 +80,47 @@ embeddable zip would need pip to make a `meshbay-node.exe` wrapper, and that
wrapper bakes in an **absolute** interpreter path — it stops working the moment
the tree is installed somewhere other than where it was built.
+## Networking (running the node)
+
+The node's admin API is loopback-only, but its **transport is not**. WebRTC
+binds an ephemeral UDP port per connection and — because browsers/Electron
+publish their host candidate as an unresolvable `<uuid>.local` mDNS name that
+`aioice` discards — the browser always dials the node, never the reverse. So the
+node has to accept unsolicited inbound UDP from its peers.
+
+**Windows Defender Firewall.** On the daemon's first run Windows pops a prompt
+for `meshbay-node.exe`. Tick **both Private and Public** — a libvirt/VM adapter,
+and sometimes a plain Ethernet one, registers as Public, and a Private-only rule
+then silently drops every peer. The installer cannot pre-create this rule (it is
+per-user and never elevates); the prompt is the mechanism. If you dismissed it,
+add the rule by hand: *Windows Defender Firewall → Advanced → Inbound Rules →
+New Rule → Program →* the bundled `…\resources\node-runtime\meshbay-node.exe` *→
+Allow → all profiles*.
+
+**A flat LAN needs nothing else.** The node offers a routable `192.168.x.y` host
+candidate and browsers on the same subnet connect straight to it — same as the
+residential-NAT cases that work without TURN.
+
+**A routed or multi-subnet LAN** (peers on `192.168.1.x` reaching a node on
+`192.168.2.x`, or a VM) additionally needs:
+
+- routing between the subnets (the two ends must have a path to each other's
+ host-candidate address);
+- inbound UDP on the ephemeral range allowed for `meshbay-node.exe` **and** for
+ any Linux host in the path — see the packaged `packaging/firewall/` profiles
+ and the "inbound WebRTC" section of `docs/PACKAGING-GUIDE.md`;
+- `MESHBAY_WEBRTC_EXPOSE_LOCAL_IPS` is not relevant to the node — that switch is
+ the *client's*, and it is already on by default (`src/main.js`).
+
+**libvirt NAT is the awkward case.** A guest on the default NAT network is
+reachable from its own hypervisor but not from other LAN machines, and its only
+host candidate is the `192.168.122.x`/`192.168.200.x` address no third machine
+can route to. Give the guest bridged (or macvtap) networking so it gets a real
+LAN address, or run the node on the host. The node log now prints the host
+addresses it offered — `WebRTC answer ready for peer=… (… host: 192.168.200.173,
+1 srflx)` — so "did the node even offer something routable" is answerable from
+the journal.
+
## Dependency surface that needs watching
`meshbay-node.spec` pulls the awkward packages in whole (`collect_all`) because
diff --git a/packaging/win/meshbay-node.spec b/packaging/win/meshbay-node.spec
index f1f994d..324d8c1 100644
--- a/packaging/win/meshbay-node.spec
+++ b/packaging/win/meshbay-node.spec
@@ -12,6 +12,44 @@
# frozen run raises ModuleNotFoundError — that is the expected way this list grows.
from PyInstaller.utils.hooks import collect_all, copy_metadata
+from PyInstaller.utils.win32.versioninfo import (
+ FixedFileInfo,
+ StringFileInfo,
+ StringStruct,
+ StringTable,
+ VarFileInfo,
+ VarStruct,
+ VSVersionInfo,
+)
+
+# Version resource stamped into meshbay-node.exe so the Windows Firewall prompt,
+# Task Manager and the file's Properties read "MeshBay Node" rather than a bare
+# exe name. Read from the installed package so it cannot drift from pyproject.
+try:
+ from importlib.metadata import version as _pkg_version
+
+ _ver = _pkg_version("meshbay-node")
+except Exception:
+ _ver = "0.0.0"
+_vtuple = tuple(
+ int(p) for p in (_ver.split("+")[0].split(".") + ["0", "0", "0", "0"])[:4]
+)
+
+_version_info = VSVersionInfo(
+ ffi=FixedFileInfo(filevers=_vtuple, prodvers=_vtuple),
+ kids=[
+ StringFileInfo([StringTable("040904B0", [
+ StringStruct("CompanyName", "MeshBay"),
+ StringStruct("FileDescription", "MeshBay Node"),
+ StringStruct("FileVersion", _ver),
+ StringStruct("InternalName", "meshbay-node"),
+ StringStruct("OriginalFilename", "meshbay-node.exe"),
+ StringStruct("ProductName", "MeshBay Node"),
+ StringStruct("ProductVersion", _ver),
+ ])]),
+ VarFileInfo([VarStruct("Translation", [0x0409, 1200])]),
+ ],
+)
# Packages taken in full: data files + shared libs + every submodule.
COLLECT_ALL = [
@@ -114,6 +152,7 @@ exe = EXE(
codesign_identity=None,
entitlements_file=None,
icon="../../packages/meshbay-client/build/icon.ico",
+ version=_version_info,
)
coll = COLLECT(
exe,