Skip to content

Commit 400500e

Browse files
committed
fix(install): unbreak macOS, atomic Linux install, real version smoke test
The current install script has three issues that affect every user: 1. macOS install produces a binary the kernel kills at exec The nightly Mach-O assets from GitHub Releases (lightpanda-aarch64-macos, lightpanda-x86_64-macos) get SIGKILLed at exec on Apple Silicon despite shipping with an ad-hoc / linker signature. Ad-hoc re-signing locally does not help. Reproduces 100% of the time on M-series Macs running the release asset directly. The existing brew tap at lightpanda-io/browser/lightpanda builds from source and produces a binary that runs, so route Darwin through it. 2. Linux install destroys an existing working binary on any failed rerun SKILL.md documents reruns of this script as the update path: > If you encounter crashes or issues, run scripts/install.sh again to > update to the latest version (max once per day). But `curl -L -o "$INSTALL_DIR/$BINARY_NAME"` overwrites the install target during download. A failed download (network drop, checksum mismatch, unrunnable replacement) leaves the user with no binary or a broken one even though they had a working one before. Atomic install via mktemp -> verify -> smoke-test -> mv preserves the existing binary on any failure. 3. Smoke test silently accepts broken binaries The test currently is: "$INSTALL_DIR/$BINARY_NAME" --version 2>/dev/null || \ "$INSTALL_DIR/$BINARY_NAME" --help 2>/dev/null | head -1 Lightpanda's CLI does not have a --version flag. Running it logs '$msg=exit err=UnknownCommand' to stderr and exits 1. The 2>/dev/null masks this and the script falls through to the --help branch. On a binary that crashes at exec (the macOS case above), --help also fails, but the surrounding 'if ... | head -1; then' construction still ends in the success branch on macOS because head exits 0 on empty stdin. Result: 'Lightpanda installed successfully!' for an unrunnable binary. Use the 'version' subcommand (which is the actual Lightpanda CLI form), capture stderr to a tempfile, and fail hard with the binary's stderr surfaced so users see real diagnostics like 'GLIBC_2.32 not found' on incompatible systems. ## Other changes in this PR - `set -euo pipefail` (was `set -e`). `pipefail` propagates a failing curl through `curl | jq` (jq exits 0 on empty input, otherwise hiding network errors behind a misleading 'could not retrieve checksum'). - `chmod 0755` instead of `chmod a+x`. mktemp creates 0600; `a+x` on top of that is 0711 (owner-only readable), which is surprising on a shared install path. - Optional $GITHUB_TOKEN raises the GitHub API quota from 60->5000/hr. Branched curl call rather than building a header array — Bash <=4.3 (RHEL 7, old containers) errors on empty-array expansion under `set -u`. - PATH-shadow check on both macOS and Linux. Warns when another `lightpanda` is earlier in PATH than the freshly installed one (catches stale binaries from earlier install attempts). - SKILL.md install section gains a per-OS strategy callout describing the new flow. ## Verified End-to-end run on Apple Silicon (after first reverting the broken release binary): brew install/upgrade succeeds, version subcommand prints '1.0.0-nightly.6051+d360fcc0', PATH check passes.
1 parent bc562f3 commit 400500e

2 files changed

Lines changed: 131 additions & 42 deletions

File tree

SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ Prefer the built-in Web Search tool when it is available and sufficient for your
2525
bash scripts/install.sh
2626
```
2727

28-
Lightpanda is available on Linux and macOS only. Windows is supported via WSL2.
28+
Lightpanda is available on Linux and macOS only. Windows is supported via WSL2. The script picks the right method per OS:
29+
- **macOS** — installs via the upstream Homebrew tap (`brew install lightpanda-io/browser/lightpanda`). Requires Homebrew. The nightly Mach-O binaries from GitHub Releases get killed by the kernel on Apple Silicon despite shipping with an ad-hoc / linker signature, so the script delegates to the tap, which builds from source.
30+
- **Linux** — downloads the nightly binary from GitHub Releases, verifies its SHA-256, smoke-tests it, and atomically installs to `$HOME/.local/bin/lightpanda` (override with `LIGHTPANDA_DIR=...`). On rerun, the existing binary is left in place if any step fails.
2931

3032
The binary is a nightly build that evolves quickly. If you encounter crashes or issues, run `scripts/install.sh` again to update to the latest version (max once per day).
3133

scripts/install.sh

Lines changed: 128 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
#!/bin/bash
22
# Lightpanda Setup Script
3-
# Run once to install Lightpanda browser
3+
# Run to install or update the Lightpanda browser binary.
4+
#
5+
# macOS — installs via the upstream Homebrew tap. The nightly Mach-O
6+
# assets from GitHub Releases are SIGKILLed at exec on Apple
7+
# Silicon despite shipping with an ad-hoc / linker signature,
8+
# so we delegate to brew which builds from source.
9+
# Linux — downloads the nightly asset, verifies its SHA-256, smoke-
10+
# tests it, and atomically `mv`s it into $LIGHTPANDA_DIR
11+
# (default $HOME/.local/bin). The existing binary is left in
12+
# place if any step fails — this is the documented update path.
13+
14+
# `pipefail` propagates a failing curl in `curl | jq` (jq exits 0 on
15+
# empty input). `-u` turns variable typos into hard errors.
16+
set -euo pipefail
417

5-
set -e
6-
7-
INSTALL_DIR="${LIGHTPANDA_DIR:-$HOME/.local/bin}"
818
BINARY_NAME="lightpanda"
919

1020
echo "=== Lightpanda Setup ==="
11-
echo "Install directory: $INSTALL_DIR"
1221

13-
# Detect OS and architecture
1422
OS="$(uname -s)"
1523
ARCH="$(uname -m)"
1624

@@ -30,18 +38,59 @@ case "$OS" in
3038
esac
3139
;;
3240
Darwin)
33-
case "$ARCH" in
34-
x86_64)
35-
DOWNLOAD_URL="https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-macos"
36-
;;
37-
arm64|aarch64)
38-
DOWNLOAD_URL="https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-aarch64-macos"
39-
;;
40-
*)
41-
echo "ERROR: Unsupported architecture: $ARCH"
42-
exit 1
43-
;;
44-
esac
41+
if ! command -v brew &> /dev/null; then
42+
echo "ERROR: macOS install requires Homebrew (https://brew.sh)."
43+
echo "Install Homebrew, then re-run this script. Or install Lightpanda directly:"
44+
echo " brew install lightpanda-io/browser/lightpanda"
45+
exit 1
46+
fi
47+
echo "Detected: $OS $ARCH — installing via Homebrew tap (lightpanda-io/browser/lightpanda)."
48+
# `install` is idempotent on an up-to-date install but does not
49+
# auto-upgrade an existing one; `upgrade` after keeps reruns
50+
# current. Both exit 0 in the up-to-date case and non-zero on
51+
# real errors — let `set -e` propagate genuine failures.
52+
brew install lightpanda-io/browser/lightpanda
53+
brew upgrade lightpanda-io/browser/lightpanda
54+
# Anchor verification to the brew prefix (general, not keg-
55+
# specific): a stale `~/.local/bin/lightpanda` from an older
56+
# curl-on-macOS attempt could shadow the brew binary and make
57+
# us report success for a broken one.
58+
BREW_PREFIX="$(brew --prefix)"
59+
BIN_PATH="$BREW_PREFIX/bin/$BINARY_NAME"
60+
if [ ! -x "$BIN_PATH" ]; then
61+
echo "ERROR: Homebrew install completed but $BIN_PATH is missing."
62+
echo "Try: brew link --overwrite lightpanda"
63+
exit 1
64+
fi
65+
# Hard-fail smoke test: brew can produce a binary that crashes
66+
# at exec (formula regression, missing dep). Stderr to a file
67+
# so we can show real diagnostics on failure.
68+
DARWIN_SMOKE_STDERR=$(mktemp)
69+
trap 'rm -f "$DARWIN_SMOKE_STDERR"' EXIT
70+
if VERSION_OUT=$("$BIN_PATH" version 2>"$DARWIN_SMOKE_STDERR") && [ -n "$VERSION_OUT" ]; then
71+
: # ok
72+
else
73+
echo "ERROR: Homebrew-installed lightpanda failed its version smoke test (\`$BIN_PATH version\` returned no output or non-zero exit)."
74+
if [ -s "$DARWIN_SMOKE_STDERR" ]; then
75+
echo " Binary stderr:"
76+
sed 's/^/ /' "$DARWIN_SMOKE_STDERR"
77+
fi
78+
echo " Try: brew reinstall lightpanda-io/browser/lightpanda"
79+
exit 1
80+
fi
81+
echo ""
82+
echo "Lightpanda installed via Homebrew: $VERSION_OUT"
83+
echo "Binary location: $BIN_PATH"
84+
# Warn if something earlier in PATH shadows the brew binary.
85+
PATH_RESOLVED="$(command -v lightpanda 2>/dev/null || true)"
86+
if [ -n "$PATH_RESOLVED" ] && [ "$PATH_RESOLVED" != "$BIN_PATH" ]; then
87+
echo ""
88+
echo "WARNING: another 'lightpanda' is earlier in your PATH:"
89+
echo " $PATH_RESOLVED"
90+
echo "Remove it or reorder PATH so $BREW_PREFIX/bin takes precedence."
91+
fi
92+
echo "Run with: lightpanda serve --host 127.0.0.1 --port 9222"
93+
exit 0
4594
;;
4695
*)
4796
echo "ERROR: Unsupported OS: $OS"
@@ -50,10 +99,12 @@ case "$OS" in
5099
;;
51100
esac
52101

102+
# --- Linux flow ---
103+
INSTALL_DIR="${LIGHTPANDA_DIR:-$HOME/.local/bin}"
53104
echo "Detected: $OS $ARCH"
105+
echo "Install directory: $INSTALL_DIR"
54106
echo "Download URL: $DOWNLOAD_URL"
55107

56-
# Check for required tools
57108
if ! command -v curl &> /dev/null; then
58109
echo "ERROR: curl not found. Please install curl."
59110
exit 1
@@ -69,65 +120,101 @@ if ! command -v sha256sum &> /dev/null && ! command -v shasum &> /dev/null; then
69120
exit 1
70121
fi
71122

72-
# Create install directory
73123
mkdir -p "$INSTALL_DIR"
74124

75-
# Determine asset name from URL
76125
ASSET_NAME="${DOWNLOAD_URL##*/}"
77126

78-
# Fetch expected SHA256 digest from GitHub release API
127+
# api.github.com unauthenticated quota is 60/hr/IP; collide on shared
128+
# NAT or in CI. $GITHUB_TOKEN raises it to 5000/hr. Branch the curl
129+
# call rather than building an array of header args — `"${arr[@]}"`
130+
# under `set -u` errors on Bash <=4.3 (RHEL 7, old containers).
131+
GITHUB_API_URL="https://api.github.com/repos/lightpanda-io/browser/releases/tags/nightly"
79132
echo "Fetching expected checksum from GitHub API..."
80-
EXPECTED_DIGEST=$(curl -sL "https://api.github.com/repos/lightpanda-io/browser/releases/tags/nightly" \
81-
| jq -r --arg name "$ASSET_NAME" '.assets[] | select(.name == $name) | .digest')
133+
if [ -n "${GITHUB_TOKEN:-}" ]; then
134+
EXPECTED_DIGEST=$(curl -fSL -H "Authorization: Bearer $GITHUB_TOKEN" "$GITHUB_API_URL" \
135+
| jq -r --arg name "$ASSET_NAME" '.assets[] | select(.name == $name) | .digest')
136+
else
137+
EXPECTED_DIGEST=$(curl -fSL "$GITHUB_API_URL" \
138+
| jq -r --arg name "$ASSET_NAME" '.assets[] | select(.name == $name) | .digest')
139+
fi
82140

83141
if [ -z "$EXPECTED_DIGEST" ] || [ "$EXPECTED_DIGEST" = "null" ]; then
84142
echo "ERROR: Could not retrieve checksum for $ASSET_NAME from GitHub API."
85143
exit 1
86144
fi
87145

88-
# Strip the "sha256:" prefix
89146
EXPECTED_SHA256="${EXPECTED_DIGEST#sha256:}"
90147
echo "Expected SHA256: $EXPECTED_SHA256"
91148

92-
# Download binary
93-
echo "Downloading Lightpanda..."
94-
curl -L -o "$INSTALL_DIR/$BINARY_NAME" "$DOWNLOAD_URL"
149+
# Download to a temp file in the same directory, verify, smoke-test,
150+
# THEN atomically `mv` into place. SKILL.md documents reruns as the
151+
# update path, so a failed download must not destroy a working binary.
152+
TMP_BINARY=$(mktemp "$INSTALL_DIR/.${BINARY_NAME}.XXXXXX")
153+
SMOKE_STDERR=$(mktemp)
154+
trap 'rm -f "$TMP_BINARY" "$SMOKE_STDERR"' EXIT
155+
156+
echo "Downloading Lightpanda to $TMP_BINARY..."
157+
curl -fSL -o "$TMP_BINARY" "$DOWNLOAD_URL"
95158

96-
# Verify checksum
97159
echo "Verifying checksum..."
98160
if command -v sha256sum &> /dev/null; then
99-
ACTUAL_SHA256=$(sha256sum "$INSTALL_DIR/$BINARY_NAME" | awk '{print $1}')
161+
ACTUAL_SHA256=$(sha256sum "$TMP_BINARY" | awk '{print $1}')
100162
else
101-
ACTUAL_SHA256=$(shasum -a 256 "$INSTALL_DIR/$BINARY_NAME" | awk '{print $1}')
163+
ACTUAL_SHA256=$(shasum -a 256 "$TMP_BINARY" | awk '{print $1}')
102164
fi
103165

104166
if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then
105167
echo "ERROR: Checksum verification FAILED!"
106168
echo " Expected: $EXPECTED_SHA256"
107169
echo " Actual: $ACTUAL_SHA256"
108-
rm -f "$INSTALL_DIR/$BINARY_NAME"
170+
echo " Existing binary (if any) at $INSTALL_DIR/$BINARY_NAME was NOT replaced."
109171
exit 1
110172
fi
111173

112174
echo "Checksum verified OK."
113-
chmod a+x "$INSTALL_DIR/$BINARY_NAME"
175+
# 0755 (conventional executable mode) rather than `chmod a+x` over
176+
# mktemp's 0600, which would yield 0711 (owner-only readable).
177+
chmod 0755 "$TMP_BINARY"
178+
179+
# Smoke-test BEFORE replacing. Use the `version` subcommand — Lightpanda's
180+
# `--version` flag exits 1 with `UnknownCommand`, so it would falsely
181+
# reject a working binary. Capturing stdout to a variable means the `if`
182+
# exit status is the binary's exit status (no pipe to mask it).
183+
echo "Smoke-testing the downloaded binary..."
184+
if ! VERSION_OUT=$("$TMP_BINARY" version 2>"$SMOKE_STDERR") || [ -z "$VERSION_OUT" ]; then
185+
echo "ERROR: Downloaded binary failed to run (\`version\` subcommand returned no output or non-zero exit)."
186+
echo " This may indicate an incompatible system, missing libc, or a corrupted release asset."
187+
if [ -s "$SMOKE_STDERR" ]; then
188+
echo " Binary stderr:"
189+
sed 's/^/ /' "$SMOKE_STDERR"
190+
fi
191+
echo " Existing binary (if any) at $INSTALL_DIR/$BINARY_NAME was NOT replaced."
192+
exit 1
193+
fi
194+
195+
# `mv` within the same filesystem is atomic.
196+
mv "$TMP_BINARY" "$INSTALL_DIR/$BINARY_NAME"
197+
# Leave the EXIT trap registered so $SMOKE_STDERR still gets cleaned;
198+
# `rm -f` on the moved-away $TMP_BINARY is a silent no-op.
114199

115-
# Check if install directory is in PATH
200+
# Check PATH membership AND shadow (something else earlier in PATH).
116201
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
117202
echo ""
118203
echo "WARNING: $INSTALL_DIR is not in your PATH"
119204
echo "Add this to your shell profile (~/.bashrc or ~/.zshrc):"
120205
echo " export PATH=\"\$PATH:$INSTALL_DIR\""
206+
else
207+
PATH_RESOLVED="$(command -v lightpanda 2>/dev/null || true)"
208+
if [ -n "$PATH_RESOLVED" ] && [ "$PATH_RESOLVED" != "$INSTALL_DIR/$BINARY_NAME" ]; then
209+
echo ""
210+
echo "WARNING: another 'lightpanda' is earlier in your PATH:"
211+
echo " $PATH_RESOLVED"
212+
echo "Remove it or reorder PATH so $INSTALL_DIR takes precedence."
213+
fi
121214
fi
122215

123-
# Test installation
124216
echo ""
125-
echo "Testing Lightpanda..."
126-
if "$INSTALL_DIR/$BINARY_NAME" --version 2>/dev/null || "$INSTALL_DIR/$BINARY_NAME" --help 2>/dev/null | head -1; then
127-
echo "Lightpanda installed successfully!"
128-
else
129-
echo "Binary installed at: $INSTALL_DIR/$BINARY_NAME"
130-
fi
217+
echo "Lightpanda installed successfully: ${VERSION_OUT:-OK}"
131218

132219
echo ""
133220
echo "=== Setup Complete ==="

0 commit comments

Comments
 (0)