aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-31 10:49:45 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-31 10:49:45 +0200
commit8d5c564e75ad2928e77ea66decc979366ca5ccd5 (patch)
tree7f88ceb2ccd838011bb2f399f148c7f9b39f75ec
parent0c2754d2d4cb1905d5e324f56fbc64e4afae526f (diff)
downloadmeshbay-8d5c564e75ad2928e77ea66decc979366ca5ccd5.tar.gz
feat(packaging): 4-package .deb/.rpm build system under /opt
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>
-rw-r--r--.gitignore4
-rw-r--r--packages/meshbay-client/package-lock.json41
-rw-r--r--packages/meshbay-client/package.json3
-rw-r--r--packaging/README.md102
-rwxr-xr-xpackaging/build/build-client.sh116
-rwxr-xr-xpackaging/build/build-common.sh75
-rwxr-xr-xpackaging/build/build-hub.sh73
-rwxr-xr-xpackaging/build/build-node.sh89
-rwxr-xr-xpackaging/build/build-packages.sh156
-rw-r--r--packaging/deb/meshbay-client/DEBIAN/control17
-rw-r--r--packaging/deb/meshbay-client/DEBIAN/postinst22
-rw-r--r--packaging/deb/meshbay-common/DEBIAN/control14
-rw-r--r--packaging/deb/meshbay-common/DEBIAN/postinst15
-rw-r--r--packaging/deb/meshbay-hub/DEBIAN/control17
-rw-r--r--packaging/deb/meshbay-hub/DEBIAN/postinst9
-rw-r--r--packaging/deb/meshbay-node/DEBIAN/control18
-rw-r--r--packaging/deb/meshbay-node/DEBIAN/postinst13
-rw-r--r--packaging/desktop/meshbay.desktop10
-rw-r--r--packaging/firewall/firewalld/meshbay-cast.xml7
-rw-r--r--packaging/firewall/firewalld/meshbay-node.xml6
-rw-r--r--packaging/firewall/ufw/meshbay9
-rw-r--r--packaging/rpm/meshbay-client.spec53
-rw-r--r--packaging/rpm/meshbay-common.spec25
-rw-r--r--packaging/rpm/meshbay-hub.spec107
-rw-r--r--packaging/rpm/meshbay-node.spec99
-rw-r--r--packaging/rpm/python3-meshbay-common.spec59
-rw-r--r--packaging/systemd/meshbay-hub.service4
-rw-r--r--packaging/systemd/meshbay-node-user.service2
-rw-r--r--packaging/systemd/meshbay-node.service2
29 files changed, 849 insertions, 318 deletions
diff --git a/.gitignore b/.gitignore
index 52897da..f08b5c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,3 +65,7 @@ packages/meshbay-client/node_modules/
# would otherwise swallow.
!packages/meshbay-client/build/
!packages/meshbay-client/build/**
+
+# packaging build scripts — real source, not build output
+!packaging/build/
+!packaging/build/**
diff --git a/packages/meshbay-client/package-lock.json b/packages/meshbay-client/package-lock.json
index 5b705b9..7838178 100644
--- a/packages/meshbay-client/package-lock.json
+++ b/packages/meshbay-client/package-lock.json
@@ -527,12 +527,6 @@
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
- "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
- "license": "BSD-3-Clause"
- },
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
@@ -637,12 +631,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/long": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
- "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
- "license": "MIT"
- },
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -3079,9 +3067,9 @@
}
},
"node_modules/long": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
- "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/lowercase-keys": {
@@ -3772,29 +3760,26 @@
}
},
"node_modules/protobufjs": {
- "version": "6.11.6",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.6.tgz",
- "integrity": "sha512-k8BHqgPBOtrlougZZqF2uUk5Z7bN8f0wj+3e8M3hvtSv0NBAz4VBy5f6R5Nxq/l+i7mRFTgNZb2trxqTpHNY/A==",
+ "version": "7.6.6",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz",
+ "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.4",
- "@protobufjs/eventemitter": "^1.1.0",
- "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/codegen": "^2.0.5",
+ "@protobufjs/eventemitter": "^1.1.1",
+ "@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.0",
- "@types/long": "^4.0.1",
+ "@protobufjs/utf8": "^1.1.1",
"@types/node": ">=13.7.0",
- "long": "^4.0.0"
+ "long": "^5.3.2"
},
- "bin": {
- "pbjs": "bin/pbjs",
- "pbts": "bin/pbts"
+ "engines": {
+ "node": ">=12.0.0"
}
},
"node_modules/pump": {
diff --git a/packages/meshbay-client/package.json b/packages/meshbay-client/package.json
index 3aa647a..e2ea6ab 100644
--- a/packages/meshbay-client/package.json
+++ b/packages/meshbay-client/package.json
@@ -46,5 +46,8 @@
},
"overrides": {
"protobufjs": "^7.5.5"
+ },
+ "allowScripts": {
+ "electron@42.9.2": true
}
}
diff --git a/packaging/README.md b/packaging/README.md
index 13b8fbf..a7f2d84 100644
--- a/packaging/README.md
+++ b/packaging/README.md
@@ -1,72 +1,67 @@
# MeshBay — Packaging
-Three distributable packages:
+Four packages, installed under `/opt/<PACKAGE_NAME>/`:
| Package | RPM spec | DEB control | Description |
|---|---|---|---|
-| `python3-meshbay-common` | `rpm/python3-meshbay-common.spec` | `deb/python3-meshbay-common/` | Shared crypto + protocol lib |
+| `meshbay-common` | `rpm/meshbay-common.spec` | `deb/meshbay-common/` | Shared Python venv + all pip deps |
| `meshbay-hub` | `rpm/meshbay-hub.spec` | `deb/meshbay-hub/` | Hub server (FastAPI + PostgreSQL) |
| `meshbay-node` | `rpm/meshbay-node.spec` | `deb/meshbay-node/` | Node daemon + local UI |
+| `meshbay-client` | `rpm/meshbay-client.spec` | `deb/meshbay-client/` | Electron desktop app |
-## Building RPMs (Fedora/RHEL)
+## Building packages
```bash
-# Install build tools
-sudo dnf install -y rpm-build python3-pip
+# On Ubuntu 26.04:
+bash packaging/build/build-packages.sh deb
-# Build all three packages
-for pkg in python3-meshbay-common meshbay-hub meshbay-node; do
- rpmbuild -ba packaging/rpm/${pkg}.spec
-done
+# On Fedora 44 (local):
+bash packaging/build/build-packages.sh rpm
+
+# On Raspberry Pi (arm64, deferred):
+bash packaging/build/build-packages.sh deb --arm64
```
-## Building DEBs (Debian/Ubuntu)
+Output: `meshbay-{common,hub,node,client}_0.8.0_{arch}.{deb,rpm}` in `/tmp/meshbay-build/out/`.
-```bash
-# Install build tools
-sudo apt install -y dpkg-dev debhelper python3-pip
+## Install order
-# Build common library first (dependency)
-dpkg-deb --build packaging/deb/python3-meshbay-common
-dpkg-deb --build packaging/deb/meshbay-hub
-dpkg-deb --build packaging/deb/meshbay-node
+Always install `meshbay-common` first (owns the shared Python venv).
+
+```bash
+# Ubuntu/Debian
+sudo dpkg -i meshbay-common_*.deb
+sudo dpkg -i meshbay-hub_*.deb meshbay-node_*.deb
+sudo dpkg -i meshbay-client_*.deb
-# Install
-sudo dpkg -i python3-meshbay-common_0.2.0_all.deb
-sudo dpkg -i meshbay-hub_0.2.0_all.deb
-sudo dpkg -i meshbay-node_0.2.0_all.deb
+# Fedora
+sudo rpm -ivh meshbay-common-*.rpm
+sudo rpm -ivh meshbay-hub-*.rpm meshbay-node-*.rpm
+sudo rpm -ivh meshbay-client-*.rpm
```
-## Install order
+## Architecture
+
+meshbay-common owns the shared Python virtual environment at
+`/opt/meshbay-common/venv/`. Hub and node install their code into that venv.
+The client is a standalone Electron app at `/opt/meshbay-client/`.
-Always install `python3-meshbay-common` first (dependency of both hub and node).
+No pip runs at install time — everything is pre-built.
## Post-install (hub)
```bash
-# Generate hub keypair
-sudo -u meshbay meshbay-hub --generate-keys
-
-# Edit config
+meshbay-hub --generate-keys
+sudo cp /opt/meshbay-hub/share/hub.toml.example /etc/meshbay/hub.toml
sudo nano /etc/meshbay/hub.toml
-
-# Configure Caddy for HTTPS
-sudo nano /etc/caddy/Caddyfile
-
-# Start
sudo systemctl enable --now meshbay-hub
```
## Post-install (node)
```bash
-# Initialize node (writes example config)
-meshbay-node init
-
-# Edit config
+meshbay-node init # copies default.env (with TMDB token)
nano ~/.config/meshbay/node.toml
-
-# Start as user service
systemctl --user enable --now meshbay-node
```
@@ -74,29 +69,42 @@ systemctl --user enable --now meshbay-node
The Electron client runs an HTTP relay on **TCP 19550-19553** to stream
decrypted video to Chromecast / Smart TV devices on the local network.
-Chromecast discovery uses **mDNS (UDP 5353)**. These ports must be open on the
-machine running the client.
+Chromecast discovery uses **mDNS (UDP 5353)**.
### Fedora / RHEL (firewalld)
+Pre-installed service files at `/usr/lib/firewalld/services/`:
+
```bash
-sudo firewall-cmd --permanent --add-service=mdns
-sudo firewall-cmd --permanent --add-port=19550-19553/tcp
+sudo firewall-cmd --permanent --add-service=meshbay-cast
sudo firewall-cmd --reload
```
### Ubuntu / Debian (ufw)
+Pre-installed application profile at `/etc/ufw/applications.d/meshbay`:
+
```bash
-sudo ufw allow 5353/udp comment "mDNS - Chromecast discovery"
-sudo ufw allow 19550:19553/tcp comment "meshbay cast relay"
+sudo ufw allow "MeshBay Cast"
```
-## Systemd service files
+## Systemd units
| File | Location |
|---|---|
| `systemd/meshbay-hub.service` | `/usr/lib/systemd/system/meshbay-hub.service` |
-| `systemd/meshbay-node.service` | `/usr/lib/systemd/user/meshbay-node.service` |
+| `systemd/meshbay-node@.service` | `/usr/lib/systemd/system/meshbay-node@.service` |
+| `systemd/meshbay-node.service` (user) | `/usr/lib/systemd/user/meshbay-node.service` |
+
+The node user service is enabled by the person themselves:
+
+```bash
+systemctl --user enable --now meshbay-node
+loginctl enable-linger $USER # keep serving when logged out
+```
+
+## Desktop launcher
-The node service is a **user service** (runs as the user's own account, with access to their home directory). The hub is a **system service** (runs as the `meshbay` system account).
+The client installs a Gnome `.desktop` file at
+`/usr/share/applications/meshbay.desktop` — the application appears as
+**"MeshBay"** in the applications menu.
diff --git a/packaging/build/build-client.sh b/packaging/build/build-client.sh
new file mode 100755
index 0000000..fbdf51e
--- /dev/null
+++ b/packaging/build/build-client.sh
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+# Build the meshbay-client package: Electron app + .desktop launcher.
+#
+# Usage: ./build-client.sh [staging-dir] [--arm64]
+#
+# Requires Node >= 22. If not in PATH, set NODEJS_DIR=/opt/nodejs or similar.
+set -euo pipefail
+
+REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+STAGING="${1:-/tmp/meshbay-build}"
+ROOT="$STAGING/meshbay-client-root"
+CLIENT="$REPO/packages/meshbay-client"
+
+ARCH_FLAG=""
+ELECTRON_ARCH="x64"
+DEB_ARCH="amd64"
+for arg in "$@"; do
+ if [ "$arg" = "--arm64" ]; then
+ ARCH_FLAG="--arm64"
+ ELECTRON_ARCH="arm64"
+ DEB_ARCH="arm64"
+ fi
+done
+
+# Node.js version check
+if [ -n "${NODEJS_DIR:-}" ]; then
+ export PATH="$NODEJS_DIR/bin:$PATH"
+fi
+NODE_VER=$(node --version 2>/dev/null || echo "none")
+echo "==> meshbay-client (Node $NODE_VER, arch=$ELECTRON_ARCH)"
+
+if ! node --version >/dev/null 2>&1; then
+ echo "!! Node.js not found. Set NODEJS_DIR or install Node >= 22." >&2
+ exit 1
+fi
+
+NODE_MAJOR=$(node -e "process.stdout.write(String(process.versions.node.split('.')[0]))")
+if [ "$NODE_MAJOR" -lt 22 ]; then
+ echo "!! Node $NODE_VER is too old — need >= 22 for Electron" >&2
+ exit 1
+fi
+
+rm -rf "$ROOT"
+mkdir -p "$ROOT"
+
+# --- Build the Electron app -----------------------------------------------
+cd "$CLIENT"
+
+echo " npm ci"
+npm ci --ignore-scripts 2>&1 | tail -3
+
+echo " approving + installing electron"
+npm approve-scripts electron 2>/dev/null || true
+node node_modules/electron/install.js 2>&1 | tail -1
+
+echo " syncing UI from hub package"
+npm run sync-ui 2>&1 | tail -1
+
+echo " electron-builder --dir $ARCH_FLAG"
+npx electron-builder --dir --linux $ARCH_FLAG 2>&1 | tail -5
+
+# Determine the unpacked output directory
+if [ "$ELECTRON_ARCH" = "arm64" ]; then
+ UNPACKED="$CLIENT/dist/linux-arm64-unpacked"
+else
+ UNPACKED="$CLIENT/dist/linux-unpacked"
+fi
+
+[ -d "$UNPACKED" ] || { echo "!! electron-builder output not found at $UNPACKED" >&2; exit 1; }
+
+# --- Assemble the package tree --------------------------------------------
+echo " assembling /opt/meshbay-client/"
+mkdir -p "$ROOT/opt/meshbay-client"
+cp -a "$UNPACKED"/* "$ROOT/opt/meshbay-client/"
+
+# Rename the electron binary
+if [ -f "$ROOT/opt/meshbay-client/meshbay-client" ]; then
+ mv "$ROOT/opt/meshbay-client/meshbay-client" "$ROOT/opt/meshbay-client/meshbay"
+elif [ -f "$ROOT/opt/meshbay-client/electron" ]; then
+ mv "$ROOT/opt/meshbay-client/electron" "$ROOT/opt/meshbay-client/meshbay"
+fi
+
+# Symlink in PATH
+mkdir -p "$ROOT/usr/bin"
+ln -sf /opt/meshbay-client/meshbay "$ROOT/usr/bin/meshbay"
+
+# --- Desktop launcher -----------------------------------------------------
+mkdir -p "$ROOT/usr/share/applications"
+cp "$REPO/packaging/desktop/meshbay.desktop" "$ROOT/usr/share/applications/"
+
+# --- Icons ----------------------------------------------------------------
+ICON_SRC="$CLIENT/build/icon.png"
+if [ -f "$ICON_SRC" ]; then
+ for size in 128 256 512; do
+ ICON_DIR="$ROOT/usr/share/icons/hicolor/${size}x${size}/apps"
+ mkdir -p "$ICON_DIR"
+ if command -v convert >/dev/null 2>&1; then
+ convert "$ICON_SRC" -resize "${size}x${size}" "$ICON_DIR/meshbay.png"
+ elif command -v magick >/dev/null 2>&1; then
+ magick "$ICON_SRC" -resize "${size}x${size}" "$ICON_DIR/meshbay.png"
+ else
+ # Fallback: copy the original at all sizes (better than no icon)
+ cp "$ICON_SRC" "$ICON_DIR/meshbay.png"
+ fi
+ done
+ echo " icons installed"
+else
+ echo " !! icon.png not found at $ICON_SRC — no icons" >&2
+fi
+
+# --- Firewall profile (Chromecast) ----------------------------------------
+mkdir -p "$ROOT/usr/lib/firewalld/services"
+cp "$REPO/packaging/firewall/firewalld/meshbay-cast.xml" \
+ "$ROOT/usr/lib/firewalld/services/"
+
+echo "==> meshbay-client staging ready at $ROOT ($DEB_ARCH)"
diff --git a/packaging/build/build-common.sh b/packaging/build/build-common.sh
new file mode 100755
index 0000000..3045689
--- /dev/null
+++ b/packaging/build/build-common.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+# Build the meshbay-common package: shared venv with ALL Python dependencies.
+#
+# Usage: ./build-common.sh [staging-dir]
+# staging-dir defaults to /tmp/meshbay-build
+#
+# Output: $STAGING/meshbay-common-root/ (ready for dpkg-deb or rpmbuild)
+set -euo pipefail
+
+REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+STAGING="${1:-/tmp/meshbay-build}"
+ROOT="$STAGING/meshbay-common-root"
+VENV_FINAL="/opt/meshbay-common/venv"
+VENV_BUILD="$ROOT$VENV_FINAL"
+WHEEL_DIR="$STAGING/wheels"
+
+VERSION=$(python3 -c "
+import tomllib, pathlib
+p = pathlib.Path('$REPO/packages/meshbay-common/pyproject.toml')
+print(tomllib.loads(p.read_text())['project']['version'])
+")
+echo "==> meshbay-common $VERSION"
+
+rm -rf "$ROOT"
+mkdir -p "$ROOT/opt/meshbay-common" "$WHEEL_DIR"
+
+# --- Create venv -----------------------------------------------------------
+echo " creating venv at $VENV_BUILD"
+python3 -m venv "$VENV_BUILD"
+"$VENV_BUILD/bin/pip" install --upgrade pip wheel 2>&1 | tail -1
+
+# --- Build wheels for all three packages -----------------------------------
+echo " building wheels"
+for pkg in meshbay-common meshbay-hub meshbay-node; do
+ "$VENV_BUILD/bin/pip" wheel \
+ --no-deps \
+ --wheel-dir "$WHEEL_DIR" \
+ "$REPO/packages/$pkg" 2>&1 | tail -1
+done
+
+# --- Install everything into the venv -------------------------------------
+echo " installing all packages + dependencies"
+"$VENV_BUILD/bin/pip" install \
+ --find-links "$WHEEL_DIR" \
+ meshbay-common meshbay-hub meshbay-node 2>&1 | tail -3
+
+# --- Strip build tools from the venv (not needed at runtime) ---------------
+echo " stripping build tools"
+"$VENV_BUILD/bin/pip" uninstall -y pip setuptools wheel 2>&1 | tail -1
+rm -rf "$VENV_BUILD/lib"/python*/site-packages/pip*
+rm -rf "$VENV_BUILD/lib"/python*/site-packages/setuptools*
+rm -rf "$VENV_BUILD/lib"/python*/site-packages/wheel*
+rm -f "$VENV_BUILD/bin/pip"*
+
+# --- Fix shebangs and pyvenv.cfg ------------------------------------------
+# The venv was built under $ROOT but will be installed at /opt/meshbay-common/venv/
+echo " fixing paths ($ROOT -> '')"
+for f in "$VENV_BUILD/bin"/*; do
+ [ -f "$f" ] || continue
+ [ -L "$f" ] && continue
+ head -1 "$f" | grep -q "^#!" || continue
+ sed -i "1s|$ROOT||" "$f"
+done
+sed -i "s|$ROOT||g" "$VENV_BUILD/pyvenv.cfg"
+
+# --- Remove __pycache__ (will be regenerated on first import) --------------
+find "$VENV_BUILD" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
+
+# --- Keep a copy of the wheels for reference -------------------------------
+mkdir -p "$ROOT/opt/meshbay-common/wheels"
+cp "$WHEEL_DIR"/meshbay_common-*.whl "$ROOT/opt/meshbay-common/wheels/"
+
+echo "==> meshbay-common staging ready at $ROOT"
+echo " venv: $VENV_BUILD"
+echo " version: $VERSION"
diff --git a/packaging/build/build-hub.sh b/packaging/build/build-hub.sh
new file mode 100755
index 0000000..45d2ab9
--- /dev/null
+++ b/packaging/build/build-hub.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+# Build the meshbay-hub package: hub-specific files on top of meshbay-common.
+#
+# Expects build-common.sh to have run first (shared venv exists in staging).
+#
+# Usage: ./build-hub.sh [staging-dir]
+set -euo pipefail
+
+REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+STAGING="${1:-/tmp/meshbay-build}"
+COMMON_ROOT="$STAGING/meshbay-common-root"
+ROOT="$STAGING/meshbay-hub-root"
+
+VENV="$COMMON_ROOT/opt/meshbay-common/venv"
+PYVER=$(basename "$VENV"/lib/python3.*)
+SITE="$VENV/lib/$PYVER/site-packages"
+
+VERSION=$(python3 -c "
+import tomllib, pathlib
+p = pathlib.Path('$REPO/packages/meshbay-hub/pyproject.toml')
+print(tomllib.loads(p.read_text())['project']['version'])
+")
+echo "==> meshbay-hub $VERSION"
+
+[ -d "$VENV" ] || { echo "!! shared venv not found — run build-common.sh first" >&2; exit 1; }
+
+rm -rf "$ROOT"
+mkdir -p "$ROOT"
+
+# --- Hub code in the shared venv (owned by meshbay-hub package) -----------
+# MOVE (not copy) from the common staging tree so dpkg/rpm knows these files
+# belong to meshbay-hub, not meshbay-common. build-common.sh installed them
+# so that shebangs and dependency resolution are correct.
+HUB_ROOT="$ROOT/opt/meshbay-common/venv/lib/$PYVER/site-packages"
+mkdir -p "$HUB_ROOT"
+mv "$SITE/meshbay_hub" "$HUB_ROOT/"
+mv "$SITE"/meshbay_hub-*.dist-info "$HUB_ROOT/"
+
+# Entry point
+mkdir -p "$ROOT/opt/meshbay-common/venv/bin"
+mv "$VENV/bin/meshbay-hub" "$ROOT/opt/meshbay-common/venv/bin/"
+
+# Symlink in PATH
+mkdir -p "$ROOT/usr/bin"
+ln -sf /opt/meshbay-common/venv/bin/meshbay-hub "$ROOT/usr/bin/meshbay-hub"
+
+# --- Hub-specific assets --------------------------------------------------
+mkdir -p "$ROOT/opt/meshbay-hub"
+
+# Alembic config (migrations are inside the installed package at meshbay_hub/db/migrations/)
+if [ -f "$REPO/packages/meshbay-hub/alembic.ini" ]; then
+ mkdir -p "$ROOT/opt/meshbay-hub/migrations"
+ cp "$REPO/packages/meshbay-hub/alembic.ini" "$ROOT/opt/meshbay-hub/migrations/"
+fi
+
+# Config example
+mkdir -p "$ROOT/opt/meshbay-hub/share"
+if [ -f "$REPO/packaging/conf/hub.toml.example" ]; then
+ cp "$REPO/packaging/conf/hub.toml.example" "$ROOT/opt/meshbay-hub/share/"
+fi
+
+# Also install to /etc/meshbay/ for discoverability
+mkdir -p "$ROOT/etc/meshbay"
+if [ -f "$ROOT/opt/meshbay-hub/share/hub.toml.example" ]; then
+ cp "$ROOT/opt/meshbay-hub/share/hub.toml.example" "$ROOT/etc/meshbay/"
+fi
+
+# --- Systemd unit ---------------------------------------------------------
+mkdir -p "$ROOT/usr/lib/systemd/system"
+cp "$REPO/packaging/systemd/meshbay-hub.service" \
+ "$ROOT/usr/lib/systemd/system/meshbay-hub.service"
+
+echo "==> meshbay-hub staging ready at $ROOT"
diff --git a/packaging/build/build-node.sh b/packaging/build/build-node.sh
new file mode 100755
index 0000000..d8a5af4
--- /dev/null
+++ b/packaging/build/build-node.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# Build the meshbay-node package: node-specific files on top of meshbay-common.
+#
+# Expects build-common.sh to have run first (shared venv exists in staging).
+#
+# Usage: ./build-node.sh [staging-dir]
+set -euo pipefail
+
+REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+STAGING="${1:-/tmp/meshbay-build}"
+COMMON_ROOT="$STAGING/meshbay-common-root"
+ROOT="$STAGING/meshbay-node-root"
+
+VENV="$COMMON_ROOT/opt/meshbay-common/venv"
+PYVER=$(basename "$VENV"/lib/python3.*)
+SITE="$VENV/lib/$PYVER/site-packages"
+
+VERSION=$(python3 -c "
+import tomllib, pathlib
+p = pathlib.Path('$REPO/packages/meshbay-node/pyproject.toml')
+print(tomllib.loads(p.read_text())['project']['version'])
+")
+echo "==> meshbay-node $VERSION"
+
+[ -d "$VENV" ] || { echo "!! shared venv not found — run build-common.sh first" >&2; exit 1; }
+
+rm -rf "$ROOT"
+mkdir -p "$ROOT"
+
+# --- Node code in the shared venv (owned by meshbay-node package) ---------
+# MOVE (not copy) from the common staging tree — same rationale as build-hub.sh.
+NODE_ROOT="$ROOT/opt/meshbay-common/venv/lib/$PYVER/site-packages"
+mkdir -p "$NODE_ROOT"
+mv "$SITE/meshbay_node" "$NODE_ROOT/"
+mv "$SITE"/meshbay_node-*.dist-info "$NODE_ROOT/"
+
+# Entry point
+mkdir -p "$ROOT/opt/meshbay-common/venv/bin"
+mv "$VENV/bin/meshbay-node" "$ROOT/opt/meshbay-common/venv/bin/"
+
+# Symlink in PATH
+mkdir -p "$ROOT/usr/bin"
+ln -sf /opt/meshbay-common/venv/bin/meshbay-node "$ROOT/usr/bin/meshbay-node"
+
+# --- Node-specific assets -------------------------------------------------
+mkdir -p "$ROOT/opt/meshbay-node/share"
+
+# Default env with TMDB token (read at build time).
+# Override with MESHBAY_TMDB_TOKEN_FILE; falls back to QE/node.env (gitignored).
+TMDB_TOKEN_FILE="${MESHBAY_TMDB_TOKEN_FILE:-$REPO/QE/node.env}"
+TMDB_TOKEN=""
+if [ -f "$TMDB_TOKEN_FILE" ]; then
+ TMDB_TOKEN=$(grep -oP 'MESHBAY_TMDB_DEFAULT_TOKEN=\K.*' "$TMDB_TOKEN_FILE" || true)
+fi
+if [ -n "$TMDB_TOKEN" ]; then
+ cat > "$ROOT/opt/meshbay-node/share/default.env" <<EOF
+# Default environment for meshbay-node.
+# Copied to ~/.config/meshbay/node.env by 'meshbay-node init' if it does not exist.
+# The operator may override any value there or in the systemd EnvironmentFile.
+
+# TMDB API token for the Videos app (read-only, shared across installations)
+MESHBAY_TMDB_DEFAULT_TOKEN=$TMDB_TOKEN
+EOF
+ echo " TMDB token baked into default.env"
+else
+ echo " !! TMDB token not found in QE/node.env — default.env will be empty" >&2
+ touch "$ROOT/opt/meshbay-node/share/default.env"
+fi
+
+# --- Systemd units --------------------------------------------------------
+mkdir -p "$ROOT/usr/lib/systemd/system"
+mkdir -p "$ROOT/usr/lib/systemd/user"
+cp "$REPO/packaging/systemd/meshbay-node.service" \
+ "$ROOT/usr/lib/systemd/system/meshbay-node@.service"
+cp "$REPO/packaging/systemd/meshbay-node-user.service" \
+ "$ROOT/usr/lib/systemd/user/meshbay-node.service"
+
+# --- Firewall profiles ----------------------------------------------------
+# firewalld (Fedora)
+mkdir -p "$ROOT/usr/lib/firewalld/services"
+cp "$REPO/packaging/firewall/firewalld/meshbay-node.xml" \
+ "$ROOT/usr/lib/firewalld/services/"
+
+# UFW (Ubuntu/Debian)
+mkdir -p "$ROOT/etc/ufw/applications.d"
+cp "$REPO/packaging/firewall/ufw/meshbay" \
+ "$ROOT/etc/ufw/applications.d/"
+
+echo "==> meshbay-node staging ready at $ROOT"
diff --git a/packaging/build/build-packages.sh b/packaging/build/build-packages.sh
new file mode 100755
index 0000000..ccacb81
--- /dev/null
+++ b/packaging/build/build-packages.sh
@@ -0,0 +1,156 @@
+#!/usr/bin/env bash
+# Build all MeshBay packages for the current platform.
+#
+# Usage:
+# ./build-packages.sh # auto-detect format (deb on Ubuntu, rpm on Fedora)
+# ./build-packages.sh deb # force .deb
+# ./build-packages.sh rpm # force .rpm
+# ./build-packages.sh deb --arm64 # arm64 .deb (Pi)
+#
+# Expects to run on the target machine:
+# - Ubuntu 26.04 (cbesson@meshbay.org) for .deb amd64
+# - Fedora 44 (local) for .rpm x86_64
+# - Raspberry Pi for .deb arm64 (deferred)
+#
+# Output: packages in $STAGING/out/
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO="$(cd "$SCRIPT_DIR/../.." && pwd)"
+STAGING="${MESHBAY_BUILD_DIR:-/tmp/meshbay-build}"
+OUT="$STAGING/out"
+
+# --- Detect format --------------------------------------------------------
+FORMAT="${1:-auto}"
+shift 2>/dev/null || true
+ARM64_FLAG=""
+for arg in "$@"; do
+ [ "$arg" = "--arm64" ] && ARM64_FLAG="--arm64"
+done
+
+if [ "$FORMAT" = "auto" ]; then
+ if [ -f /etc/debian_version ]; then
+ FORMAT="deb"
+ elif [ -f /etc/fedora-release ] || [ -f /etc/redhat-release ]; then
+ FORMAT="rpm"
+ else
+ echo "!! Cannot detect OS — pass 'deb' or 'rpm' as argument" >&2
+ exit 1
+ fi
+fi
+
+ARCH=$(dpkg --print-architecture 2>/dev/null || rpm --eval '%{_arch}' 2>/dev/null || uname -m)
+if [ -n "$ARM64_FLAG" ]; then
+ ARCH="arm64"
+fi
+
+VERSION=$(python3 -c "
+import tomllib, pathlib
+p = pathlib.Path('$REPO/packages/meshbay-common/pyproject.toml')
+print(tomllib.loads(p.read_text())['project']['version'])
+")
+
+echo "========================================"
+echo " MeshBay $VERSION — $FORMAT $ARCH"
+echo "========================================"
+echo " Repo: $REPO"
+echo " Staging: $STAGING"
+echo " Output: $OUT"
+echo ""
+
+rm -rf "$OUT"
+mkdir -p "$OUT"
+
+# --- Step 1: Build staging trees ------------------------------------------
+
+echo "--- [1/4] meshbay-common (shared venv + all deps) ---"
+bash "$SCRIPT_DIR/build-common.sh" "$STAGING"
+
+echo ""
+echo "--- [2/4] meshbay-hub ---"
+bash "$SCRIPT_DIR/build-hub.sh" "$STAGING"
+
+echo ""
+echo "--- [3/4] meshbay-node ---"
+bash "$SCRIPT_DIR/build-node.sh" "$STAGING"
+
+echo ""
+echo "--- [4/4] meshbay-client ---"
+bash "$SCRIPT_DIR/build-client.sh" "$STAGING" $ARM64_FLAG
+
+# --- Step 2: Package ------------------------------------------------------
+
+echo ""
+echo "--- Packaging ($FORMAT) ---"
+
+if [ "$FORMAT" = "deb" ]; then
+ build_deb() {
+ local pkg="$1"
+ local root="$STAGING/${pkg}-root"
+ local deb_src="$REPO/packaging/deb/$pkg/DEBIAN"
+ local deb_dir="$root/DEBIAN"
+
+ mkdir -p "$deb_dir"
+ for f in "$deb_src"/*; do
+ [ -f "$f" ] || continue
+ sed -e "s/__VERSION__/$VERSION/g" \
+ -e "s/__ARCH__/$ARCH/g" \
+ "$f" > "$deb_dir/$(basename "$f")"
+ chmod 755 "$deb_dir/$(basename "$f")" 2>/dev/null || true
+ done
+ # control files should be 644, scripts 755
+ [ -f "$deb_dir/control" ] && chmod 644 "$deb_dir/control"
+ [ -f "$deb_dir/conffiles" ] && chmod 644 "$deb_dir/conffiles"
+
+ dpkg-deb --build --root-owner-group "$root" "$OUT/${pkg}_${VERSION}_${ARCH}.deb"
+ echo " -> $OUT/${pkg}_${VERSION}_${ARCH}.deb"
+ }
+
+ build_deb meshbay-common
+ build_deb meshbay-hub
+ build_deb meshbay-node
+ build_deb meshbay-client
+
+elif [ "$FORMAT" = "rpm" ]; then
+ RPMBUILD_DIR="$STAGING/rpmbuild"
+ rm -rf "$RPMBUILD_DIR"
+ mkdir -p "$RPMBUILD_DIR"/{SPECS,SOURCES,BUILD,RPMS,SRPMS}
+
+ build_rpm() {
+ local pkg="$1"
+ local spec="$REPO/packaging/rpm/${pkg}.spec"
+ local root="$STAGING/${pkg}-root"
+
+ # Substitute version and pass the staging root as a macro
+ sed "s/__VERSION__/$VERSION/g" "$spec" > "$RPMBUILD_DIR/SPECS/${pkg}.spec"
+
+ rpmbuild \
+ --define "_topdir $RPMBUILD_DIR" \
+ --define "_staging_root $root" \
+ -bb "$RPMBUILD_DIR/SPECS/${pkg}.spec" 2>&1 | tail -5
+
+ local rpm_file
+ rpm_file=$(find "$RPMBUILD_DIR/RPMS" -name "${pkg}-${VERSION}*.rpm" -print -quit)
+ if [ -n "$rpm_file" ]; then
+ cp "$rpm_file" "$OUT/"
+ echo " -> $OUT/$(basename "$rpm_file")"
+ fi
+ }
+
+ build_rpm meshbay-common
+ build_rpm meshbay-hub
+ build_rpm meshbay-node
+ build_rpm meshbay-client
+fi
+
+# --- Summary --------------------------------------------------------------
+echo ""
+echo "========================================"
+echo " Build complete"
+echo "========================================"
+ls -lh "$OUT"/
+echo ""
+echo "Install order:"
+echo " 1. meshbay-common"
+echo " 2. meshbay-hub and/or meshbay-node"
+echo " 3. meshbay-client"
diff --git a/packaging/deb/meshbay-client/DEBIAN/control b/packaging/deb/meshbay-client/DEBIAN/control
new file mode 100644
index 0000000..e6a49b9
--- /dev/null
+++ b/packaging/deb/meshbay-client/DEBIAN/control
@@ -0,0 +1,17 @@
+Package: meshbay-client
+Version: __VERSION__
+Section: net
+Priority: optional
+Architecture: __ARCH__
+Maintainer: MeshBay Team <team@meshbay.org>
+Homepage: https://meshbay.org
+Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6,
+ libatspi2.0-0, libdrm2, libgbm1, libasound2
+Recommends: meshbay-node
+Description: MeshBay — peer-to-peer file sharing, streaming and group chat
+ Desktop client for MeshBay. Connects to MeshBay nodes over WebRTC for
+ file sharing, video streaming, and group chat. Includes Chromecast and
+ Smart TV casting support.
+ .
+ Installs the Electron application to /opt/meshbay-client/ and a Gnome
+ launcher named "MeshBay".
diff --git a/packaging/deb/meshbay-client/DEBIAN/postinst b/packaging/deb/meshbay-client/DEBIAN/postinst
new file mode 100644
index 0000000..f262665
--- /dev/null
+++ b/packaging/deb/meshbay-client/DEBIAN/postinst
@@ -0,0 +1,22 @@
+#!/bin/sh
+set -e
+
+case "$1" in
+ configure)
+ # chrome-sandbox requires SUID root for Chromium's namespace sandbox
+ if [ -f /opt/meshbay-client/chrome-sandbox ]; then
+ chown root:root /opt/meshbay-client/chrome-sandbox
+ chmod 4755 /opt/meshbay-client/chrome-sandbox
+ fi
+
+ # Update desktop database and icon cache
+ if command -v update-desktop-database >/dev/null 2>&1; then
+ update-desktop-database /usr/share/applications || true
+ fi
+ if command -v gtk-update-icon-cache >/dev/null 2>&1; then
+ gtk-update-icon-cache -f -t /usr/share/icons/hicolor || true
+ fi
+ ;;
+esac
+
+#DEBHELPER#
diff --git a/packaging/deb/meshbay-common/DEBIAN/control b/packaging/deb/meshbay-common/DEBIAN/control
new file mode 100644
index 0000000..4089f4b
--- /dev/null
+++ b/packaging/deb/meshbay-common/DEBIAN/control
@@ -0,0 +1,14 @@
+Package: meshbay-common
+Version: __VERSION__
+Section: python
+Priority: optional
+Architecture: __ARCH__
+Maintainer: MeshBay Team <team@meshbay.org>
+Homepage: https://meshbay.org
+Depends: python3 (>= 3.12)
+Description: MeshBay shared Python runtime and libraries
+ Provides the shared Python virtual environment, cryptographic primitives
+ (Ed25519, X25519, GEK wrap/unwrap, ChaCha20-Poly1305), protocol types (MNP,
+ MHP), and all pip dependencies used by meshbay-hub and meshbay-node.
+ .
+ Installs to /opt/meshbay-common/venv/.
diff --git a/packaging/deb/meshbay-common/DEBIAN/postinst b/packaging/deb/meshbay-common/DEBIAN/postinst
new file mode 100644
index 0000000..d675c0c
--- /dev/null
+++ b/packaging/deb/meshbay-common/DEBIAN/postinst
@@ -0,0 +1,15 @@
+#!/bin/sh
+set -e
+
+case "$1" in
+ configure)
+ # Recompile bytecode for the installed Python version.
+ # The venv was built on the build machine; .pyc files may reference
+ # a different path or Python micro-version.
+ if command -v py3compile >/dev/null 2>&1; then
+ py3compile /opt/meshbay-common/venv/lib/ 2>/dev/null || true
+ fi
+ ;;
+esac
+
+#DEBHELPER#
diff --git a/packaging/deb/meshbay-hub/DEBIAN/control b/packaging/deb/meshbay-hub/DEBIAN/control
index 2f3541c..07799a1 100644
--- a/packaging/deb/meshbay-hub/DEBIAN/control
+++ b/packaging/deb/meshbay-hub/DEBIAN/control
@@ -1,19 +1,16 @@
Package: meshbay-hub
-Version: 0.2.0
+Version: __VERSION__
Section: net
Priority: optional
Architecture: all
Maintainer: MeshBay Team <team@meshbay.org>
Homepage: https://meshbay.org
-Depends: python3 (>= 3.12),
- python3-meshbay-common (= 0.2.0),
- postgresql,
- python3-fastapi,
- python3-uvicorn,
- adduser
-Recommends: caddy
+Depends: meshbay-common (= __VERSION__), adduser
+Recommends: caddy, postgresql
Description: MeshBay Hub — identity authority and group registry server
MeshBay Hub provides user registration, JWT issuance, group management,
- GEK bundle distribution, revocation, and moderation for MeshBay networks.
+ WebRTC signaling, notifications, and moderation for MeshBay networks.
.
- Runs as a systemd service behind Caddy for HTTPS (auto Let's Encrypt).
+ Installs hub code into the shared venv at /opt/meshbay-common/venv/ and
+ Alembic migrations at /opt/meshbay-hub/migrations/.
+ Runs as a systemd service behind Caddy for HTTPS.
diff --git a/packaging/deb/meshbay-hub/DEBIAN/postinst b/packaging/deb/meshbay-hub/DEBIAN/postinst
index 48921f2..3cf66a9 100644
--- a/packaging/deb/meshbay-hub/DEBIAN/postinst
+++ b/packaging/deb/meshbay-hub/DEBIAN/postinst
@@ -13,15 +13,16 @@ case "$1" in
--gecos "MeshBay service account" meshbay
fi
- # Create data directory
+ # Create data and log directories
install -d -o meshbay -g meshbay -m 750 /var/lib/meshbay/hub
install -d -o meshbay -g meshbay -m 750 /var/log/meshbay
- # Enable and start service
+ # Create config directory (files are placed by the admin, not by us)
+ install -d -m 755 /etc/meshbay
+
+ # Reload systemd if available
if [ -d /run/systemd/system ]; then
systemctl daemon-reload || true
- systemctl enable meshbay-hub.service || true
- systemctl start meshbay-hub.service || true
fi
;;
esac
diff --git a/packaging/deb/meshbay-node/DEBIAN/control b/packaging/deb/meshbay-node/DEBIAN/control
index 8d74ee4..0c9d73d 100644
--- a/packaging/deb/meshbay-node/DEBIAN/control
+++ b/packaging/deb/meshbay-node/DEBIAN/control
@@ -1,17 +1,17 @@
Package: meshbay-node
-Version: 0.2.0
+Version: __VERSION__
Section: net
Priority: optional
Architecture: all
Maintainer: MeshBay Team <team@meshbay.org>
Homepage: https://meshbay.org
-Depends: python3 (>= 3.12),
- python3-meshbay-common (= 0.2.0),
- adduser
+Depends: meshbay-common (= __VERSION__)
Recommends: ffmpeg
-Description: MeshBay Node — local file host and streaming server
- MeshBay Node indexes local directories and serves encrypted files
- to authenticated group members over QUIC (MNP v2) or TCP+TLS (MNP v1).
+Description: MeshBay Node — local file host, streaming server, and group daemon
+ MeshBay Node indexes local directories and serves encrypted files to
+ authenticated group members over WebRTC. Includes video streaming (fMP4
+ remux via ffmpeg), group chat, and a local admin UI on localhost:18000.
.
- Includes a local management web UI at http://localhost:18000.
- Designed to run on a home server or NAS.
+ Installs node code into the shared venv at /opt/meshbay-common/venv/.
+ Ships a default TMDB API token for the Videos app.
+ Runs as a systemd user service.
diff --git a/packaging/deb/meshbay-node/DEBIAN/postinst b/packaging/deb/meshbay-node/DEBIAN/postinst
new file mode 100644
index 0000000..ba16633
--- /dev/null
+++ b/packaging/deb/meshbay-node/DEBIAN/postinst
@@ -0,0 +1,13 @@
+#!/bin/sh
+set -e
+
+case "$1" in
+ configure)
+ # Reload systemd if available
+ if [ -d /run/systemd/system ]; then
+ systemctl daemon-reload || true
+ fi
+ ;;
+esac
+
+#DEBHELPER#
diff --git a/packaging/desktop/meshbay.desktop b/packaging/desktop/meshbay.desktop
new file mode 100644
index 0000000..9c1f6a5
--- /dev/null
+++ b/packaging/desktop/meshbay.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Type=Application
+Name=MeshBay
+Comment=Peer-to-peer file sharing, streaming and group chat
+Exec=/opt/meshbay-client/meshbay %U
+Icon=meshbay
+Terminal=false
+Categories=Network;FileTransfer;Chat;
+MimeType=x-scheme-handler/meshbay;
+StartupWMClass=MeshBay
diff --git a/packaging/firewall/firewalld/meshbay-cast.xml b/packaging/firewall/firewalld/meshbay-cast.xml
new file mode 100644
index 0000000..065820b
--- /dev/null
+++ b/packaging/firewall/firewalld/meshbay-cast.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>MeshBay Cast</short>
+ <description>HTTP relay for casting decrypted video to Chromecast and Smart TV devices on the local network. Includes mDNS for device discovery.</description>
+ <port protocol="tcp" port="19550-19553"/>
+ <port protocol="udp" port="5353"/>
+</service>
diff --git a/packaging/firewall/firewalld/meshbay-node.xml b/packaging/firewall/firewalld/meshbay-node.xml
new file mode 100644
index 0000000..3443b54
--- /dev/null
+++ b/packaging/firewall/firewalld/meshbay-node.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<service>
+ <short>MeshBay Node</short>
+ <description>MeshBay Node local administration interface (localhost only by default).</description>
+ <port protocol="tcp" port="18000"/>
+</service>
diff --git a/packaging/firewall/ufw/meshbay b/packaging/firewall/ufw/meshbay
new file mode 100644
index 0000000..5a610a5
--- /dev/null
+++ b/packaging/firewall/ufw/meshbay
@@ -0,0 +1,9 @@
+[MeshBay Cast]
+title=MeshBay Chromecast relay
+description=HTTP relay for casting decrypted video to LAN devices
+ports=19550:19553/tcp|5353/udp
+
+[MeshBay Node]
+title=MeshBay Node admin UI
+description=Local administration interface (localhost only by default)
+ports=18000/tcp
diff --git a/packaging/rpm/meshbay-client.spec b/packaging/rpm/meshbay-client.spec
new file mode 100644
index 0000000..8788e67
--- /dev/null
+++ b/packaging/rpm/meshbay-client.spec
@@ -0,0 +1,53 @@
+Name: meshbay-client
+Version: __VERSION__
+Release: 1%{?dist}
+Summary: MeshBay — peer-to-peer file sharing, streaming and group chat
+License: AGPLv3+
+URL: https://meshbay.org
+
+AutoReqProv: no
+
+Requires: gtk3
+Requires: libnotify
+Requires: nss
+Requires: libXScrnSaver
+Requires: libXtst
+Requires: at-spi2-core
+Requires: libdrm
+Requires: mesa-libgbm
+Requires: alsa-lib
+Recommends: meshbay-node
+
+%description
+Desktop client for MeshBay. Connects to MeshBay nodes over WebRTC for
+file sharing, video streaming, and group chat. Includes Chromecast and
+Smart TV casting support.
+
+Installs the Electron application to /opt/meshbay-client/ and a Gnome
+launcher named "MeshBay".
+
+%install
+cp -a %{_staging_root}/* %{buildroot}/
+
+%post
+if [ -f /opt/meshbay-client/chrome-sandbox ]; then
+ chown root:root /opt/meshbay-client/chrome-sandbox
+ chmod 4755 /opt/meshbay-client/chrome-sandbox
+fi
+update-desktop-database /usr/share/applications 2>/dev/null || true
+gtk-update-icon-cache -f -t /usr/share/icons/hicolor 2>/dev/null || true
+
+%postun
+update-desktop-database /usr/share/applications 2>/dev/null || true
+gtk-update-icon-cache -f -t /usr/share/icons/hicolor 2>/dev/null || true
+
+%files
+/opt/meshbay-client
+/usr/bin/meshbay
+/usr/share/applications/meshbay.desktop
+/usr/share/icons/hicolor/*/apps/meshbay.png
+/usr/lib/firewalld/services/meshbay-cast.xml
+
+%changelog
+* Sun Aug 31 2026 MeshBay Team <team@meshbay.org> - %{version}-1
+- Initial packaging of the desktop client
diff --git a/packaging/rpm/meshbay-common.spec b/packaging/rpm/meshbay-common.spec
new file mode 100644
index 0000000..e1088d4
--- /dev/null
+++ b/packaging/rpm/meshbay-common.spec
@@ -0,0 +1,25 @@
+Name: meshbay-common
+Version: __VERSION__
+Release: 1%{?dist}
+Summary: MeshBay shared Python runtime and libraries
+License: AGPLv3+
+URL: https://meshbay.org
+
+AutoReqProv: no
+
+%description
+Shared Python virtual environment, cryptographic primitives
+(Ed25519, X25519, GEK wrap/unwrap, ChaCha20-Poly1305), protocol types
+(MNP, MHP), and all pip dependencies used by meshbay-hub and meshbay-node.
+
+Installs to /opt/meshbay-common/venv/.
+
+%install
+cp -a %{_staging_root}/* %{buildroot}/
+
+%files
+/opt/meshbay-common
+
+%changelog
+* Sun Aug 31 2026 MeshBay Team <team@meshbay.org> - %{version}-1
+- Packaging overhaul: shared venv under /opt/meshbay-common/venv/
diff --git a/packaging/rpm/meshbay-hub.spec b/packaging/rpm/meshbay-hub.spec
index dd4803f..73df0b9 100644
--- a/packaging/rpm/meshbay-hub.spec
+++ b/packaging/rpm/meshbay-hub.spec
@@ -1,61 +1,38 @@
Name: meshbay-hub
-Version: 0.6.0
+Version: __VERSION__
Release: 1%{?dist}
Summary: MeshBay Hub — identity authority and group registry server
License: AGPLv3+
URL: https://meshbay.org
-Source0: %{name}-%{version}.tar.gz
+AutoReqProv: no
BuildArch: noarch
-BuildRequires: python3-devel >= 3.12
-BuildRequires: python3-pip
-BuildRequires: python3-hatchling
-Requires: python3 >= 3.12
-Requires: python3-meshbay-common = %{version}
-Requires: python3-fastapi
-Requires: python3-uvicorn
-Requires: python3-sqlalchemy >= 2.0
-Requires: python3-alembic
-Requires: python3-asyncpg
-Requires: python3-pyjwt
-Requires: python3-blake3
-Requires: python3-slowapi
-Requires: postgresql-server
+Requires: meshbay-common = %{version}
+Recommends: caddy
+Recommends: postgresql-server
%description
-MeshBay Hub provides identity management, group registry,
-GEK bundle distribution, and coordination for MeshBay nodes.
-Runs as a systemd service behind Caddy (HTTPS).
+MeshBay Hub provides user registration, JWT issuance, group management,
+WebRTC signaling, notifications, and moderation for MeshBay networks.
-%prep
-%autosetup
-
-%build
-%{python3} -m pip wheel --no-deps --wheel-dir dist .
+Installs hub code into the shared venv at /opt/meshbay-common/venv/.
+Runs as a systemd service behind Caddy for HTTPS.
%install
-%{python3} -m pip install --root %{buildroot} --no-index --find-links dist meshbay-hub
-
-# systemd service
-install -Dm644 packaging/systemd/meshbay-hub.service \
- %{buildroot}%{_unitdir}/meshbay-hub.service
-
-# Config file template
-install -Dm644 packaging/conf/hub.toml.example \
- %{buildroot}%{_sysconfdir}/meshbay/hub.toml.example
-
-# Data directory
-install -d %{buildroot}%{_sharedstatedir}/meshbay/hub
+cp -a %{_staging_root}/* %{buildroot}/
%pre
getent group meshbay >/dev/null || groupadd -r meshbay
getent passwd meshbay >/dev/null || \
- useradd -r -g meshbay -d %{_sharedstatedir}/meshbay -s /sbin/nologin \
+ useradd -r -g meshbay -d /var/lib/meshbay -s /sbin/nologin \
-c "MeshBay service account" meshbay
%post
%systemd_post meshbay-hub.service
+install -d -o meshbay -g meshbay -m 750 /var/lib/meshbay/hub
+install -d -o meshbay -g meshbay -m 750 /var/log/meshbay
+install -d -m 755 /etc/meshbay
%preun
%systemd_preun meshbay-hub.service
@@ -64,55 +41,13 @@ getent passwd meshbay >/dev/null || \
%systemd_postun_with_restart meshbay-hub.service
%files
-%license LICENSE
-%doc README.md
-%{python3_sitelib}/meshbay_hub/
-%{python3_sitelib}/meshbay_hub-*.dist-info/
-%{_bindir}/meshbay-hub
+/opt/meshbay-hub
+/opt/meshbay-common/venv/lib/python*/site-packages/meshbay_hub/
+/opt/meshbay-common/venv/lib/python*/site-packages/meshbay_hub-*.dist-info/
+/opt/meshbay-common/venv/bin/meshbay-hub
+/usr/bin/meshbay-hub
%{_unitdir}/meshbay-hub.service
-%config(noreplace) %{_sysconfdir}/meshbay/hub.toml.example
-%dir %attr(750, meshbay, meshbay) %{_sharedstatedir}/meshbay/hub
%changelog
-* Sun Aug 23 2026 MeshBay Team <team@meshbay.org> - 0.6.0-1
-- Group UI split into a pluggable "applications" architecture (Chat, Files
- today; Videos/Music/Photos can register without touching GroupPage)
-- Operators can enable/disable applications per group from Settings
-- Group Settings sections reordered: Invite, Pairing, Applications, Shared
- directories, Uploads, danger zone, Your devices, Members
-- Fixed: a fresh access token was never picked up when a "not a member"
- handshake retry fired, wrongly locking a member (including a group's own
- creator) out of a group they had just joined
-- Fixed: opening Chat on a group with existing messages threw and wedged the
- page's rendering, leaving every button unresponsive until reload
-
-* Sun Aug 16 2026 MeshBay Team <team@meshbay.org> - 0.5.0-1
-- Leave a group; public groups capped at 10 per owner, staff exempt
-- Groups are listed only once a node has announced them; prune-groups collects the rest
-- Node presence dot in the sidebar, from the signaling registry — no polling
-- Chat opens on the newest 100 messages and pages backwards (it used to page forwards)
-- Profile split out of Settings; public groups are open, invite-only is private
-- Streamed downloads verify the service worker is really serving before writing
-- Static assets are revalidated, so a half-updated SPA cannot load
-
-* Sun Aug 16 2026 MeshBay Team <team@meshbay.org> - 0.4.0-1
-- Web client translated into fr, es, pt-BR, zh-CN, ja, de, it, nl and pl
-- Catalogues load on demand, one file per language, English as fallback
-- Plural forms selected through Intl.PluralRules (Polish needs four)
-- Locale matching keeps the region, so pt-BR and zh-CN resolve
-- Static assets are revalidated, so a half-updated SPA cannot load
-
-* Sat Aug 15 2026 MeshBay Team <team@meshbay.org> - 0.3.0-1
-- Transfers survive leaving a group; downloads stream to disk in every browser
-- Download a folder as a zip, built in the browser (zip64, store-only)
-- Group owner can remove a member; editable group description
-- Nodes are recorded at the address their signed announcement arrived from
-- Deleted accounts stop being counted; the connection log keeps their name
-
-* Fri Aug 14 2026 MeshBay Team <team@meshbay.org> - 0.2.0-1
-- Hub no longer stores or publishes user identity keys (H3); schema migration
-- Access tokens carry no pk_user claim
-- SPA: Argon2id for the keypair bundle, per-node identity, invitation codes
-
-* Sat Aug 09 2026 MeshBay Team <team@meshbay.org> - 0.1.0-1
-- Initial package
+* Sun Aug 31 2026 MeshBay Team <team@meshbay.org> - %{version}-1
+- Packaging overhaul: installs into shared venv under /opt
diff --git a/packaging/rpm/meshbay-node.spec b/packaging/rpm/meshbay-node.spec
index e4f386e..20d69bc 100644
--- a/packaging/rpm/meshbay-node.spec
+++ b/packaging/rpm/meshbay-node.spec
@@ -1,92 +1,49 @@
Name: meshbay-node
-Version: 0.6.0
+Version: __VERSION__
Release: 1%{?dist}
Summary: MeshBay Node — local file host, streaming server, and group daemon
License: AGPLv3+
URL: https://meshbay.org
-Source0: %{name}-%{version}.tar.gz
+AutoReqProv: no
BuildArch: noarch
-BuildRequires: python3-devel >= 3.12
-BuildRequires: python3-pip
-BuildRequires: python3-hatchling
-Requires: python3 >= 3.12
-Requires: python3-meshbay-common = %{version}
-Requires: python3-fastapi
-Requires: python3-uvicorn
-Requires: python3-httpx
-Requires: python3-watchdog
-Requires: python3-aioquic >= 1.0
-Requires: python3-aioice
-Requires: python3-pyjwt
-Requires: python3-blake3
-Requires: python3-msgpack
-Requires: python3-zstandard
-# Optional: ffmpeg for HLS streaming
+Requires: meshbay-common = %{version}
Recommends: ffmpeg
%description
-MeshBay Node indexes local directories and serves encrypted files
-to authenticated group members over QUIC (MNP v2) or TCP+TLS (MNP v1).
-Includes a local web UI at http://localhost:18000.
+MeshBay Node indexes local directories and serves encrypted files to
+authenticated group members over WebRTC. Includes video streaming (fMP4
+remux via ffmpeg), group chat, and a local admin UI on localhost:18000.
-%prep
-%autosetup
-
-%build
-%{python3} -m pip wheel --no-deps --wheel-dir dist .
+Installs node code into the shared venv at /opt/meshbay-common/venv/.
+Ships a default TMDB API token for the Videos app.
+Runs as a systemd user service.
%install
-%{python3} -m pip install --root %{buildroot} --no-index --find-links dist meshbay-node
+cp -a %{_staging_root}/* %{buildroot}/
+
+%post
+if [ -d /run/systemd/system ]; then
+ systemctl daemon-reload || true
+fi
-# 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
+%preun
+if [ -d /run/systemd/system ]; then
+ systemctl daemon-reload || true
+fi
%files
-%license LICENSE
-%doc README.md
-%{python3_sitelib}/meshbay_node/
-%{python3_sitelib}/meshbay_node-*.dist-info/
-%{_bindir}/meshbay-node
+/opt/meshbay-node
+/opt/meshbay-common/venv/lib/python*/site-packages/meshbay_node/
+/opt/meshbay-common/venv/lib/python*/site-packages/meshbay_node-*.dist-info/
+/opt/meshbay-common/venv/bin/meshbay-node
+/usr/bin/meshbay-node
%{_unitdir}/meshbay-node@.service
%{_userunitdir}/meshbay-node.service
+/usr/lib/firewalld/services/meshbay-node.xml
+/etc/ufw/applications.d/meshbay
%changelog
-* Sun Aug 23 2026 MeshBay Team <team@meshbay.org> - 0.6.0-1
-- Per-group application enablement: roster setting, signed apps_enabled op,
- enforced by the same admin-authority check as member_upload
-
-* Sun Aug 16 2026 MeshBay Team <team@meshbay.org> - 0.5.0-1
-- Abandoned video streams release their transcode slot at once
-- Background tasks are held, so none is collected mid-flight losing a slot
-- ffmpeg is reaped without deadlocking on its own unread output
-- Chunk replies wait for room on the channel instead of queueing 8 MB
-- Chat history pages backwards; upload names accept any script, and errors name the file
-
-* Sun Aug 16 2026 MeshBay Team <team@meshbay.org> - 0.4.0-1
-- No functional change; released in step with the other packages
-
-* Sat Aug 15 2026 MeshBay Team <team@meshbay.org> - 0.3.0-1
-- Video streaming is paced by the client; stream_stop frees the transcode slot
-- Operator can remove an empty directory and revoke a member over MNP
-- meshbay-node group add: host another of your hub groups
-- admin_pk_ed25519 removed; the roster is the only source of node authority
-
-* Fri Aug 14 2026 MeshBay Team <team@meshbay.org> - 0.2.0-1
-- Node wraps the group key itself; members are admitted from a local roster
-- Identity keys are per node, created at first contact and pinned there
-- Operator surface over SSH: operator pair, member list|invite|revoke|unpin
-- Unauthenticated HTTP file API and TCP transport removed (C1, C6)
-
-* Sat Aug 09 2026 MeshBay Team <team@meshbay.org> - 0.1.0-1
-- Initial package
+* Sun Aug 31 2026 MeshBay Team <team@meshbay.org> - %{version}-1
+- Packaging overhaul: installs into shared venv under /opt
diff --git a/packaging/rpm/python3-meshbay-common.spec b/packaging/rpm/python3-meshbay-common.spec
deleted file mode 100644
index 6af8ed9..0000000
--- a/packaging/rpm/python3-meshbay-common.spec
+++ /dev/null
@@ -1,59 +0,0 @@
-Name: python3-meshbay-common
-Version: 0.6.0
-Release: 1%{?dist}
-Summary: MeshBay shared cryptographic primitives and protocol types
-License: AGPLv3+
-URL: https://meshbay.org
-Source0: meshbay-common-%{version}.tar.gz
-
-BuildArch: noarch
-BuildRequires: python3-devel >= 3.12
-BuildRequires: python3-pip
-BuildRequires: python3-hatchling
-
-Requires: python3 >= 3.12
-Requires: python3-cryptography >= 43.0
-Requires: python3-pyjwt >= 2.9
-Requires: python3-blake3 >= 1.0
-Requires: python3-msgpack >= 1.1
-Requires: python3-zstandard >= 0.23
-
-%description
-Shared library for MeshBay hub and node packages.
-Provides: Ed25519/X25519 operations, GEK wrap/unwrap, chunk
-encryption/signing, keystore AES-256-GCM, and MNP protocol types.
-
-%prep
-%autosetup -n meshbay-common-%{version}
-
-%build
-%{python3} -m pip wheel --no-deps --wheel-dir dist .
-
-%install
-%{python3} -m pip install --root %{buildroot} --no-index --find-links dist meshbay-common
-
-%files
-%license LICENSE
-%{python3_sitelib}/meshbay_common/
-%{python3_sitelib}/meshbay_common-*.dist-info/
-
-%changelog
-* Sun Aug 23 2026 MeshBay Team <team@meshbay.org> - 0.6.0-1
-- MNP 0.4: apps_enabled/apps_enabled_ack, enabled_apps on the handshake ack
-
-* Sun Aug 16 2026 MeshBay Team <team@meshbay.org> - 0.5.0-1
-- MNP 0.2: PING/PONG, and backward chat paging (before / has_more)
-
-* Sun Aug 16 2026 MeshBay Team <team@meshbay.org> - 0.4.0-1
-- No functional change; released in step with the other packages
-
-* Sat Aug 15 2026 MeshBay Team <team@meshbay.org> - 0.3.0-1
-- MNP: dir_delete, member_revoke, stream_more/stream_stop; file_chunk names its file
-
-* Fri Aug 14 2026 MeshBay Team <team@meshbay.org> - 0.2.0-1
-- Join and pairing transcripts (meshbay:join:v1)
-- Handshake refusals carry a machine-readable code
-- gek_bundle_store removed from the protocol; no member supplies key material
-
-* Sat Aug 09 2026 MeshBay Team <team@meshbay.org> - 0.1.0-1
-- Initial package
diff --git a/packaging/systemd/meshbay-hub.service b/packaging/systemd/meshbay-hub.service
index 2bea8ea..40cdb98 100644
--- a/packaging/systemd/meshbay-hub.service
+++ b/packaging/systemd/meshbay-hub.service
@@ -16,8 +16,8 @@ EnvironmentFile=-/etc/meshbay/hub.env
Environment=MESHBAY_DATABASE_URL=postgresql+asyncpg://meshbay:CHANGEME@localhost/meshbay_hub
Environment=MESHBAY_HUB_KEY=/etc/meshbay/hub_private.pem
-ExecStartPre=/usr/bin/meshbay-hub-migrate
-ExecStart=/usr/bin/meshbay-hub --config /etc/meshbay/hub.toml
+ExecStartPre=/opt/meshbay-common/venv/bin/alembic -c /opt/meshbay-hub/migrations/alembic.ini upgrade head
+ExecStart=/opt/meshbay-common/venv/bin/meshbay-hub --config /etc/meshbay/hub.toml
Restart=always
RestartSec=5
TimeoutStopSec=30
diff --git a/packaging/systemd/meshbay-node-user.service b/packaging/systemd/meshbay-node-user.service
index 62ca4de..feb415b 100644
--- a/packaging/systemd/meshbay-node-user.service
+++ b/packaging/systemd/meshbay-node-user.service
@@ -36,7 +36,7 @@ Type=simple
# configures a contact in Settings.
EnvironmentFile=-%h/.config/meshbay/node.env
-ExecStart=/usr/bin/meshbay-node --config %h/.config/meshbay/node.toml
+ExecStart=/opt/meshbay-common/venv/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
diff --git a/packaging/systemd/meshbay-node.service b/packaging/systemd/meshbay-node.service
index 5c4de6b..ad4dc40 100644
--- a/packaging/systemd/meshbay-node.service
+++ b/packaging/systemd/meshbay-node.service
@@ -24,7 +24,7 @@ WorkingDirectory=%h
# configures a contact in Settings.
EnvironmentFile=-%h/.config/meshbay/node.env
-ExecStart=/usr/bin/meshbay-node --config %h/.config/meshbay/node.toml
+ExecStart=/opt/meshbay-common/venv/bin/meshbay-node --config %h/.config/meshbay/node.toml
Restart=on-failure
RestartSec=10
TimeoutStopSec=30