1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
#!/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"
# --- Electron: always build against the latest release --------------------
#
# Chromium CVEs are fixed in Electron releases, and a client built against an
# old one ships those holes to every user. That is a certain harm; a build that
# breaks on a new Electron is a repairable one. So this bumps to the latest on
# every build and lets the build fail if it cannot cope — the failure is the
# signal to fix, not a reason to stay behind.
#
# It writes package.json and package-lock.json, so the build leaves the repo
# dirty on purpose: the new pin is meant to be committed. `npm audit` will not
# tell you any of this — Chromium CVEs fixed in Electron do not reliably reach
# the npm advisory database, which is why this check exists at all.
echo " checking for a newer Electron"
PINNED=$(node -p "require('./package-lock.json').packages['node_modules/electron'].version" 2>/dev/null || echo "unknown")
LATEST=$(npm view electron version 2>/dev/null || echo "")
if [ -z "$LATEST" ]; then
echo " !! could not reach the npm registry — building against the pinned Electron $PINNED" >&2
elif [ "$LATEST" = "$PINNED" ]; then
echo " Electron $PINNED is the latest"
else
echo ""
echo " ==> Electron $PINNED -> $LATEST"
echo ""
npm install --save-dev --ignore-scripts "electron@$LATEST" 2>&1 | tail -2
# allowScripts pins an exact version; leave it matching so npm does not
# start refusing a script a future Electron reintroduces.
node -e "
const fs = require('node:fs');
const p = JSON.parse(fs.readFileSync('package.json', 'utf8'));
if (p.allowScripts) {
for (const k of Object.keys(p.allowScripts)) {
if (k.startsWith('electron@')) {
delete p.allowScripts[k];
p.allowScripts['electron@$LATEST'] = true;
}
}
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
}
"
echo " package.json and package-lock.json updated — commit them"
fi
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)"
# `npm ci` above deletes node_modules wholesale and install.js re-extracts
# Electron's dist, so a chrome-sandbox that had been made root-owned 4755 for
# local development comes back 755, owned by whoever ran this build. Running
# the app straight from node_modules then aborts outright — Chromium refuses
# to run without its SUID helper rather than quietly dropping the sandbox,
# which is the right call and a baffling one if you have not connected the
# crash to a package build you ran minutes earlier.
#
# Said, not done: this script does not use sudo, and a build is not where a
# setuid bit should be set behind someone's back. The *packaged* app is
# unaffected — packaging/deb/meshbay-client/DEBIAN/postinst does it at
# install time, which is where it belongs.
SANDBOX="$CLIENT/node_modules/electron/dist/chrome-sandbox"
if [ -e "$SANDBOX" ] && [ ! -u "$SANDBOX" ]; then
echo
echo " NOTE: this build reset $SANDBOX to $(stat -c '%a %U:%G' "$SANDBOX")."
echo " Running Electron from node_modules needs it back:"
echo " sudo chown root:root '$SANDBOX' && sudo chmod 4755 '$SANDBOX'"
fi
|