<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-node/src/meshbay_node/daemon.py, branch 0.18</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.18</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.18'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-10-05T09:10:50Z</updated>
<entry>
<title>fix(node): plug an auto-ejected removable root back once its files return</title>
<updated>2026-10-05T09:10:50Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-05T09:10:50Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6cdc6016d72dcfb7530ac38a8fa92232418ac305'/>
<id>urn:sha1:6cdc6016d72dcfb7530ac38a8fa92232418ac305</id>
<content type='text'>
A node started with the desktop session runs before the session has mounted
its USB drives. The safety net then auto-ejected every removable root and
persisted it exactly like an operator's eject, so after each reboot those
roots stayed ejected until someone plugged them by hand (seen on a node whose
/media drives were mounted a minute after it started).

An auto-eject is now stored as such ("auto" in roster.db). At startup and at
every reconcile, an auto-ejected root whose path is readable again is checked
against a few files the hash cache knows under it, at the same path with the
same size and mtime; one found and the root is plugged back and rescanned.
An empty mount point or another drive in its place is not recognised and
stays ejected. An operator's eject is never undone automatically.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): a revoked account is disconnected, not only refused next time</title>
<updated>2026-10-01T11:24:11Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-01T11:24:11Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=f0019e366fef813b22d2d55cf7604e20f0a08707'/>
<id>urn:sha1:f0019e366fef813b22d2d55cf7604e20f0a08707</id>
<content type='text'>
A user revocation closed nothing: the denylist stopped the next connection and
left the live ones streaming and chatting. Revocations now go through one
method that closes the account's or the group's sessions (F-21).

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): one member holds a share of the node, sized past real use</title>
<updated>2026-10-01T08:34:26Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-01T08:34:26Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=15e117673d2303bf476d4f78699e47913ce1aec0'/>
<id>urn:sha1:15e117673d2303bf476d4f78699e47913ce1aec0</id>
<content type='text'>
128 peer sessions on the node, at most 64 per account (the hub names the
account with each offer; the node's own account is not counted). One account
plays at most half the stream slots, rounded up, and runs two subtitle
extractions at once. Frames after the handshake are 8 MiB (was 64), decoded
with per-container bounds, and a frame refused for either ends the session
instead of jamming its buffer (F-16).

Sized for the heaviest real member: twenty groups on one node, three devices
and a tab, up to 52 sessions. Measured: ~0.15 MiB and one fd per idle session.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: nodes apply the content blocklist in their public groups</title>
<updated>2026-09-28T19:49:14Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-28T19:49:14Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=07480eb3f8ad0bb4369ac8c41df7c4140b108d0e'/>
<id>urn:sha1:07480eb3f8ad0bb4369ac8c41df7c4140b108d0e</id>
<content type='text'>
A node hosting a public group syncs the hub's blocklist on every
connection (paged, node token only) and applies pushed changes. A
blocked file leaves the index and is refused (content_blocked); private
groups are untouched. The unused per-hash check route is gone.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor: remove the public-content swarm</title>
<updated>2026-09-28T19:35:20Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-28T19:35:20Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=91505face56f7ee6817408e52bad7902add75f09'/>
<id>urn:sha1:91505face56f7ee6817408e52bad7902add75f09</id>
<content type='text'>
Nodes registered the hashes of their public groups on the hub and nothing
ever read them back. Routes, model and node registration removed; a
migration drops swarm_sources. No node sends the hub a content hash now.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node): a Windows daemon that stops properly, starts honestly and runs once</title>
<updated>2026-09-27T20:20:35Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:20:35Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=7662484cae8e74b7d9aa383bd6cd0dad4690aadc'/>
<id>urn:sha1:7662484cae8e74b7d9aa383bd6cd0dad4690aadc</id>
<content type='text'>
Found by installing the builds and driving every startup mode live:

- Stop through the node's own control API first (POST /api/shutdown, loopback
  and per-run token): the one channel that reaches a daemon in any session
  without elevation -- a service node runs in session 0 -- and the one that
  runs its shutdown. Then Task Scheduler, then a forced stop. Nine stops in a
  row used to log no shutdown at all: each was a TerminateProcess.
- The forced stop spares the command running it. The frozen meshbay-node.exe
  is the daemon and every CLI verb, so `taskkill /IM meshbay-node.exe` killed
  `autostart stop` and `restart-daemon` themselves: exit 1, no output, and no
  node after a restart. It excludes its own pid and its parent's, and /T takes
  a venv launcher's python child and a daemon's ffmpeg children with it.
- Start and restart report the version that answered, never "started" about a
  node nobody asked; `service start` says so when no node answered, and where
  the log is.
- A second instance fails before it touches anything. The daemon wrote
  ui-token, then failed to bind inside uvicorn's task and exited with the
  reason on a hidden console; the node still running then refused every stop
  and status, its token file naming a dead process. The control port is now
  bound first (exclusively on Windows, where SO_REUSEADDR would share it), and
  a refusal is logged and exits 2. Linux had the same order.
- The daemon logs to %LOCALAPPDATA%\meshbay\state\node.log: Task Scheduler
  discards its stderr. Only the daemon run opens it, never a CLI verb.
- Hub sign-in waits are interruptible, a stop requested before the node is up
  is honoured, and a hub that answers 429 or restarts leaves the node in
  waiting_for_hub rather than looking dead.
- operator_paired is null until the roster is read, instead of a false that
  showed "No operator paired" about a node whose pairing was intact.

The node test conftest also points HOME, USERPROFILE, LOCALAPPDATA and APPDATA
at a throwaway directory for every test, and keeps log_file() away from the
developer's own node: redirecting HOME alone isolates nothing on Windows, and
the CLI tests had been writing invite and pairing codes into the real profile.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node,client): survive a transient hub state on login, and surface a failed node-key link</title>
<updated>2026-09-25T16:59:20Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-25T16:59:20Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=2657ffd62ece8b8461d55b398139503ec504c3c6'/>
<id>urn:sha1:2657ffd62ece8b8461d55b398139503ec504c3c6</id>
<content type='text'>
Two defensive gaps turned a routine reset-and-reonboard into "impossible de
démarrer le node":

1. daemon._login_with_retry retried a 401 (node key not linked yet) but `raise`d
   on every other status, so a 429 — the daemon's own 5s retries hitting the
   sign-in rate limit — or a 502/503 while the hub restarts during a deploy
   killed the process, and systemd crash-looped it. Those statuses (429, 5xx)
   are now retried with a back-off that respects Retry-After, so a freshly
   reset node stays alive (the operator needs it up to read its key) instead of
   dying. A genuine 4xx (400/422) still raises.

2. create-group's linkNodeKey swallowed every error as "already linked or same
   key" — but PUT /me/node_key is idempotent and returns 200 on a re-link, so
   there was no benign error to hide: the catch only ever hid a real failure
   (a rejected session, a bad key), letting the wizard proceed against a node
   that looked linked but was not, which then could not authenticate. The link
   failure now surfaces (detectNode shows it).

test_login_retry_is_resilient.py holds the retry behaviour (429/5xx retried,
Retry-After honoured, 401 stays alive, 400 still raises); red before, green
after. common/node/hub suites green.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor(node): split ops.py into the ops package</title>
<updated>2026-09-24T23:30:47Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-24T23:30:47Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=762233772162a05be67432aa551a430b939250de'/>
<id>urn:sha1:762233772162a05be67432aa551a430b939250de</id>
<content type='text'>
Each section of ops.py becomes a module of meshbay_node/ops/ (core,
node_toml, members, chat, groups, roots, files, settings, apps), cut as
text; ops/__init__.py keeps the docstring and re-exports every name, so
`ops.&lt;name&gt;` is unchanged for every caller. Logger name unchanged.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor(node): move per-app enrichment out of NodeDaemon</title>
<updated>2026-09-24T16:30:10Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-24T16:30:10Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=387138410532daad174ed602fd03f4f979cd3d81'/>
<id>urn:sha1:387138410532daad174ed602fd03f4f979cd3d81</id>
<content type='text'>
The _enrich_*/_reenrich_* methods and _on_enriched become
EnrichmentMixin in meshbay_node/enrichment.py, with the two directory
helpers only they use. APP_DIR_KEYS stays on NodeDaemon.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>refactor(node): move the CLI out of daemon.py into cli/</title>
<updated>2026-09-24T16:19:13Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-24T16:19:13Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6e9afe3e54ab6fb0162393150b7892655b4561d4'/>
<id>urn:sha1:6e9afe3e54ab6fb0162393150b7892655b4561d4</id>
<content type='text'>
Each `if args.command == ...` branch of main() becomes a function in
cli/ (one module per family of verbs); the parser, the process setup
and a VERBS table go to cli/parser.py and cli/dispatch.py. daemon.main
runs the verb or starts the daemon. Tests patch the CLI through
conftest.patch_cli; the CLI golden is unchanged.

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