Skip to content

Commit 6799d6a

Browse files
authored
Merge pull request #148 from browser-use/claude/cold-startup-times-cd25a7
feat(bcode-serve): serve-only binary variant for headless containers
2 parents 2229055 + fe459c6 commit 6799d6a

8 files changed

Lines changed: 579 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,42 @@ jobs:
146146
run: |
147147
./packages/opencode/script/build.ts
148148
149+
- name: Build serve-only variant and upload to release
150+
# Additive: publishes `bcode-linux-{arm64,x64}[-musl]-serve.tar.gz` for
151+
# headless containers, from its own package so the upstream-forked
152+
# `packages/opencode` and the canonical step above stay untouched.
153+
#
154+
# `continue-on-error` so this variant can never block a normal release;
155+
# its asset names differ, so `--clobber` cannot touch the standard ones.
156+
# Invoked via `bun` so a lost executable bit cannot silently disable it.
157+
#
158+
# No baseline (non-AVX2 x64) target — install-bytecode.sh rejects that
159+
# case with a pointer to /install rather than shipping a binary that
160+
# SIGILLs.
161+
continue-on-error: true
162+
env:
163+
OPENCODE_VERSION: ${{ steps.ver.outputs.version }}
164+
OPENCODE_RELEASE: "1"
165+
OPENCODE_CHANNEL: latest
166+
GH_REPO: ${{ github.repository }}
167+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
168+
TAG: ${{ steps.ver.outputs.tag }}
169+
BCODE_DEFAULT_LMNR_KEY: ${{ secrets.LMNR_PROJECT_API_KEY_OSS }}
170+
run: |
171+
# Checkout has no `ref:`, so on `workflow_dispatch` the tree is the
172+
# dispatch ref, not the selected tag, while the upload still targets
173+
# that tag. Only build when the tree really is the tag. (The canonical
174+
# step has the same exposure; fixing it means changing `Checkout` for
175+
# the whole job, which is out of scope here.)
176+
TAG_SHA=$(git rev-parse -q --verify "refs/tags/${TAG}^{commit}" || true)
177+
HEAD_SHA=$(git rev-parse HEAD)
178+
if [ "$HEAD_SHA" != "$TAG_SHA" ]; then
179+
echo "::warning::Skipped the serve variant: checkout $(git rev-parse --short HEAD) is not tag ${TAG}. Re-run from a state where they match to publish it."
180+
exit 0
181+
fi
182+
bun ./packages/bcode-serve/script/build.ts \
183+
--targets linux-arm64,linux-x64,linux-arm64-musl,linux-x64-musl
184+
149185
- name: Summarise uploaded assets
150186
env:
151187
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

bun.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

install-bytecode.sh

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/bin/sh
2+
#
3+
# BrowserCode installer — serve-only build. Hosted at https://bcode.sh/bytecode,
4+
# alongside (not replacing) https://bcode.sh/install, which stays the path for
5+
# the full CLI.
6+
#
7+
# curl -fsSL https://bcode.sh/bytecode | sh -s -- --no-modify-path --version 0.1.19
8+
#
9+
# Installs `bcode-linux-<arch>[-musl]-serve`, which provides ONLY `bcode serve`;
10+
# run, tui, web, github and the rest are absent and exit 1. It exists for headless
11+
# containers.
12+
#
13+
# POSIX sh, not bash: Alpine is a supported target here and ships no bash.
14+
set -eu
15+
16+
REPO=browser-use/browsercode
17+
DIM='\033[0;2m'
18+
RED='\033[0;31m'
19+
OFF='\033[0m'
20+
21+
say() { printf "${DIM}%s${OFF}\n" "$1" >&2; }
22+
die() {
23+
printf "${RED}%s${OFF}\n" "$1" >&2
24+
shift
25+
for line in "$@"; do [ -n "$line" ] && printf "${DIM}%s${OFF}\n" "$line" >&2; done
26+
exit 1
27+
}
28+
29+
# Reject empty and flag-shaped values: `--version "$UNSET"` would otherwise
30+
# silently install latest, which is exactly what pinning exists to prevent.
31+
need() {
32+
case $2 in
33+
"" | -*) die "$1 needs a value (got: ${2:-<missing>})" ;;
34+
esac
35+
}
36+
37+
usage() {
38+
cat >&2 <<EOF
39+
BrowserCode installer (serve-only build)
40+
41+
-v, --version <ver> version to install (default: latest release)
42+
--install-dir <d> where to install (default: \$HOME/.bcode/bin)
43+
--no-modify-path accepted for install.sh parity; this script never edits
44+
shell config files
45+
-h, --help show this help
46+
47+
Installs a bcode that provides ONLY 'bcode serve'.
48+
For the full CLI: curl -fsSL https://bcode.sh/install | bash
49+
EOF
50+
}
51+
52+
# Namespaced on purpose: a bare VERSION is a common Dockerfile ARG, and picking
53+
# it up here would silently install the wrong build.
54+
version=${BCODE_VERSION:-}
55+
# HOME is routinely unset under `--user`/runAsUser with no passwd entry.
56+
install_dir=${BCODE_INSTALL_DIR:-${HOME:-/root}/.bcode/bin}
57+
58+
while [ $# -gt 0 ]; do
59+
case $1 in
60+
-v | --version) need "--version" "${2:-}"; version=$2; shift 2 ;;
61+
--install-dir) need "--install-dir" "${2:-}"; install_dir=$2; shift 2 ;;
62+
--no-modify-path) shift ;;
63+
# Tolerated: wrappers relaying args often forward an extra one.
64+
--) shift ;;
65+
-h | --help) usage; exit 0 ;;
66+
# Fail rather than warn: a typo'd flag would otherwise quietly install
67+
# "latest" into a build that meant to pin a version.
68+
*) die "Unknown option: $1" "Run with --help for usage." ;;
69+
esac
70+
done
71+
72+
for tool in curl tar; do
73+
command -v "$tool" >/dev/null 2>&1 || die "'$tool' is required but not installed."
74+
done
75+
76+
[ "$(uname -s)" = Linux ] || die \
77+
"The serve build is published for linux only (got $(uname -s))." \
78+
"Use https://bcode.sh/install for the standard cross-platform binary."
79+
80+
case $(uname -m) in
81+
aarch64 | arm64) arch=arm64 ;;
82+
x86_64 | amd64) arch=x64 ;;
83+
*) die "Unsupported architecture: $(uname -m)." "Supported: arm64, x64." ;;
84+
esac
85+
86+
# Non-baseline x64 builds need AVX2 and no baseline serve asset is published.
87+
# Only decide when /proc/cpuinfo actually carries a flags line: emulated or
88+
# redacted cpuinfo (qemu `--platform linux/amd64` on arm64, lxcfs, some
89+
# hypervisors) has none, and unknown is not the same as absent.
90+
if [ "$arch" = x64 ] && grep -qi '^flags' /proc/cpuinfo 2>/dev/null && ! grep -qwi avx2 /proc/cpuinfo; then
91+
die "This CPU has no AVX2 and no baseline serve build is published." \
92+
"Use https://bcode.sh/install, which ships a baseline binary."
93+
fi
94+
95+
# A glibc binary cannot exec on musl. musl's ldd prints its banner and then exits
96+
# non-zero, so match its output — the pipeline's status is grep's, not ldd's.
97+
target=linux-$arch
98+
if [ -f /etc/alpine-release ] || ldd --version 2>&1 | grep -qi musl; then
99+
target=$target-musl
100+
fi
101+
asset=bcode-$target-serve.tar.gz
102+
103+
if [ -n "$version" ]; then
104+
version=${version#v}
105+
else
106+
version=$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null |
107+
sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
108+
[ -n "$version" ] || die "Could not resolve the latest version." "Pin one with --version <ver>."
109+
fi
110+
url=https://github.com/$REPO/releases/download/v$version/$asset
111+
112+
say "Installing bcode $version ($target, serve build)"
113+
114+
tmp=$(mktemp -d)
115+
staged=
116+
# `return 0` is load-bearing: the `&&` above it returns 1 whenever staged is
117+
# empty, which under `set -e` would turn a clean run into a non-zero exit.
118+
cleanup() {
119+
rm -rf "$tmp"
120+
[ -n "$staged" ] && rm -f "$staged"
121+
return 0
122+
}
123+
trap cleanup EXIT
124+
# ash/dash do not run the EXIT trap on a signal; `docker build` cancellation
125+
# sends TERM. Exiting from the handler routes through the EXIT trap above.
126+
trap 'exit 130' INT
127+
trap 'exit 143' TERM
128+
trap 'exit 129' HUP
129+
130+
curl -fsSL -o "$tmp/$asset" "$url" || die \
131+
"Could not download $asset for v$version." \
132+
"URL: $url" \
133+
"Releases published before this variant existed do not carry the asset."
134+
135+
tar -xzf "$tmp/$asset" -C "$tmp"
136+
[ -f "$tmp/bcode" ] || die "Archive did not contain a bcode binary."
137+
138+
# Stage inside the install dir, validate, then swap. Validating first means a bad
139+
# download never replaces a working bcode; staging here rather than in /tmp avoids
140+
# requiring an exec-capable /tmp (noexec there is common hardening) and makes the
141+
# final step a same-filesystem rename, so the swap is atomic.
142+
case $install_dir in
143+
"") die "Install directory cannot be empty." ;;
144+
-*) die "Install directory must not start with '-' (got: $install_dir)." ;;
145+
esac
146+
mkdir -p -- "$install_dir"
147+
if [ -d "$install_dir/bcode" ]; then
148+
die "$install_dir/bcode is a directory; refusing to install over it."
149+
fi
150+
staged=$(mktemp "$install_dir/.bcode.XXXXXX")
151+
mv "$tmp/bcode" "$staged"
152+
chmod 755 "$staged"
153+
154+
# Keep the binary's own stderr: on a libc mismatch it names the missing loader,
155+
# which is the actual diagnosis.
156+
if ! installed=$("$staged" --version 2>"$tmp/err"); then
157+
die "Downloaded binary failed to run; existing install left untouched." \
158+
"$(cat "$tmp/err" 2>/dev/null)" \
159+
"If $install_dir is mounted noexec, pass --install-dir."
160+
fi
161+
162+
mv "$staged" "$install_dir/bcode"
163+
staged=
164+
165+
say "Installed $install_dir/bcode ($installed) — provides 'bcode serve' only"
166+
case ":$PATH:" in
167+
*":$install_dir:"*) ;;
168+
*) say "Not on PATH. Add it with: export PATH=\"$install_dir:\$PATH\"" ;;
169+
esac

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
1414
"dev:storybook": "bun --cwd packages/storybook storybook",
1515
"lint": "oxlint",
16-
"typecheck": "bun turbo typecheck --filter='@browser-use/browsercode-core...' --filter='@browser-use/bcode-browser' --filter='@browser-use/bcode-laminar'",
16+
"typecheck": "bun turbo typecheck --filter='@browser-use/browsercode-core...' --filter='@browser-use/bcode-browser' --filter='@browser-use/bcode-laminar' --filter='@browser-use/bcode-serve'",
1717
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
1818
"postinstall": "bun run --cwd packages/core fix-node-pty",
1919
"prepare": "husky",

packages/bcode-serve/package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "https://json.schemastore.org/package.json",
3+
"version": "0.0.0",
4+
"name": "@browser-use/bcode-serve",
5+
"description": "Serve-only bcode binary variant for headless containers",
6+
"type": "module",
7+
"license": "MIT",
8+
"private": true,
9+
"scripts": {
10+
"typecheck": "tsgo --noEmit",
11+
"build": "bun run script/build.ts"
12+
},
13+
"dependencies": {
14+
"@browser-use/bcode-browser": "workspace:*",
15+
"@browser-use/browsercode-core": "workspace:*",
16+
"@opencode-ai/core": "workspace:*",
17+
"@opencode-ai/script": "workspace:*",
18+
"yargs": "18.0.0"
19+
},
20+
"devDependencies": {
21+
"@tsconfig/bun": "catalog:",
22+
"@types/bun": "catalog:",
23+
"@types/yargs": "17.0.33"
24+
}
25+
}

0 commit comments

Comments
 (0)