<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-node/src, branch 0.11</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.11</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.11'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-05T14:39:09Z</updated>
<entry>
<title>chore: bump all packages to 0.11.0</title>
<updated>2026-09-05T14:39:09Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T14:39:09Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=5b4c4df56bcc795be6a8416987e2d91f91ea14ba'/>
<id>urn:sha1:5b4c4df56bcc795be6a8416987e2d91f91ea14ba</id>
<content type='text'>
Co-Authored-By: Claude Opus 4.6 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): register the service-mode task with Register-ScheduledTask -LogonType S4U</title>
<updated>2026-09-05T12:48:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T12:48:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=c11dd22b593358ef7932deec53c8200f5f14ed8b'/>
<id>urn:sha1:c11dd22b593358ef7932deec53c8200f5f14ed8b</id>
<content type='text'>
schtasks.exe has no flag naming the logon type directly -- it only infers
S4U vs Interactive from whether /rp is present, and both readings broke
live on a blank-password account: /rp "" fails schtasks' own credential
validation, and omitting /rp registers "Interactive only", which never
launches the process at boot or on demand despite installing cleanly.

Register-ScheduledTask -LogonType S4U names the logon type explicitly, no
inference. Confirmed live: install, manual start, and unattended boot-time
start all now work.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(win): graceful shutdown, one startup-mode control, and a stray-\r bug</title>
<updated>2026-09-05T11:06:55Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T11:06:55Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e89a57bb97b5a0d624e8d490b6b8aa38ba140817'/>
<id>urn:sha1:e89a57bb97b5a0d624e8d490b6b8aa38ba140817</id>
<content type='text'>
Windows-only changes, all found by actually running the previous session's
work rather than by review alone:

- CTRL_CLOSE_EVENT/LOGOFF/SHUTDOWN handler (platform.py, ctypes
  SetConsoleCtrlHandler) so closing a console window, signing off, or a
  system shutdown runs the daemon's real _shutdown() instead of Windows
  just ending the process — closing WebRTC sessions and any in-flight
  ffmpeg transcode instead of orphaning it. `taskkill /F` itself stays
  uncatchable (like SIGKILL), so autostart_run() now spawns with
  CREATE_NEW_PROCESS_GROUP instead of DETACHED_PROCESS and autostart_end()
  tries CTRL_BREAK_EVENT against the recorded pid first, falling back to
  the hard kill only if that doesn't stop it in time.

- Replaced the Node page's two independent autostart/service-mode toggles
  with one "start automatically" select (off / at sign-in / as a
  background service). The old pair let both be active at once — starting
  the daemon twice, at boot and at sign-in — and their layout broke
  wrapping inside .node-service's flex row. The new control always removes
  whichever mechanism is active before installing the target; platform.py's
  service_install() does the same on the CLI side. The "background
  service" option disables itself (with a hint pointing at the CLI) when
  running unpackaged, since service-mode.ps1/service.ps1/firewall.ps1 all
  assume an installed build's layout — verified live rather than assumed
  by actually running those scripts unelevated.

- findNodeBinary() no longer bakes a stray \r into resolved paths. Found by
  rebooting after enabling per-user autostart: where.exe listed two
  matches, and stdout.trim().split('\n')[0] only strips the whole string's
  ends, leaving line one's own trailing \r attached — which landed inside
  the Startup .vbs's quoted path and broke it with "Unterminated string
  constant" at boot. Fixed by splitting on \r?\n and trimming every line.

- Dependency audit for the Windows installer (docs/WINDOWS-PORT.md): no
  VC++ Redistributable needed, confirmed by inspecting the built
  node-runtime's actual import table rather than assuming. New
  docs/windows-build.md: a concise clone-to-installer build guide.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: opt-in Windows service mode (boot-time, one elevation) + v1.0.0</title>
<updated>2026-09-04T15:29:24Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T15:29:24Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=b78288640d8c13cc0fb3f4ee7c82f3efac33940f'/>
<id>urn:sha1:b78288640d8c13cc0fb3f4ee7c82f3efac33940f</id>
<content type='text'>
The per-user Startup-folder launcher (W3) only ever runs after this user
signs in. A real Windows Service would start earlier, but under
LocalSystem/NetworkService -- accounts with no normal profile, so
%LOCALAPPDATA%\meshbay\ (config, keystore, data) would not exist for it.
Relocating storage to make that work is real surgery, deliberately not
done here.

Instead: a Scheduled Task, created once with admin rights, that runs AS
THIS USER at boot without needing them to sign in first.
`schtasks /create ... /ru &lt;user&gt; /rp ""` with no `/it` registers an S4U
(Service For User) logon -- no password stored anywhere, and unlike
LocalSystem it loads this account's own profile, so config_dir()/
data_dir() need zero changes. The cost: S4U carries no network credential,
which the node never needed -- everything it touches is local disk plus
outbound internet. Creating the task needs admin (a boot trigger touches
system-wide scheduler state, the same reason /sc onlogon needed it);
querying/starting/stopping an existing one does not -- Task Scheduler
grants the owning user that much itself, which is what lets the Node
page's Start/Stop/Restart drive it with no further UAC prompts.

meshbay_node/platform.py
  service_install/_remove/_status/_run/_end -- mirrors autostart_* but
  for the Scheduled Task; TASK_NAME moved here (was decorative before)

meshbay_node/daemon.py
  new `service install|remove|start|stop|status` verb; restart-daemon and
  reset now check for the service task too

packaging/win/service.ps1
  the installer-side equivalent (extraResource); status/run/end never
  self-elevate -- only install/remove do, exactly matching what
  Task Scheduler itself requires

packaging/win/service-mode.ps1
  ONE elevated helper running service.ps1 + firewall.ps1 together, so
  choosing service mode costs exactly one UAC prompt, not two

build/installer.nsh
  the install-time choice: "run as a background service?" (one
  elevation, both jobs) vs the existing per-user + separate firewall
  question. Checked first, unelevated, so re-running setup with
  everything already configured asks nothing. Uninstall offers the
  matching one-elevation cleanup, default No.

src/main.js
  winServiceTaskStatus/Run/End, wired into node:installed,
  node:service-status/-stop/-restart and node:start: when the Scheduled
  Task exists, drive it; otherwise fall back to the existing per-user
  spawn/kill path. This is the hard requirement -- Start/Stop/Restart
  from the Node page must work in either mode.

node-page.js / locales
  a hint explaining why the per-user autostart toggle is absent when
  service mode is active (info.mode from the backend, no new field to
  gate on -- it just isn't sent in that case)

package.json: 0.1.0 -&gt; 1.0.0.

Verified: electron-builder compiles the new NSIS choice logic and ships
all three scripts; service.ps1's S4U install fails cleanly (Access
denied) when run unelevated, and its status/run/end never touch "runas".
Cannot verify the elevated success path myself (no admin in this
session) -- that needs a real UAC click. Node suite 843 pass / 25 skip;
test_packaging_win.py pins the one-elevation property, the S4U flags,
and that main.js actually checks the service task in all three handlers.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(node): log the host candidates the WebRTC answer offers</title>
<updated>2026-09-04T12:51:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T12:51:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=b53298e339b47c6f0b53ab9fd0cfa3fb7741fc28'/>
<id>urn:sha1:b53298e339b47c6f0b53ab9fd0cfa3fb7741fc28</id>
<content type='text'>
"DataChannel closed" from a peer and a clean node log look identical: the
answer-ready line reported only the srflx count, not the host addresses.
On a NAT'd host or a VM the sole host candidate is an address no other
machine can route to, and that is exactly the case you cannot see. The
line now reads `... host: 192.168.200.173, 1 srflx`, so "did the node
offer anything routable" is answerable from the journal.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: drop the retired Mozilla STUN server from the defaults</title>
<updated>2026-09-04T12:51:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T12:51:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=08b0f8d7c457e5e58a31bea2b2e43c9f398a6923'/>
<id>urn:sha1:08b0f8d7c457e5e58a31bea2b2e43c9f398a6923</id>
<content type='text'>
stun.services.mozilla.com no longer resolves — Mozilla shut the service
down — so every ICE gather waited out a DNS timeout on it. Removed from the
node defaults (config.py), the browser defaults (transport.js) and the Node
page's "reset to defaults" (node-page.js). Google (two endpoints) plus
Cloudflare still give two-provider coverage against a single outage, which
is the §2.12 resilience claim. draft-v6 §2.12 updated.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): say so when MusicBrainz lookups are inert</title>
<updated>2026-09-04T12:39:26Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T12:39:26Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=598158757a436eb6ea554f7ce34182dc96f8d851'/>
<id>urn:sha1:598158757a436eb6ea554f7ce34182dc96f8d851</id>
<content type='text'>
Staying inert without a contact is the documented policy (module docstring,
musicbay.md §3.1): the usage policy wants a contact in the User-Agent, so an
unidentified client is never sent. Staying *silent* about it was not a
decision — the operator sees Music tiles with no metadata or cover art and
has nothing to search the logs for.

Warns once per client rather than once per lookup, since the condition is
constant for the client's lifetime.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
<entry>
<title>fix(packaging): actually ship the TMDB token, on Linux and Windows</title>
<updated>2026-09-04T12:33:33Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T12:33:33Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=c2eade6db582966fa7fc3dd037f952baf3ae1cb5'/>
<id>urn:sha1:c2eade6db582966fa7fc3dd037f952baf3ae1cb5</id>
<content type='text'>
default.env was empty in every build, for three independent reasons:

1. build-node.sh read QE/node.env, which does not exist. Even pointed at the
   real file it would have failed: its `grep MESHBAY_TMDB_DEFAULT_TOKEN=`
   cannot match QE/tmdb.txt, which is a free-form note, not KEY=VALUE.

2. Nothing consumed default.env. packaging/README.md and build-node.sh both
   claimed `meshbay-node init` copies it to &lt;config&gt;/node.env; grep found the
   name in exactly two places, the README and the script that writes it. No
   code implemented the copy, and `EnvironmentFile=-` hid the absence.

3. build-win.ps1 had no env handling at all, so Windows was empty for a
   different reason than Linux.

Now: the build extracts the v4 read token -- tmdb.py sends `Authorization:
Bearer`, so it is the JWT, not the 32-char v3 key beside it in the same file --
matching KEY=VALUE first and then by shape, from MESHBAY_TMDB_TOKEN,
MESHBAY_TMDB_TOKEN_FILE, QE/node.env, QE/tmdb.txt. It writes default.env 0600
and *fails the build* if no token resolves; MESHBAY_ALLOW_NO_TMDB=1 opts out.
An empty default.env is invisible until a user opens Videos and finds no
metadata, which is how this shipped empty on two platforms at once.

platform.py gains packaged_default_env()/install_node_env()/load_node_env().
init copies the packaged file once, never overwriting an existing node.env,
and the daemon loads node.env itself at startup: systemd does this on Linux
via EnvironmentFile, but Windows autostart is a Startup-folder .vbs with no
equivalent. Already-set variables always win.

Also fixes an UnboundLocalError in main(): `config_dir` was assigned at the
top of the init branch, which made it function-local for all of main(), while
the reset branch calls `config_dir()` as the imported function. init returns
before that line, so `meshbay-node reset` could only ever raise. The local is
now cfg_dir.

Verified end to end on Linux: token baked (239 chars), init writes
&lt;config&gt;/node.env 0600 with it. The PowerShell half is written but unrun --
no pwsh on this machine.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
<entry>
<title>fix(node): make ice_interfaces match adapters on Windows (W9)</title>
<updated>2026-09-04T11:44:12Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T11:44:12Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=8288714853952aca6b3511268b9d772f1b7f489f'/>
<id>urn:sha1:8288714853952aca6b3511268b9d772f1b7f489f</id>
<content type='text'>
`ice_interfaces` compared the operator's entry against ifaddr's
`adapter.name` only -- the kernel name on Linux (`wlp3s0f0`), but the adapter
GUID on Windows (`{846EE342-...}`). A setting written on Linux, or copied
into a Windows node's node.toml, matched no adapter at all.

The failure was silent and total rather than partial: aioice binds one socket
per host address, so an empty list means no sockets, no host candidates, and
an SDP offering only a reflexive address. The settings field is free text
with no picker, and on Windows the operator sees neither the GUID nor the
description -- `ipconfig` shows the connection name -- so an entry now
matches the adapter name, the device description, or one of the adapter's own
IPv4 addresses, case-insensitively. An address is the one identifier visible
on every platform.

A filter that matches nothing now falls back to the unfiltered list with a
warning. Losing the 5 s timeout saving is a regression; being silently
unconnectable is a defect.

Also fixes IPv4/IPv6 discrimination in the same loop: the two were told apart
by falling through to an `elif` that index-probed `ip.ip[0]` and `ip.ip[2]`,
which on an IPv4 str yields characters that compared unequal by luck rather
than by design. Now discriminated by isinstance.

WINDOWS-PORT.md claimed Transport had "no platform dependency"; it does.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
<entry>
<title>feat: Windows daemon lifecycle (W3) — Startup-folder autostart</title>
<updated>2026-09-04T01:58:16Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T01:58:16Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=220e6e701806213a576ce80fa655cd9cf4a51880'/>
<id>urn:sha1:220e6e701806213a576ce80fa655cd9cf4a51880</id>
<content type='text'>
The Linux node runs under `systemctl --user`. Windows has no per-user
equivalent that works without elevation: `schtasks /create /sc ONLOGON`
(even `/rl LIMITED /it`) fails with "Access is denied" for a non-admin
user, because a logon trigger touches machine-wide scheduler state.

So autostart is a `.vbs` in the per-user Startup folder instead:

    CreateObject("WScript.Shell").Run Chr(34) &amp; "&lt;exe&gt;" &amp; Chr(34), 0, False

wscript runs it at every sign-in, hidden (0) and non-blocking. No admin,
no console window, no new dependency. Verified end to end: the launcher
brings the daemon up with no window and it answers its loopback API.

node/platform.py
  autostart_install/remove/status  — write / delete / detect the launcher
  autostart_run/end                — start now (DETACHED|NO_WINDOW) / taskkill
  _node_exe                        — PATH, then next to sys.executable, then argv[0]

node/daemon.py
  new `autostart install|remove|start|stop|status` verb
  reload (win32)         -&gt; POST /api/reload on the loopback API
  restart-daemon (win32) -&gt; autostart_end + autostart_run
  reset (win32)          -&gt; also removes the launcher

client/main.js, preload.js
  node:autostart handler + winAutostart* helpers (kept in step with platform.py)
  node:service-status (win32) probes the daemon; stop/restart/start use
  taskkill + a detached, windowless spawn

Tests: 8 autostart cases in test_platform.py (mocked sys.platform, APPDATA
pointed at tmp); `autostart status` added to the CLI dispatch sweep. Full
meshbay-node suite green on Windows (784 passed / 34 skipped).

Still open: no CTRL_CLOSE_EVENT handler, so a bare taskkill / window close
does not run _shutdown() (SetConsoleCtrlHandler, follow-up). Service mode
(pywin32/NSSM) stays Phase 2.

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