summaryrefslogtreecommitdiffstats
path: root/packaging/systemd
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-18 09:42:34 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-18 09:42:34 +0200
commit30e855f55f1d920b25da0bdd8e538c249d3c0c26 (patch)
tree587dcad0beb8a120352613be8aa751a97040015c /packaging/systemd
parent768e07046368819b8a8f15c8b21e5a8bbfcdf282 (diff)
downloadmeshbay-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/systemd')
-rw-r--r--packaging/systemd/meshbay-node-user.service63
1 files changed, 63 insertions, 0 deletions
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