diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-18 09:42:34 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-18 09:42:34 +0200 |
| commit | 30e855f55f1d920b25da0bdd8e538c249d3c0c26 (patch) | |
| tree | 587dcad0beb8a120352613be8aa751a97040015c /packaging | |
| parent | 768e07046368819b8a8f15c8b21e5a8bbfcdf282 (diff) | |
| download | meshbay-30e855f55f1d920b25da0bdd8e538c249d3c0c26.tar.gz | |
feat(client): the platform seam, and an Electron shell that has never been run
Stage D, and the honest half of it.
D1 — the seam (done, and verified)
----------------------------------
`static/platform.js`. `HUB` becomes `platform.hubBase()` and the transport is
built with the same base, so one address has one source. In a browser it returns
'' and every path stays relative to the origin that served the page — the
acceptance criterion for this split was "the browser SPA behaves identically",
and it does. `platform.js` joins `_ASSETS`, or a change to it would not move the
content hash and a cached browser would never ask for it.
D2 — the shell (written, never launched)
-----------------------------------------
**There is no npm on this machine. Electron was never installed and
`packages/meshbay-client/` has not been run once.** That is stated here rather
than discovered later.
What is there: a main process serving the packaged interface over a privileged
`app://` scheme (`secure` and `standard` are not cosmetic — without them the
service worker refuses to register and streamed downloads break silently), a
preload exposing an enumerated bridge that never passes a filesystem path, a
window with `sandbox`, `contextIsolation` and no node integration, navigation
away from the package refused, and a CSP where the hub is reachable over
connect-src and is not a script source. The hub address arrives as a process
argument because `platform.hubBase()` runs before anything can await.
`test_desktop_shell.py` pins each of those by reading the source — the treatment
`test_downloads.py` already gives the three browser save paths. It catches a
property being removed and proves nothing about the application running. Two
were checked by breaking them.
The interface is *copied* into the package by `build/sync-ui.js` from the hub's
static directory, and `ui/` is gitignored: a silent fork is the only real way to
end up maintaining the interface twice.
D3 — partial
------------
The bridge, and the part worth having now: safeStorage's backend is reported
rather than assumed. On Linux it falls back to a fixed key when no keyring is
running, silently — someone who believes the OS is holding their keys is told
when it is not. The native key lifecycle belongs with D4 and needs a running
application to mean anything.
D8 — partial, and a real defect found
--------------------------------------
`meshbay-node.spec` installed the SYSTEM template — the one carrying `User=%i` —
into `%{_userunitdir}`. A user unit already runs as its owner and cannot carry
`User=`; systemd refuses the file, so the packaged unit could never have
started. Nothing noticed because nobody had built and installed the RPM.
Two units now: the template to `%{_unitdir}`, and a new `meshbay-node-user.service`
that a person enables themselves without a password — which is what lets the
desktop client install a node without asking for one. It carries ExecReload, so
`meshbay-node reload` does not have to stop a service somebody is streaming from,
and documents the drop-in for a drive outside the home, RequiresMountsFor
included.
798 tests pass; e2e.py still passes end to end. Nothing here was built or
launched: no npm, no rpmbuild.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packaging')
| -rw-r--r-- | packaging/rpm/meshbay-node.spec | 11 | ||||
| -rw-r--r-- | packaging/systemd/meshbay-node-user.service | 63 |
2 files changed, 73 insertions, 1 deletions
diff --git a/packaging/rpm/meshbay-node.spec b/packaging/rpm/meshbay-node.spec index 7052cff..5f28c52 100644 --- a/packaging/rpm/meshbay-node.spec +++ b/packaging/rpm/meshbay-node.spec @@ -40,8 +40,16 @@ Includes a local web UI at http://localhost:18000. %install %{python3} -m pip install --root %{buildroot} --no-index --find-links dist meshbay-node -# systemd user service (template) +# Two units, because there are two personas and one file cannot be both. +# +# The SYSTEM template carries User=%%i and is instantiated per person by root +# (`systemctl enable --now meshbay-node@alice`) — the server case. A *user* unit +# cannot carry User= at all: it already runs as its owner, and systemd refuses +# the file. This spec used to install the template into the user unit directory, +# where it could never have started. install -Dm644 packaging/systemd/meshbay-node.service \ + %{buildroot}%{_unitdir}/meshbay-node@.service +install -Dm644 packaging/systemd/meshbay-node-user.service \ %{buildroot}%{_userunitdir}/meshbay-node.service %files @@ -50,6 +58,7 @@ install -Dm644 packaging/systemd/meshbay-node.service \ %{python3_sitelib}/meshbay_node/ %{python3_sitelib}/meshbay_node-*.dist-info/ %{_bindir}/meshbay-node +%{_unitdir}/meshbay-node@.service %{_userunitdir}/meshbay-node.service %changelog diff --git a/packaging/systemd/meshbay-node-user.service b/packaging/systemd/meshbay-node-user.service new file mode 100644 index 0000000..833f31a --- /dev/null +++ b/packaging/systemd/meshbay-node-user.service @@ -0,0 +1,63 @@ +[Unit] +Description=MeshBay Node — P2P file host, streaming, and chat +Documentation=https://meshbay.org/docs +After=network-online.target +Wants=network-online.target + +# The per-user unit, for the desktop persona. +# +# Its sibling `meshbay-node.service` is a SYSTEM template with `User=%i`, which +# root instantiates once per person (`systemctl enable --now meshbay-node@alice`) +# — the ordinary gesture on a server. That is the wrong shape for someone +# running a node on their own machine: it needs a password they should not have +# to give, for a service that only ever runs as them. +# +# This one is enabled by the person themselves, and therefore by the desktop +# client on their behalf: +# +# systemctl --user enable --now meshbay-node +# loginctl enable-linger $USER # keep serving when logged out +# +# A user unit cannot carry User= or Group= — it already runs as its owner. + +[Service] +Type=simple + +# Secrets: MESHBAY_UNLOCK_KEY (keystore). MESHBAY_PASSWORD is no longer read — +# the node authenticates to the hub with an Ed25519 signature (NS7). +EnvironmentFile=-%h/.config/meshbay/node.env + +ExecStart=/usr/bin/meshbay-node --config %h/.config/meshbay/node.toml +# The client asks for a reload after changing a group's directories, and that +# must not drop a member who is watching a film. +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartSec=10 +TimeoutStopSec=30 + +# Data: chat DBs, roster, indexes — ~/.local/share/meshbay/ +StateDirectory=meshbay + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=false +ReadWritePaths=%h/.config/meshbay %h/.local/share/meshbay + +# A directory shared from outside the home — an external drive, another +# partition — is added by a drop-in the client writes, rather than by weakening +# ProtectSystem: +# +# ~/.config/systemd/user/meshbay-node.service.d/paths.conf +# [Service] +# ReadWritePaths=/run/media/%u/Films +# RequiresMountsFor=/run/media/%u/Films +# +# RequiresMountsFor matters as much as the path: a unit with ProtectSystem gets +# its own mount namespace, so a volume mounted on the host *after* this service +# started is invisible inside it — the directory reads as empty even when +# everything else is right. + +[Install] +WantedBy=default.target |