#!/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 the shared TMDB token, read at build time and copied to # /node.env by `meshbay-node init`. # # tmdb.py sends `Authorization: Bearer`, so this is the v4 *read access token* # (a JWT, "eyJ..."), not the 32-char v3 API key that sits beside it in the same # note file. Sources, in order: an explicit variable, an explicit file, the # KEY=VALUE form, then QE/tmdb.txt -- which is free-form prose, so the token is # matched by shape rather than by a label. extract_tmdb_token() { local file="$1" tok="" [ -f "$file" ] || return 0 tok=$(sed -n 's/^[[:space:]]*MESHBAY_TMDB_DEFAULT_TOKEN[[:space:]]*=[[:space:]]*//p' \ "$file" | head -1) [ -n "$tok" ] || tok=$(grep -oE '^eyJ[A-Za-z0-9._-]{40,}$' "$file" | head -1 || true) printf '%s' "$tok" | tr -d '"'"'"'\r' } TMDB_TOKEN="${MESHBAY_TMDB_TOKEN:-}" if [ -z "$TMDB_TOKEN" ] && [ -n "${MESHBAY_TMDB_TOKEN_FILE:-}" ]; then TMDB_TOKEN=$(extract_tmdb_token "$MESHBAY_TMDB_TOKEN_FILE") fi [ -n "$TMDB_TOKEN" ] || TMDB_TOKEN=$(extract_tmdb_token "$REPO/QE/node.env") [ -n "$TMDB_TOKEN" ] || TMDB_TOKEN=$(extract_tmdb_token "$REPO/QE/tmdb.txt") if [ -n "$TMDB_TOKEN" ]; then cat > "$ROOT/opt/meshbay-node/share/default.env" </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 chmod 600 "$ROOT/opt/meshbay-node/share/default.env" echo " TMDB token baked into default.env (${#TMDB_TOKEN} chars)" elif [ "${MESHBAY_ALLOW_NO_TMDB:-0}" = "1" ]; then echo " !! no TMDB token; default.env left empty (MESHBAY_ALLOW_NO_TMDB=1)" >&2 : > "$ROOT/opt/meshbay-node/share/default.env" else # Failing here is deliberate: an empty default.env is invisible until a user # opens the Videos app and finds no metadata, which is exactly how this # shipped empty on two platforms at once. echo "!! TMDB token not found. Looked at:" >&2 echo " \$MESHBAY_TMDB_TOKEN, \$MESHBAY_TMDB_TOKEN_FILE," >&2 echo " $REPO/QE/node.env, $REPO/QE/tmdb.txt" >&2 echo " Set MESHBAY_ALLOW_NO_TMDB=1 to build without it." >&2 exit 1 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 -------------------------------------------------------- # The node's admin surface is a loopback API (127.0.0.1, token-gated) and is # never firewall-exposed. Its *peer* traffic is: WebRTC binds an ephemeral UDP # port per connection, and a peer that publishes an unroutable address — every # browser does, as an mDNS .local name aioice cannot resolve — can only be # reached if it calls the node. A node refusing unsolicited inbound UDP is # therefore unreachable from browsers on its own LAN. Both profiles are passive: # packaged, not activated, and meant to be scoped to a LAN zone/source. mkdir -p "$ROOT/etc/ufw/applications.d" cp "$REPO/packaging/firewall/ufw/meshbay" \ "$ROOT/etc/ufw/applications.d/" mkdir -p "$ROOT/usr/lib/firewalld/services" cp "$REPO/packaging/firewall/firewalld/meshbay-node.xml" \ "$ROOT/usr/lib/firewalld/services/" echo "==> meshbay-node staging ready at $ROOT"