aboutsummaryrefslogtreecommitdiffstats
path: root/packaging
Commit message (Collapse)AuthorAgeFilesLines
* fix(hub): a desktop solve reports no hostname at all, not "meshbay"Christophe Besson2026-09-021-3/+13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Registering from the native client failed with `captcha_failed` while the checkbox was green — a worse symptom than the one being fixed, because the widget now looked fine and only the hub's own log said otherwise: captcha solved on an unexpected host ''; allowed: ['localhost', 'meshbay', 'meshbay.org'] The previous commit assumed Google would report the host component of the origin, so `app://meshbay` would come back as `meshbay` and could sit in `allowed_hosts`. It does not. A solve Google cannot attribute to a domain reports an **empty** hostname, and no allowlist entry can match that. An empty entry is not the answer either: a blank in a TOML list is a typo far more often than an intention, and `load_config` drops blanks for that reason — `captcha.allow_unattributed_host` is a named flag instead, so the trade is stated where it is made. What it admits, plainly: every non-web client, not only ours. A file:// page or somebody else's Electron application look identical from here. That is the same bar the client's own origin would have been — main.js already records that `app://meshbay` is not a credential — and it is a bar: the captcha still has to be solved, per token, in something that can render it. What is given up is the origin restriction for non-web clients, not the captcha. Off by default, and a hub without the desktop client should leave it off. The refusal now names which of the two it is, since they need different answers: an unexpected host names the host, an unattributed one says to set the flag. docs/captcha.md §6 said `meshbay` was the value and told operators to add it; it now records what was measured and why the guess was wrong. The packaged example config carries the flag with the same warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UtzVrzM7e2tG9fSpkR9ML
* build(client): say when a build has reset the local chrome-sandboxChristophe Besson2026-09-021-0/+20
| | | | | | | | | | | | | | | | | | | | | | | | `build-client.sh` runs `npm ci`, which deletes node_modules wholesale, then re-extracts Electron's dist. A chrome-sandbox that had been made root-owned 4755 for local development comes back 755 and owned by whoever ran the build. Running the app straight from node_modules then aborts outright: FATAL: The SUID sandbox helper binary was found, but is not configured correctly. Rather than run without sandboxing I'm aborting now. Chromium refusing to start beats it quietly dropping the sandbox, and that refusal is baffling if the crash is not connected to a package build run minutes earlier — the two look unrelated, and the file's own mtime is 1980 either way, so nothing on it points at what happened. The build now says so, with the command to put it back. Said and not done: a build script has no business setting a setuid bit behind someone's back, and this one uses no sudo. The packaged app was never affected — packaging/deb/meshbay-client/DEBIAN/postinst does the chown+chmod at install time, which is where it belongs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UtzVrzM7e2tG9fSpkR9ML
* fix(packaging): ship the example hub config, and stop leaving /etc/meshbay openChristophe Besson2026-09-024-12/+112
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three defects, found while answering whether installing the .deb would land where the production server was just moved to by hand. - **The example config was never packaged.** `build-hub.sh` copied `packaging/conf/hub.toml.example` under `if [ -f ]`, and that path does not exist in this repo — so every package ever built shipped no example at all and said nothing about it. The postinst places no config either, on purpose (a shipped hub.toml is overwritten on upgrade; a shipped secret gets run in production), which left an installed hub with nothing to copy from. The file now exists, documents every key `config.py` reads including the captcha `allowed_hosts` the desktop client needs, and the copy is a hard failure rather than a silent skip. - **`/etc/meshbay` was created 0755.** It holds the hub's Ed25519 private key and its database password. The file modes protect the contents, but a world-listable config directory tells anyone with a shell what a hub keeps and where. Now 0750 root:meshbay, in both the deb postinst and the rpm scriptlet; the service reads it by group. - **The rpm would have failed to build on the new file.** `%files` claimed nothing under /etc, and rpmbuild refuses an installed file no line claims. It now declares the directory and the example, with explicit `%attr` and `%config` so an operator's edits become .rpmsave rather than vanishing. Package modes no longer follow the builder's umask either — the same source tree produced 775/664 on a machine with umask 002 and 755/644 with 022. `install -m` sets them. Verified by building: the deb now carries ./etc/meshbay/ at drwxr-x--- with hub.toml.example at 0644, and the embedded postinst tightens the directory as belt and braces rather than as the only thing making it right. The rpm path is unverified — no rpmbuild on this machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UtzVrzM7e2tG9fSpkR9ML
* chore(packaging): drop orphaned python3-meshbay-common deb controlChristophe Besson2026-09-011-18/+0
| | | | | | | | | | The directory held a single DEBIAN/control still pinned to 0.2.0. Nothing builds it: build-packages.sh packages meshbay-common (the bundled-venv deb under packaging/deb/meshbay-common/), which replaced the old system-Python python3-meshbay-common package. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QNPfgH6VWcRzJDZGuzy1jJ
* refactor(node): JSON-only control API, Node page absorbs the admin dashboardChristophe Besson2026-09-015-21/+8
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Remove the node daemon's server-rendered admin UI (GET / and /audit, the _render_* helpers and inline templates) and the `meshbay-node ui` CLI verb. The loopback control API stays; it is now JSON only, ruff-clean, and 453 lines (was 1074). Also drop three never-wired endpoints (/api/config, /api/chat/history, /ws/chat, plus broadcast_chat) and the pointless 18000/tcp firewall profiles. The desktop client's Node page (static/node-page.js) takes over what the dashboard showed, reorganised into six tabs (Overview, Groups, Roster, Peers, Audit, Settings): - Overview: version, node id, QUIC port, hub, index-cache maintenance - Roster: node-wide view with unpin - Peers and Audit: auto-load on open, no Load button - Audit: real usernames and group names (resolved from the roster and node.toml), Previous/Next pagination newest-first, Export CSV of every matching row - Settings: node settings, STUN, ICE, denylist, then Unlink from hub Backend: audit.get_entries gains `offset`; /api/audit and /api/peers resolve ids to names via a new _display_names helper; CSP tightened to default-src 'none' now that no HTML is served. draft-v6 sections 2.11 and 2.12 corrected -- the Node page uses the loopback API, not MNP. One capability is intentionally dropped: browser-based admin on a headless server. The CLI covers every operation there. See docs/refactor-node-ui.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MQCaZnde4Bjjdu84dhSuF5
* chore: bump version to 0.9.0v0.9.0Christophe Besson2026-08-311-1/+1
| | | | | | Packaging system complete and verified on Ubuntu 26.04 and Fedora 44. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(packaging): 4-package .deb/.rpm build system under /optChristophe Besson2026-08-3126-290/+829
| | | | | | | | | | | | | | | | | | Shared venv architecture: meshbay-common owns the Python venv with all pip deps pre-installed; hub and node add only their code into it. Client is a standalone Electron app. No pip runs at install time. - Add build scripts (packaging/build/) for common, hub, node, client - Add orchestrator build-packages.sh with deb/rpm auto-detection - Add .deb control/postinst for all 4 packages - Add .rpm specs for all 4 packages (replaces python3-meshbay-common) - Add Gnome .desktop launcher and icon resizing - Add firewalld services (meshbay-cast, meshbay-node) and UFW profiles - Update systemd units to use /opt/meshbay-common/venv/bin/ paths - TMDB token baked into node package at build time via QE/node.env - Fix package-lock.json sync for protobufjs override Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(node): fall back to a loose MusicBrainz query when the strict one finds ↵Christophe Besson2026-08-252-1/+13
| | | | | | | | | | | | | | | | | | | | | | nothing search_release() only ever tried a field-scoped exact-phrase Lucene query (artist:"..." AND release:"..."). Verified live against musicbrainz.org: any deviation from MusicBrainz's own spelling (a year suffix, an edition tag, an artist credited under an older/aliased name) drops it to zero results outright rather than a low-scored one, so the confidence check never even ran — this is why well-recognized artists were still getting almost no cover art. Add an unscoped loose-query fallback, escape Lucene special characters in the interpolated tag text, and make the confidence score consider the artist match too (not just the album title) now that the fallback has no field scoping to rely on. Also document MESHBAY_MUSICBRAINZ_CONTACT_DEFAULT in the systemd units and man page, mirroring MESHBAY_TMDB_DEFAULT_TOKEN's precedent — never a literal value in source, configured via node.env. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013XSohfUQQiaE77qyFLgSv3
* feat(node,hub): add Videos group app (poster grid, flat list, TMDB metadata)Christophe Besson2026-08-242-1/+7
| | | | | | | | | | | | | | | | | | | | | Implements docs/mediacenter.md: a "Videos" group application built on the existing files index rather than a separate catalogue. On the node side, new indexer enrichment (technical probe, filename/season parsing, thumbnail generation) runs per-file once an operator has chosen a video_root for the group, plus a TMDB client for on-demand poster/metadata lookups (never client-side, thumbnails delivered over the existing chunk path). On the hub side, a new video-app.js renders a lazily-mounted poster grid or a thumbnail-only flat list, with TMDB entirely optional per group. Along the way: the global apps registry now drives Settings' default-tab picker instead of a hardcoded list, and the video_root is configured from group Settings (like uploads) rather than from Files, with the node refusing to run any TMDB/thumbnail work until one is set. Fixes several bugs found via live testing against a real library, notably a race between two effects writing the same "image ready" state that could leave a poster grid spinning forever on a same-tab revisit — see mediacenter.md §5.4 for the full account of each one.
* chore: release 0.6.00.6Christophe Besson2026-08-233-3/+22
| | | | | | | | | | | | | The group UI's applications split, and the two missing-import bugs it surfaced and fixed along the way. MNP goes to 0.4: apps_enabled/apps_enabled_ack, and enabled_apps on the handshake ack, for the group-applications registry. Additive — a node that predates it is never sent the op, and a client that predates it never looks for the field. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016SF6RKNBKg9qejmoMJ9ybA
* fix: first-run wizard reliability and node startup performanceChristophe Besson2026-08-221-0/+22
| | | | | | | | | | | | | Node daemon no longer blocks startup on slow directory scans — initial indexing runs in the background so the node reaches "running" immediately after transports are up. Fixes the wizard failing to detect the node when large USB/NAS roots take minutes to scan. Also: wizard key-linking deadlock resolved (main.js links during poll), invite form stays in DOM during reconnects (disabled instead of destroyed), pairing code bridges to renderer, and firewall docs for LAN casting added. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(client): the platform seam, and an Electron shell that has never been runChristophe Besson2026-08-182-1/+73
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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>
* docs: settle the desktop client, and draft v6Christophe Besson2026-08-181-0/+64
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A design discussion on 2026-08-17 settled Phase 13 and, in doing so, changed four things the spec states. v6 restates only those; v5 stays authoritative for everything it does not touch, per the convention v5 itself used with v4. What changed: * The native shell is **Electron**, not pywebview — structural decision 18 reversed. The SPA depends on Chromium-class APIs (WebRTC, WebCrypto X25519/Ed25519, MSE, Service Workers), so keeping Chromium keeps transport.js, crypto.js, keyderive.js, downloads.js and sw.js *as the client*. A system webview meant reimplementing ~2500-3000 lines. The old "69 % reused" figure was measured against an app.js of ~2600 lines; it is 4586. * A group's content is **several named roots**, not one directory, because the planned video and audio libraries will not live in one folder on one disk. * **Device linking**: one person may hold several devices on a node, admitted by a key the node already pinned and bound by a one-time code the new device generates. Without it a native client is refused where a browser is not, and an account created natively could never be opened in a browser. * **Authorship is authenticated, not asserted** — chat senders sign, uploads have a provable owner, and delete authorization moves from the uploading key to the account. And one rule v5 assumed without writing down: **group-related server state lives on the node.** Verified for multi-root — SwarmSource carries hashes and endpoints, no paths. Also here: the Caddy configuration, which was a snippet in the roadmap that would have broken the SPA (it predates /a/<hash>/ asset versioning and would have 404ed /sw.js, silently killing streamed downloads on Firefox and Safari); and downloads.html, which becomes a security page once a release key exists. Phase 15 was re-read against device linking and is wrong as written: a sender key must be per **device**, never per person, or two devices sharing a chain produce key and nonce reuse — C1 again, one level down. senderkeys.py already fails this silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: release 0.5.0Christophe Besson2026-08-163-3/+22
| | | | | | | | | | | | | | | | | | | | | | | Two rounds of features and one long hunt. The hub gained leaving a group, a cap of ten live public groups per owner, and the rule that a group is listed only once a node has announced it — with `prune-groups` to collect the ones that never got one. Presence rides on the group list, from the registry the hub already keeps for signaling. The web client speaks ten languages, splits Profile from Settings, and reads chat the way it is written: newest first, paging backwards. The rest was one symptom — "close the viewer, the next video hangs" — with three independent causes underneath, none of which the test suite or e2e.py could see. A background task the loop only weakly referenced, collected while it held a transcode slot. A connection-state handler that forgot a peer without stopping it. And `await proc.wait()` deadlocking on ffmpeg's own unread output, which no amount of SIGKILL resolves. Found by instrumenting the node and reading the log, after two confident fixes that addressed real but different bugs. MNP goes to 0.2: PING/PONG and backward chat paging, both additive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: release 0.4.00.4Christophe Besson2026-08-163-3/+16
| | | | | | | | | | | | | | | | | | | | | | | | The web client speaks ten languages instead of one: French, Spanish, Brazilian Portuguese, Simplified Chinese, Japanese, German, Italian, Dutch and Polish, all formal, with `hub`, `node` and `GEK` deliberately left in English so the interface still matches the CLI and the docs. Catalogues are fetched per language rather than shipped together, plural forms go through Intl.PluralRules because Polish needs four of them, and locale matching keeps the region so pt-BR and zh-CN resolve to the files written for them. Splitting one module into a loader and ten catalogues gave the SPA a version dependency it did not have before, and the hub was serving static assets with no explicit freshness at all. A browser that cached half a deploy either rendered every string as its own key or, in the other direction, failed to link the module graph and showed nothing. Static responses now carry no-cache, which costs one conditional request and answers 304 with no body. The node and common packages carry no functional change; they move with the version because the three are released together. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: release 0.3.00.3Christophe Besson2026-08-153-3/+19
| | | | | | | | | | | | | | | | | | | | | | | Twenty-one commits since 0.2, and enough of them change what the thing does that moving the old tag would have been the wrong description. Node: video streaming paced by the client rather than pushed at it, and a stream that ends when the viewer closes instead of holding a transcode slot for two minutes. An operator can remove an empty directory and revoke a member over MNP. `meshbay-node group add` attaches another hub group without hand-editing node.toml. The node.toml operator key is gone; the roster is the only source of authority. Hub and web client: transfers outlive the page that started them, with a widget that shows the rate and can cancel them; downloads stream to disk in every browser, through the File System Access API where it exists and a service worker where it does not; a folder can be taken as a zip built in the browser. A group owner can remove a member and edit the description. Nodes are recorded at the address their signed announcement arrived from, not the one STUN told them about. Deleted accounts stop being counted while the connection log keeps their name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: release 0.2.00.2Christophe Besson2026-08-147-11/+27
| | | | | | | | | | | | All three packages together, as the conventions require, plus the RPM and DEB metadata and their changelogs. The tag said 0.2 while every package announced 0.1.0, which would have shipped an RPM claiming to be the reviewed build while containing a different protocol: the hub schema lost the user identity keys, tokens lost pk_user, and gek_bundle_store left the wire. Pre-1.0, a breaking change bumps MINOR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(node): Phase 11 — production-ready daemon with WebRTC, WS, chat, HTTPChristophe Besson2026-08-111-3/+8
| | | | | | | | | | | | | | | | | | The node daemon was previously a skeleton that only started QUIC/TCP servers and the local web UI. All browser-facing functionality (WebRTC, hub WebSocket, chat store, HTTP file API) lived in QE demo scripts. This rewrites daemon.py to be fully self-contained: - WebRTC transport for browser clients (aiortc DataChannel) - Hub WebSocket task (signaling, revocations, WebRTC offers) - ChatStore per group (SQLite in ~/.local/share/meshbay/) - HTTP file API per group (create_http_app on configured port) - Graceful shutdown (all transports, stores, tasks) - hub_client: _ws tracking + send_ws() for chat notifications - config: data_dir field for persistent state - systemd: security hardening (ProtectSystem, StateDirectory) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add RPM/DEB packaging artifacts — 5.10Christophe Besson2026-08-0910-0/+404
3 packages: python3-meshbay-common (dep), meshbay-hub, meshbay-node. RPM: spec files with pre/post scriptlets (useradd, systemd macros). DEB: DEBIAN/control + postinst for hub, control for node + common. Systemd: hub.service (system, security hardening) + node.service (user template @%i, EnvironmentFile for MESHBAY_UNLOCK_KEY). packaging/README.md: build + install instructions. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>