Files
vdm/packaging/nativehost/README.md
T
samiandClaude Sonnet 5 eb72aa522c daemon: build velox-nmhost, systemd socket activation + units, velox(1) man page
Closes build order items 7 (the systemd half) and 9 (D11 in deferrals.md).

velox-nmhost (nmhost/src/main.cpp, 185 lines): a poll()-driven byte pump between
Firefox's native-messaging framing on stdio (4-byte native-byte-order length prefix)
and veloxd's own NDJSON framing on the Unix socket. Reframes each direction, no JSON
parsing, no retry/backoff (the extension relaunches a fresh host on its own
reconnect), exits the moment either side closes. Deliberately dependency-free — no
veloxd_* library, no nlohmann_json — since it runs unconfined outside Firefox's
sandbox regardless of packaging format.

Two real bugs found and fixed while getting the integration test to actually pass
rather than hang, both exactly the class of bug a "trivial pump" invites:
1. Never set the pumped fds non-blocking, so the "drain what's available" read loop
   blocked on its own second read() instead of returning to poll().
2. stdin and stdout are two different descriptors (0 and 1), not one — an early draft
   polled POLLOUT on fd 0, which is opened read-only, so EOF and writability were
   never both observable through the same pollfd entry.

packaging/nativehost/com.velox.host.json + its own README.md supersede
AGENT-DAEMON.md's stale "four locations" line: spike S1 / ADR 0003 found only three
manifest locations are real (~/.mozilla/native-messaging-hosts/ for BOTH deb/tarball
and snap Firefox, /usr/lib/mozilla/... for deb/tarball only, the flatpak sandbox path)
— the fourth, ~/snap/firefox/common/.mozilla/..., is not read by snap Firefox at all.
The README spells out the per-user-manifest / postinst enumeration implication for
PKG/QA (postinst runs once as root; the two ~/-relative locations are per-user) and
flags that docs/07-packaging.md's own install-layout line only shows the one
root-owned path.

Socket activation: rpc/systemd_activation.cpp is a from-scratch sd_listen_fds() (env
vars only — LISTEN_PID/LISTEN_FDS, fd 3 — no libsystemd link) that UdsServer::start()
checks first, skipping its own create/bind/chmod/listen when systemd already bound
the socket. packaging/systemd/velox.socket + velox.service are the unit pair,
verified both by systemd-analyze verify and by an actual fork/dup2/execve simulation
of the activation handshake — a real session.hello round-tripped over the handed-off
fd with no bind() ever called inside the daemon for that run. velox.service
deliberately skips ProtectSystem=/ProtectHome=/ReadWritePaths=: saveTo.allowedRoots is
user-configurable to anywhere on the filesystem, and a sandbox here would turn a
legitimately-configured save location into an opaque EROFS/EACCES instead of the
daemon's own clear -32011.

cli/man/velox.1 documents the CLI as it actually exists today (add/ls/pause/resume/rm,
--json) — the queue/settings subcommands AGENT-DAEMON.md's build order originally
sketched aren't implemented in cli/src/main.cpp yet, so the page doesn't claim they
are. Checked warning-free with groff -mandoc -ww -z.

Full ctest: 57/57 (excluding the pre-existing, unrelated conformance failure noted in
earlier commits).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GRDjHGgpYmMoPE2UFbe7pP
2026-09-12 22:46:52 +04:00

6.5 KiB

packaging/nativehost — the Firefox native-messaging manifest

Owner: lane DAEMON (packaging/nativehost/** is explicitly DAEMON's per CLAUDE.md's lane table, unlike the rest of packaging/, which is PKG/QA's). This directory holds the manifest velox-nmhost (built from nmhost/) is registered under, and this file is the one place that says exactly which on-disk locations need it and why — the "install manifests to all four locations" line in docs/agents/AGENT-DAEMON.md's build order predates docs/adr/0003-native-messaging-under-snap.md, which cut that down to three real ones. Read the ADR before touching this list; it is the empirical spike, not a guess.

The manifest

com.velox.host.json in this directory is the template:

{
  "name": "com.velox.host",
  "description": "Velox download manager — native messaging bridge to veloxd",
  "path": "/usr/libexec/velox/velox-nmhost",
  "type": "stdio",
  "allowed_extensions": ["[email protected]"]
}
  • name is what the extension calls browser.runtime.connectNative("com.velox.host") with (extension/src/background/transport/native.ts's HOST_NAME) — do not rename one without the other.
  • allowed_extensions must match extension/manifest.json's browser_specific_settings.gecko.id exactly ([email protected]). A mismatch here is a silent failure: Firefox reports "no such native application" as if the manifest didn't exist at all, which reads exactly like a missing-file bug and is easy to chase in the wrong place.
  • path is /usr/libexec/velox/velox-nmhost — the .deb's install layout (docs/07-packaging.md). Every other packaging format needs a different path — the binary doesn't live at that absolute path inside a Flatpak sandbox or an AppImage mount, so whatever installs the manifest for those formats must rewrite this field to wherever it actually put the binary, not ship this file verbatim. That substitution is the packaging step's job, not this template's.

Where it has to be written, and why

Per ADR 0003 (spike S1, run against the real snap Firefox on the target machine — not a guess about how confinement "should" work):

Location Covers Why
~/.mozilla/native-messaging-hosts/com.velox.host.json deb/tarball Firefox, and snap Firefox The one location snap Firefox 154 actually reads. Firefox's snap launches native-messaging hosts outside the sandbox with the user's real $HOME — so this is not "the deb location that happens to also work"; it is the snap location, full stop. ~/snap/firefox/common/.mozilla/native-messaging-hosts/ (the intuitive "inside the snap" path) is not read by Firefox 154 snap — confirmed empirically, not inferred.
/usr/lib/mozilla/native-messaging-hosts/com.velox.host.json deb/tarball Firefox only System-wide, so it covers every user on the box for a real (non-snap) Firefox install — but confirmed not read by snap Firefox. Do not treat this path as "covers snap too"; that was the wrong assumption docs/05 §4 corrected in the same commit as the ADR.
~/.var/app/org.mozilla.firefox/.mozilla/native-messaging-hosts/com.velox.host.json Flatpak Firefox Flatpak's own sandboxed home. Untested on this machine (Firefox here is the snap, not flatpak) — carried over from docs/05 §4's original four-location list, which this table otherwise supersedes. Verify before relying on it in a release checklist.

That is three real locations, not four — the fourth (~/snap/firefox/common/.mozilla/native-messaging-hosts/) was the pre-ADR guess the spike disproved. If docs/agents/AGENT-DAEMON.md's build order still says "four locations" when you read this, it is stale; this table is the current source of truth alongside the ADR itself.

What this means for postinst (PKG/QA's file, not this one)

This directory ships the manifest template and documents the target paths; it does not install anything itself (packaging/** outside this one directory is PKG/QA's — see CLAUDE.md's lane table — and postinst specifically is a .deb-packaging concern this lane doesn't own the file for). For whoever writes it:

  • The two ~/-relative locations are per-user. postinst runs as root, once, at install time — it does not run once per user session. It needs either a real-user enumeration at install time (every UID with a home directory and no login shell of /usr/sbin/nologin-style exclusions, roughly what deluser --remove-home scripts already have to reason about) or a first-run hook that runs as the logged-in user (a systemd user unit's ExecStartPre, or the GUI's own first-run wizard) and writes its own manifest the first time it starts. docs/adr/0003's own follow-up section flagged this as open; it still is.
  • /usr/lib/mozilla/native-messaging-hosts/ is the only one of the three that's a plain root-owned, install-time write — no per-user enumeration needed for that one.
  • Detect snap Firefox and say so. docs/07-packaging.md already commits to this ("detects whether Firefox is a snap... prints... a one-line note that the extension will pair over loopback") — the detection matters here specifically because if Firefox turns out to be neither deb/tarball nor a snap this spike covers (a genuinely unknown or future packaging of Firefox), silently trusting NativeTransport to work is exactly the failure mode ADR 0003 exists to prevent. WebSocketTransport is the guaranteed fallback either way (ADR 0003's own decision) — nothing breaks if the manifest doesn't land correctly, it just means the extension pairs over loopback instead of the (opportunistic, not required) native path.
  • docs/07-packaging.md's own install layout line currently lists only the system-wide /usr/lib/mozilla/... path. That line is correct as far as it goes (it is one of the three locations, and the only pure root-owned one) but reads as if it were the whole story for native messaging; it predates this file and the ADR. Worth a line pointing here so the two documents don't quietly disagree — PKG/QA's call, not edited here since docs/07 is PKG/QA's own file.

The binary

nmhost/ builds velox-nmhost — see that directory's own src/main.cpp for what it does (a byte-level pump, no protocol logic) and docs/05-extension-spec.md §4 for the two transports it sits behind. It is deliberately dependency-free (not linked against any veloxd_* library) so wherever a packaging format's sandbox puts it, it has nothing else to go looking for at runtime.