<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packaging, 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-05T12:48:08Z</updated>
<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>docs(packaging): correct four claims the README makes about the build</title>
<updated>2026-09-05T11:32:00Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T12:42:05Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=f808cf0ecaa3c6db023b1496f4b09cca5ca934f9'/>
<id>urn:sha1:f808cf0ecaa3c6db023b1496f4b09cca5ca934f9</id>
<content type='text'>
Checked every instruction in packaging/README.md against the code. Four were
wrong; the rest hold.

- `meshbay-hub --generate-keys` does not exist. The hub CLI takes --config,
  --log-level and a prune-groups subcommand, so the documented first step of
  the hub install fails with "unrecognized arguments". It is also unnecessary:
  app.py's lifespan generates the keypair on first start when the file is
  absent. Step removed, behaviour documented instead.

- The systemd table named two source files that do not exist. The system unit
  is built from systemd/meshbay-node.service and *installed as*
  meshbay-node@.service; the user unit comes from meshbay-node-user.service.
  Only the destination column was right.

- The output line pinned 0.9.0; every package is 0.10.0. Replaced with
  &lt;version&gt; so it cannot go stale again, and MESHBAY_BUILD_DIR is mentioned.

- The firewall section documented only the client's casting profile. It now
  covers the node profile added in 40abf09, with the LAN scoping that profile
  requires.

Verified as accurate and left alone: the /tmp/meshbay-build/out/ path, the
install order, hub.toml.example, the desktop file, and the default.env claim
(true as of c2eade6, which implemented the copy it described).

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
<entry>
<title>feat(packaging): bundle ffmpeg in the Windows installer by default</title>
<updated>2026-09-05T06:43:11Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-05T06:43:11Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=301c8998bfdcac80ad3302e2d2ebe853e2ea6de1'/>
<id>urn:sha1:301c8998bfdcac80ad3302e2d2ebe853e2ea6de1</id>
<content type='text'>
winget install ffmpeg was considered and rejected as the mechanism: it
needs network access and winget/App Installer present at the exact
moment setup runs, and its failure mode is silent -- video just does
not stream, with nothing pointing back at ffmpeg. Not viable for a
non-technical install.

MeshBay transcodes browser-incompatible video to H.264 (-c:v libx264,
webrtc_server.py) -- a real encode, not remux -- so this needs a genuine
GPL ffmpeg build; no LGPL-only build includes an H.264 encoder, since
libx264 itself is GPL.

packaging/win/fetch-ffmpeg.ps1 (new)
  Downloads, checksum-verifies and stages ffmpeg for the build. Source:
  BtbN/FFmpeg-Builds' Windows x86_64 gpl-shared preset -- shared DLLs
  rather than two independent static binaries, which is what nearly
  tripled this: the "full" static build many devs already have via
  winget is ~220 MB *per executable*. Pinned to one dated release tag
  (immutable once published) and its own sha256, not the "latest" alias
  BtbN repoints on every auto-build -- verified by hand first (downloaded,
  hash matched, ran a real encode+probe with libx264) before pinning.
  ffplay.exe (an SDL2 player, ~17 MB) is dropped; MeshBay never invokes
  it. Cached after the first build. Runs its own smoke test (encode +
  probe a real clip) so a broken fetch fails at build time, not for the
  first user who tries to watch something.

packaging/win/LICENSE-ffmpeg.txt (new)
  GPLv3 notice + where the corresponding source is, required because
  this redistributes a GPL binary even though it is unmodified and only
  ever invoked as a subprocess. Ships alongside ffmpeg.exe in the
  installer.

build-node-runtime.ps1 / build-win.ps1
  Bundling is now the DEFAULT, replacing the old opt-in -FfmpegDir (which
  copied from a local directory and left most builds without ffmpeg at
  all). -SkipFfmpeg opts out for a smaller, streaming-less local-iteration
  build.

  Also fixes a real bug the ffmpeg change exposed rather than caused: the
  final `--help` smoke test did `$help -notmatch "meshbay-node"` against
  $help captured as a PowerShell ARRAY (one element per line) -- -notmatch
  on a collection is a FILTER, not a boolean test, and returns the
  non-matching elements; any non-empty array is truthy in if() regardless
  of content. Once --help wrapped past one line (it now does, with
  autostart/service in the verb list) this threw unconditionally. Fixed
  by joining to one string before matching, and pinned by a new test so
  a future edit cannot silently reintroduce the collection-vs-scalar trap.

Verified: downloaded and hashed the pinned release by hand (matches),
ran a real libx264 encode + ffprobe against the extracted build,
fetch-ffmpeg.ps1 end to end (161 MB staged), a full build-node-runtime.ps1
run (308 MB node-runtime/) and a full installer build (MeshBay-Setup-
1.0.0.exe, 210.8 MB with ffmpeg bundled). Node suite 850 pass / 25 skip.

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>fix(packaging): re-running setup no longer re-prompts for firewall access</title>
<updated>2026-09-04T14:08:06Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T14:08:06Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=a4aabd1d33770d6199a8cb7bc87f639668617bc9'/>
<id>urn:sha1:a4aabd1d33770d6199a8cb7bc87f639668617bc9</id>
<content type='text'>
Every run of customInstall showed the "Allow MeshBay through Windows
Firewall?" question and, on Yes, a fresh UAC prompt -- an upgrade or repair
install would ask again even with all four rules already in place.

customInstall now checks first: firewall.ps1 check, unelevated
(Get-NetFirewallRule needs no admin, only New/Remove do), exits 0 if every
rule already exists. Only a nonzero result reaches the MessageBox and the
elevated add. A second run of setup on an already-configured machine now
asks nothing.

Verified unelevated: check exits 1 and logs which rules are missing on a
machine with none of them (the fresh-install case); electron-builder
compiles the nsExec::Exec / Pop $0 / ${If} wiring. Node suite 837 pass /
25 skip.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(packaging): cover LAN casting in the Windows firewall step, too</title>
<updated>2026-09-04T13:54:05Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T13:54:05Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=7ff675fdc0866ca40bdb703bc95c0cc60e3edd87'/>
<id>urn:sha1:7ff675fdc0866ca40bdb703bc95c0cc60e3edd87</id>
<content type='text'>
firewall.ps1 only handled WebRTC. The cast HTTP relay (src/cast-relay.js,
fixed TCP 19550-19553) and Chromecast/Smart TV mDNS discovery
(src/cast-chromecast.js, bonjour-service, UDP 5353) are a separate surface
on the client and were still hitting Windows' own "Allow access" dialog.

Now four named rules: MeshBay / MeshBay Node stay program-scoped with no
port restriction (there is no fixed WebRTC port to name -- the Windows
equivalent of the broad 1024-65535/udp range the Linux packaging needs,
since netfilter has no program scoping); MeshBay Cast / MeshBay Cast
Discovery add the program AND the fixed port, matching
packaging/firewall/*/meshbay-cast.xml exactly.

test_packaging_win.py cross-checks the port numbers against cast-relay.js's
own constants and the Linux firewalld definition, so the three descriptions
of one port range can't quietly drift apart.

Verified: rebuilt MeshBay-Setup-0.1.0.exe; the deployed firewall.ps1 carries
all four rules. Node suite 836 pass / 25 skip.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(packaging): offer one elevated firewall step instead of two dialogs</title>
<updated>2026-09-04T13:36:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T13:36:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=74941aae5451c03a296413710fe888b1924e8c27'/>
<id>urn:sha1:74941aae5451c03a296413710fe888b1924e8c27</id>
<content type='text'>
Installing used to mean clicking through two separate Windows "Allow
access" prompts later — one for MeshBay.exe, one for meshbay-node.exe —
each confusing on its own and worse before the exe carried a version
resource. Adding a firewall rule needs admin, and the installer is
deliberately per-user with no elevation, so this can only ever be opt-in.

packaging/win/firewall.ps1 (new, shipped as an extraResource at
resources\firewall.ps1): idempotent add/remove of the two inbound UDP
rules ("MeshBay", "MeshBay Node"), grouped, logged to
%TEMP%\meshbay-firewall.log. Locates both executables from its own path,
no arguments needed beyond the action.

build/installer.nsh: customInstall asks "Allow MeshBay through Windows
Firewall now?" and runs firewall.ps1 via NSIS ExecShellWait "runas" — one
UAC prompt — only when not ${Silent}; declining or dismissing UAC falls
back to Windows' own per-process prompts, unchanged. customUnInstall
offers the same in reverse, defaulted to No (a stale rule for a deleted
exe is inert, so this should not nag on the way out) and skipped for a
silent uninstall.

Verified: rebuilt MeshBay-Setup-0.1.0.exe (electron-builder compiles the
new LogicLib.nsh / ExecShellWait NSIS successfully); firewall.ps1 run
unelevated fails cleanly into its log ("Access is denied") rather than
silently doing nothing, confirming the fallback path. Node suite 835
pass / 25 skip.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(packaging): stamp the frozen node exe, document Windows networking</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=d04b915580c8ba05beb0943a6fab9b04e12294e4'/>
<id>urn:sha1:d04b915580c8ba05beb0943a6fab9b04e12294e4</id>
<content type='text'>
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 &lt;noreply@anthropic.com&gt;
</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>feat(packaging): ship a node firewall profile for inbound WebRTC</title>
<updated>2026-09-04T12:10:16Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-04T12:10:16Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=40abf0979f93771ccfb58eecb8a6fcc5863ec604'/>
<id>urn:sha1:40abf0979f93771ccfb58eecb8a6fcc5863ec604</id>
<content type='text'>
The packages carried a profile for LAN casting and none for the node's own
peer traffic, on the reasoning that the node exposes only a loopback admin
API. That is true of its administration surface and false of its transport.

WebRTC binds an ephemeral UDP port per connection, so there is no fixed port
to open, and a connection succeeds only if one side can initiate. Browsers
publish their host candidate as an mDNS `&lt;uuid&gt;.local` name, which aioice
cannot resolve on any platform and discards — so the node can never call a
browser back, and the browser must call the node. A node that refuses
unsolicited inbound UDP is unreachable from every browser on its own LAN,
leaving reflexive candidates, which fail whenever both peers share one public
IP and the router will not hairpin.

Hit twice in one session on two different hosts: a firewalld zone narrowed to
mdns + 19550-19553/tcp, and a ufw host with default deny-incoming. Both
presented as "the app cannot connect", neither as a firewall message.

Passive like the cast profile: packaged, not activated. The guide says to
scope it to a LAN zone or source, including the libvirt case, where traffic
from a guest to its own hypervisor is not masqueraded and so must be scoped
to the guest subnet rather than the LAN.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DtfG7z6wHWj8RKHCvxQtY1
</content>
</entry>
</feed>
