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
66 lines
3.8 KiB
Markdown
66 lines
3.8 KiB
Markdown
# packaging/systemd — the user unit pair for veloxd
|
|
|
|
Provided by lane DAEMON (`daemon/docs/AGENT-DAEMON.md` build step 7: "systemd user units
|
|
— `velox.service` + `velox.socket` for socket activation"), for PKG/QA to install per
|
|
`docs/07-packaging.md`'s layout:
|
|
|
|
```
|
|
/usr/lib/systemd/user/velox.service
|
|
/usr/lib/systemd/user/velox.socket
|
|
```
|
|
|
|
`packaging/` outside `nativehost/` is PKG/QA's per `CLAUDE.md`'s lane table; these two
|
|
files are here because they are inputs to that packaging step, not a claim on the rest of
|
|
the directory — same relationship `packaging/nativehost/` already has.
|
|
|
|
## Why both files, and what `RuntimeDirectory=` is doing
|
|
|
|
`velox.socket` binds `$XDG_RUNTIME_DIR/velox/velox.sock` **before `veloxd` ever runs** and
|
|
hands the daemon the already-listening fd at startup (`daemon/src/rpc/systemd_activation.cpp`
|
|
implements the receiving half — `LISTEN_PID`/`LISTEN_FDS`, fd 3 — without a `libsystemd`
|
|
link). Two things this buys over the daemon binding its own socket on every start:
|
|
|
|
- **No window where a client gets `ECONNREFUSED`.** The socket exists and queues
|
|
connections from the moment `velox.socket` is active, not from whenever `veloxd`
|
|
finishes starting up — this is the actual point of socket activation, not just "start
|
|
on demand."
|
|
- **Cold-boot ordering is free.** Nothing has to wait for `veloxd` to be ready before the
|
|
GUI, the CLI, or a native-messaging host can attempt a connection; the kernel queues it.
|
|
|
|
`RuntimeDirectory=velox` on the socket unit creates `%t/velox` (mode 0700) before the
|
|
`ListenStream=` bind — without it, binding fails outright the first time (nothing has
|
|
created the parent directory yet). `veloxd` itself creates that same directory
|
|
(`ensure_private_dir` in `runtime_dir.cpp`) for the case where it's started directly,
|
|
outside systemd (`./veloxd` in a terminal, still supported and how most of this daemon's
|
|
own testing runs) — the two paths converge on the same directory with the same mode
|
|
either way.
|
|
|
|
`UdsServer::start()` (`daemon/src/rpc/uds_server.cpp`) checks for the activated fd first
|
|
and, if present, skips create/bind/chmod/listen entirely — the socket file's lifecycle then
|
|
belongs to the unit (including `RemoveOnStop=yes` on stop), not to the daemon. Falls back
|
|
to binding its own socket exactly as before when not socket-activated (a manual run, or a
|
|
distro that ships the daemon without the unit files).
|
|
|
|
## What was deliberately left out
|
|
|
|
`velox.service` does **not** set `ProtectSystem=`, `ProtectHome=`, or `ReadWritePaths=`.
|
|
`saveTo.allowedRoots` is user-configurable to anywhere on the filesystem — an external
|
|
drive, a second mount, anywhere `fs/safepath.hpp`'s own canonicalize-and-check accepts —
|
|
not a fixed set of directories a unit file could enumerate ahead of time. A filesystem-level
|
|
sandbox here would turn a legitimately-configured save location into an opaque
|
|
`EROFS`/`EACCES` the daemon can't explain, in place of its own clear `-32011` — worse than
|
|
no sandbox, specifically for a download manager. `NoNewPrivileges=yes` is kept: it has no
|
|
such trade-off.
|
|
|
|
## Verifying socket activation without a real install
|
|
|
|
`systemd-analyze verify --user velox.service velox.socket` checks unit-file syntax (it
|
|
will complain that `/usr/bin/veloxd` and the `velox(1)` man page don't exist on a dev
|
|
box that hasn't installed the package — expected, not a unit bug). To exercise the actual
|
|
activation handshake without installing anything: bind a Unix socket, `dup2` it onto fd 3,
|
|
fork, set `LISTEN_PID=<child pid>` and `LISTEN_FDS=1` in the child's environment, clear
|
|
`FD_CLOEXEC` on fd 3, and `execve` `veloxd` — a real `session.hello` round-trips over that
|
|
fd with no `bind()`/`listen()` call ever happening inside the daemon for that run. This is
|
|
exactly what `velox.socket`'s `Requires=`/`ExecStart` sequence does in production; systemd
|
|
supplies the fd, `veloxd` doesn't know the difference.
|