<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packaging/win, branch 0.17</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.17</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.17'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-28T14:24:12Z</updated>
<entry>
<title>refactor: remove the unused GroupIndex.serialize chain</title>
<updated>2026-09-28T14:24:12Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-28T14:24:12Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=a4b36e5fe31cb671a4cbbdaad75746cd68b601fe'/>
<id>urn:sha1:a4b36e5fe31cb671a4cbbdaad75746cd68b601fe</id>
<content type='text'>
serialize/deserialize had no production caller, and took with them the
per-chunk signature, the ChaCha20 cipher variant and the zstandard
dependency. Key derivations are unchanged. Docs corrected, including
design §4.3's claim that chunks are compressed.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: the Windows node's three modes and one lifecycle, and what it cost</title>
<updated>2026-09-27T20:21:42Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:21:42Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6c7b61a8e946b777ea1985058dbed9019bddd53a'/>
<id>urn:sha1:6c7b61a8e946b777ea1985058dbed9019bddd53a</id>
<content type='text'>
- MESHBAY_DESIGN.md §11.2: the node runs only while the app is open, at
  sign-in, or as a boot-time service; starting and stopping have one
  implementation, the CLI's; a second instance refuses before it writes
  anything the running one depends on.
- packaging/win/README.md: the three modes, switching between them, upgrading
  a running node, where the log is, the service task's settings.
- docs/windows-build.md: the build's smoke start of the frozen daemon, the log
  location, and what an upgrade does to a running node.
- CLAUDE.md: four engineering lessons -- an upgrade that cannot stop the node
  installs around it; on Windows the CLI is the process it is stopping; a
  second instance must fail before it touches anything shared; a test that
  redirects HOME isolates nothing on Windows.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: Windows installer and desktop app start and stop the node one way</title>
<updated>2026-09-27T20:20:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:20:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=8c7e39b6dca758badec6867ab6610fd5e8d93d1e'/>
<id>urn:sha1:8c7e39b6dca758badec6867ab6610fd5e8d93d1e</id>
<content type='text'>
A 0.16 upgrade in service mode left the previous node running: setup's
unelevated taskkill cannot reach session 0, and it ran in customInstall, which
electron-builder inserts after the files are copied. The locked exe was not
replaced, and the new app talked to the old node ("started but could not link",
"No operator paired").

Installer (build/installer.nsh, build/stop-node.ps1):
- customCheckAppRunning, which runs before uninstallOldVersion and extraction,
  stops the node with an embedded stop-node.ps1: control API, then schtasks
  /end, then Stop-Process, and refuses to half-upgrade if one survives.
- An upgrade keeps the mode it finds (task, launcher, previous install),
  restores the sign-in launcher the old uninstaller deletes, and restarts the
  node the way that mode runs it. A silent upgrade of an "at sign-in" install
  used to end with no autostart and no node.
- The uninstaller removes the task and firewall rules only on a real
  uninstall, not on an update.

Desktop app (src/main.js):
- Start, Stop, Restart and node:start go through the CLI's lifecycle verbs
  instead of a second implementation; a child spawned by Electron also held
  Electron's sockets after the app quit.
- "Only while MeshBay is open" is a real mode: the app starts a provisioned
  node at launch and stops the one it started when it quits.
- Switching modes stops the node first -- deleting a task does not end its
  instance, and a new service found the port taken -- keeps the firewall
  rules every mode needs, and starts the node again. A declined or unanswered
  UAC prompt restores the node instead of leaving it stopped, and says that
  nothing changed.
- waiting_for_hub counts as a node that is up; linking waits for a node that
  answers, with a longer deadline, and reports a version mismatch.

Packaging (packaging/win):
- The service task gets no 72-hour limit, runs on battery and ignores a second
  start; service.ps1 status reports a stale registration so setup re-registers
  it; remove ends the running instance before deleting the task.
- build-node-runtime.ps1 starts the frozen daemon in a throwaway profile
  (smoke-node-runtime.ps1) instead of only asking for --help.

The mode that was "Off (start manually)" is labelled "Only while MeshBay is
open" in all ten catalogues.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): read the packaged TMDB token in place</title>
<updated>2026-09-26T10:23:16Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-26T10:23:16Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=fc761e7df40eac828d4e9858fab56958078c928b'/>
<id>urn:sha1:fc761e7df40eac828d4e9858fab56958078c928b</id>
<content type='text'>
A node onboarded by the desktop client never ran `init`, so default.env was
never copied to node.env; and default.env was 0600 root, unreadable to a
per-user node anyway. The daemon now loads it beneath node.env, 0644.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: drop the references to design notes that live outside the repo</title>
<updated>2026-09-18T23:15:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-18T23:15:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=cc0820de8d8c33560c44bd6a0864de30e33a0109'/>
<id>urn:sha1:cc0820de8d8c33560c44bd6a0864de30e33a0109</id>
<content type='text'>
Seventeen comments across the Windows packaging targets pointed at
C:\Users\admin\devel\light-client.md and msix-installer.md -- absolute
paths on one developer's machine, unreadable to anyone else who clones
this repository and unverifiable by any test here.

The surrounding prose already carried the substance in every case, so
these are removals, not rewrites, with two exceptions where the pointer
was doing real work:

  electron-builder.msix.yml's header told the reader to go read §4 and §8
  first. It now states the fact directly (an AppX/MSIX install never
  elevates, by design, so the package carries none of installer.nsh's
  elevation logic) and says the open items are called out at each
  declaration below -- which they already were, at `capabilities` and
  `customExtensionsPath`.

  The two "msix-installer.md §8" citations become "an open item" / "the
  other open item", beside the description of the item that was already
  there.

Paragraphs the removals left ragged are re-wrapped.

Verified: node suite 1403 pass / 4 skip (71 of them test_packaging_win.py),
both electron-builder configs still parse as YAML, node --check on main.js.
The .ps1 edits are inside &lt;# #&gt; comment headers plus one deleted Write-Host
in a block that keeps two others; no pwsh on this machine to parse them.

Not touched, and much larger: ~230 comments elsewhere in the codebase cite
per-feature design notes (musicbay.md, mediacenter.md, auth-confirm.md and
twenty more) that were merged into docs/MESHBAY_DESIGN.md.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: decode and re-encode video on the GPU where there is one</title>
<updated>2026-09-18T12:37:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-18T12:37:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=d22ce0ba5aebfff5ffaea97982472f876af5da22'/>
<id>urn:sha1:d22ce0ba5aebfff5ffaea97982472f876af5da22</id>
<content type='text'>
A 1080p film is decoded by whoever watches it and re-encoded by the node
when no browser can decode the source. Both were on the CPU, and on an
Atom or Celeron mini-PC neither reaches real time — which is what
`transcode_incompatible_video` exists to refuse. This adds the mechanism
that makes refusing it unnecessary.

Node — `hwaccel.py`: VA-API on Linux, Quick Sync or NVENC on Windows,
established by encoding 1080p and reading the file back with ffprobe.
Nothing is accepted that does not produce the exact profile and level
`stream_init` announces, since the client checks that string before it
trusts a byte: an encoder that wrote another level would make the node's
own codec string a lie, and ffmpeg takes `-level 4.1` and `-level 41`
from h264_qsv without saying which it understood. Three modes per
stream — hardware decode and encode, hardware encode alone, libx264 —
demoted per source codec, because a GPU that decodes HEVC may have no
decoder for MPEG-4 Part 2 and only asking it finds out. A mode that
fails is detected on an empty stdout before `stream_init` goes out, so
the viewer sees one working stream and never an error.

Client — Chromium ships VA-API off on Linux. It is enabled where a
render node and a driver are present, then verified through
`navigator.mediaCapabilities`: a no moves to the next GL backend on the
next launch and an exhausted list drops the switches, so a renamed
feature cannot pass for a feature that is on and `--ignore-gpu-blocklist`
cannot survive on a machine it did not help. Feature lists now merge
rather than overwrite — `appendSwitch` replaces the value, and a second
caller would have silently cancelled the mDNS switch aiortc depends on.

Packaging — the drivers are weak dependencies on all four formats, so a
machine without a GPU installs exactly as before. `dpkg -i` and
`rpm -ivh` ignore weak deps; `packaging/README.md` now says so. Windows
needs no driver: the bundled ffmpeg already carries h264_qsv and
h264_nvenc, and a re-pin that dropped them would cost every low-power
Windows node its hardware encoding silently.

AMD on Windows (AMF) and macOS (VideoToolbox) are named gaps, not
oversights: neither could be tried anywhere in this project, and both
re-encode in software as before.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(win): a service-mode daemon can be replaced, and the Node page can link one</title>
<updated>2026-09-14T21:51:36Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-14T21:51:36Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e12570f9d1aa2645e6bb223b1417fa0e81957b65'/>
<id>urn:sha1:e12570f9d1aa2645e6bb223b1417fa0e81957b65</id>
<content type='text'>
Two live-reproduced bugs in Windows node start/stop, found sideloading the
0.14.0 build:

- node:start's crash-recovery step killed a service-mode daemon with
  taskkill/CTRL_BREAK, both of which fail with "Access is denied" against a
  process running under the Scheduled Task's own S4U logon session (a
  different session from the Electron app's). The daemon it was meant to
  replace just kept running, unreplaced, and schtasks /run on a task Windows
  still considered Running was then a silent no-op too. Route through
  winServiceTaskEnd() (schtasks /end) first, the way nodeServiceStop/
  nodeServiceRestart already correctly do. service-mode.ps1 also now starts
  the task right after registering it -- Register-ScheduledTask's own
  AtStartup trigger does not run it immediately, so nothing was listening
  until the next reboot.

- The Node page's Start button called node.start() with no arguments, so an
  unlinked node (a fresh install, or one whose hub-side link was lost) could
  never link on Start alone -- only create-group-page.js's own call passed
  {hubUrl, username, token}. Reproduced on a fresh non-service install signed
  in to the real hub: Start hung for ~105s and failed with "could not link",
  pointing at a "Link Node" control that lives on Settings, not the Node
  page (that message is fixed too).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(packaging): three MSIX first-run regressions found by a real sideload</title>
<updated>2026-09-12T13:48:22Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-12T13:48:03Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=9cc2909cb4a360c81b471ceab1d9578a7655a88e'/>
<id>urn:sha1:9cc2909cb4a360c81b471ceab1d9578a7655a88e</id>
<content type='text'>
A second-machine sideload of the MSIX target surfaced three things the
earlier verification round (which only proved the package installs and
runs) had missed:

1. meshbay-node missing from PATH. installer.nsh's customInstall adds
   node-runtime\ to HKCU\Environment at install time -- an unelevated
   per-user write, never blocked by MSIX's no-elevation rule, only by the
   more basic fact that an AppX/MSIX install runs no custom code at all.
   packaging/win/ensure-node-path.ps1 (idempotent, no admin verb) plus
   main.js's winEnsureNodeOnPath() do it from the app itself instead, once
   per launch, shipped to Full and MSIX (not Light, nothing to add there).
   Verified live via the Node inspector protocol: the entry was in
   HKCU\Environment\Path after a launch, absent before.

2. A daemon that crashes on startup failed silently. spawnNodeDetached()
   used stdio: 'ignore', so a real crash reproduced live (a second instance
   colliding with the first on 127.0.0.1:18000) left waitForNode()'s
   generic 60s timeout as the only failure ever shown. spawnNodeDetachedWatched()
   pipes stdio and watches ~2.5s, rejecting immediately with the daemon's
   own stderr on an early exit; a survivor has its streams released and
   runs fully detached exactly as before. First version bounded the
   captured text by line count and a live test showed that cut the actual
   OSError line -- two uvicorn/asyncio tracebacks followed it in the real
   capture -- so it is bounded by characters instead.

3. No hint that a startup-mode choice exists. The install-time radio page
   was the only place this was ever offered, and nothing replaces it now
   that no install-time page can exist at all. SetupWelcome (the existing
   first-run banner) grew a conditional hint, shown only while a bundled
   node is present and neither autostart nor service mode is configured
   yet. Considered and rejected: linking straight to the Node page -- its
   route is gated on a linked hub node key, false on the exact fresh-install
   screen this hint targets, so the link would have been dead on arrival.
   New key setup.node_startup_hint, added to all ten locale catalogues.

test_packaging_win.py gained six tests pinning all three (69 total).
Full plan and verification detail: C:\Users\admin\devel\msix-installer.md
section 13 (out of repo).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(packaging): an MSIX target for Microsoft Store submission</title>
<updated>2026-09-11T15:51:51Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-11T15:47:42Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=edbff1768054afa80efda721cd1011b29c7fe355'/>
<id>urn:sha1:edbff1768054afa80efda721cd1011b29c7fe355</id>
<content type='text'>
Store certification of the NSIS "MSI/EXE" submission failed on three
checks (silent-install verification, Add/Remove Programs entry, bundleware
check) -- traced and reproduced live to one cause: SmartScreen blocks an
unsigned, internet-downloaded installer at the shell layer before
Microsoft's own unattended validation bot ever gets to run it. MSIX
sidesteps this class of failure entirely: submitted through the Store's
native pipeline, there is no browser-download-then-launch step for
SmartScreen to intercept, and Microsoft signs the package itself at
publish time -- free, and specific to this submission type (Trusted
Signing remains a paid service for the MSI/EXE path). Full plan and
findings: C:\Users\admin\devel\msix-installer.md (out of repo).

electron-builder.msix.yml carries the same bundle as Full (node runtime,
ffmpeg, both service scripts) -- an AppX/MSIX install never elevates, by
design, but that changes only *when* the two elevated operations can run,
not whether the daemon ships. No main.js changes were needed: the on-demand
elevation path for service-mode (winElevateServiceMode(), driven from the
Node page) already existed for a different reason and depends only on
service-mode.ps1 being present as an extraResource, true for any packaged
Windows target. identityName/publisher/publisherDisplayName are the real
values from Partner Center's app-identity reservation, not placeholders.

build-win-msix.ps1 points electron-builder at the system Windows 10 SDK
(auto-detected) instead of letting it download its own bundled copy --
that download's 7z extraction creates symlinks this target never uses and
fails without SeCreateSymbolicLinkPrivilege, reproduced on this machine.
build/appx/ carries the four tile images the AppX target requires
regardless of showNameOnTiles, generated once from the existing app icon
(see that directory's README) since the system-SDK redirect has no vendor
samples to fall back to. build/appx-extensions.xml declares
windows.startupTask by hand rather than via electron-builder's
addAutoLaunchExtension, which always targets the Electron shell -- this
points at the bundled node binary instead, matching what "starts at sign
in" already means for Full.

Verified live via a signed sideload install (self-signed test cert,
cleaned up after): the package installs and the app runs correctly. One
finding worth carrying forward -- the declared network capabilities
(internetClientServer, privateNetworkClientServer) do not create any
firewall exemption for this app, most likely because automatic
capability-based exemption is an AppContainer-sandbox property and this
app deliberately runs full-trust, outside any sandbox. Not a regression:
no install-time elevation was possible either way, so the cost is the same
one-time OS firewall prompt firewall.ps1's own header already documents as
its fallback today.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(packaging): a Light installer target with no bundled node runtime</title>
<updated>2026-09-11T15:51:51Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-11T12:17:50Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=bca6fc3f0884fcdb0455b502ee4495b04945baee'/>
<id>urn:sha1:bca6fc3f0884fcdb0455b502ee4495b04945baee</id>
<content type='text'>
MeshBay Light ships the Electron client + UI only -- no PyInstaller node
freeze, no ffmpeg, no service install/autostart. Two standalone
electron-builder configs (Full via package.json's build field, Light via
electron-builder.light.yml passed with --config, which reads only that
file -- confirmed against app-builder-lib's own config loader) rather than
one config branching on a flag.

build-win-common.ps1 holds the steps both orchestrators share (Node check,
npm ci, Electron bump, sync-ui) so build-win.ps1 (Full) and the new
build-win-light.ps1 cannot drift apart; build-win.ps1 is refactored to
dot-source it with no behavior change (rebuilt and diffed byte-identical
output).

installer-light.nsh keeps the one thing Light still needs -- an
unconditional firewall rule, since the client listens too -- and none of
the service-mode/autostart machinery installer.nsh carries, which has
nothing to gate without a bundled node.

dist-light/ (Light's own electron-builder output dir) gets its own
.gitignore line since the bare dist/ rule does not match it.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
