Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,49 @@ jobs:
run: |
./packages/opencode/script/build.ts

- name: Build serve-only variant and upload to release
# Additive: produces `bcode-linux-{arm64,x64}-serve.tar.gz` alongside the
# standard assets, for headless containers. Built from its own package so
# `packages/opencode` — forked from upstream — stays untouched, and the
# canonical build step above is unmodified.
#
# Runs after that step and is `continue-on-error` on purpose: this variant
# must never block or alter a normal release. Different asset names, so
# `--clobber` cannot overwrite the standard archives.
#
# Linux only — the only consumer is the container image. glibc + musl,
# matching what install-bytecode.sh can select; no baseline (non-AVX2
# x64), which the installer rejects with a pointer to /install.
#
# Invoked through `bun` rather than as `./...` so a lost executable bit
# cannot silently disable the whole step under continue-on-error.
continue-on-error: true
env:
OPENCODE_VERSION: ${{ steps.ver.outputs.version }}
OPENCODE_RELEASE: "1"
OPENCODE_CHANNEL: latest
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.ver.outputs.tag }}
BCODE_DEFAULT_LMNR_KEY: ${{ secrets.LMNR_PROJECT_API_KEY_OSS }}
run: |
# Checkout has no `ref:`, so on `workflow_dispatch` the tree is the
# dispatch ref (usually main), not the selected tag — while the upload
# still targets that tag. Publishing then would put main's code inside
# the tag's assets. Build only when the tree really is the tag.
#
# The canonical build step above has the same exposure; fixing that
# means changing `Checkout` for the whole job, which is out of scope
# here. Tracked separately.
TAG_SHA=$(git rev-parse -q --verify "refs/tags/${TAG}^{commit}" || true)
HEAD_SHA=$(git rev-parse HEAD)
if [ "$HEAD_SHA" != "$TAG_SHA" ]; then
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."
exit 0
fi
bun ./packages/bcode-serve/script/build.ts \
--targets linux-arm64,linux-x64,linux-arm64-musl,linux-x64-musl
Comment thread
sauravpanda marked this conversation as resolved.

- name: Summarise uploaded assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
18 changes: 18 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

209 changes: 209 additions & 0 deletions install-bytecode.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env bash
#
# BrowserCode installer — bytecode / serve-only build.
#
# Hosted at https://bcode.sh/bytecode, alongside (not replacing)
# https://bcode.sh/install:
#
# curl -fsSL https://bcode.sh/bytecode | bash -s -- --no-modify-path --version 0.0.3
#
# Installs the `bcode-linux-<arch>-serve` asset, which provides ONLY
# `bcode serve` — `run`, `tui`, `web`, `github` and the rest are absent and
# exit 1. It exists for headless containers; anyone installing bcode on a
# laptop wants https://bcode.sh/install.
#
# `install.sh` is the published path docs point at and stays untouched. This is
# additive: a second script, a second URL, a second release asset.
#
# Linux only. Deliberately much shorter than install.sh: one build per arch (no
# baseline/AVX2 detection), no shell-rc editing (containers set PATH in the
# Dockerfile), no uv hint. Takes install.sh's flags so switching is a one-word
# change to the URL in a Dockerfile.
set -euo pipefail

APP=bcode
VARIANT=serve
REPO=browser-use/browsercode

MUTED='\033[0;2m'
RED='\033[0;31m'
ORANGE='\033[38;5;214m'
NC='\033[0m'

usage() {
cat <<EOF
BrowserCode Installer (bytecode / serve-only build)

Usage: install-bytecode.sh [options]

Installs a bcode binary that provides ONLY 'bcode serve'. For the full CLI use
https://bcode.sh/install instead.

Options:
-h, --help Display this help message
-v, --version <version> Install a specific version (e.g. 0.0.3)
--install-dir <dir> Install to <dir> (default: \$HOME/.bcode/bin)
--no-modify-path Accepted for parity with install.sh; this script
never edits shell config files.

Examples:
curl -fsSL https://bcode.sh/bytecode | bash
curl -fsSL https://bcode.sh/bytecode | bash -s -- --no-modify-path --version 0.0.3
EOF
}

requested_version=${VERSION:-}
install_dir=${BCODE_INSTALL_DIR:-$HOME/.bcode/bin}

while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-v|--version)
if [[ -n "${2:-}" ]]; then
requested_version="$2"
shift 2
else
echo -e "${RED}Error: --version requires a version argument${NC}" >&2
exit 1
fi
;;
--install-dir)
if [[ -n "${2:-}" ]]; then
install_dir="$2"
shift 2
else
echo -e "${RED}Error: --install-dir requires a path argument${NC}" >&2
exit 1
fi
;;
--no-modify-path)
# No-op: this script never touches shell config. Accepted so an
# existing install.sh invocation works verbatim against this URL.
shift
;;
*)
echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
shift
;;
esac
done

raw_os=$(uname -s)
case "$raw_os" in
Linux*) os="linux" ;;
*)
echo -e "${RED}Error: the ${VARIANT} build is published for linux only (detected: ${raw_os}).${NC}" >&2
echo -e "${MUTED}Use https://bcode.sh/install for the standard cross-platform binary.${NC}" >&2
exit 1
;;
esac

arch=$(uname -m)
case "$arch" in
aarch64|arm64) arch="arm64" ;;
x86_64|amd64) arch="x64" ;;
*)
echo -e "${RED}Error: unsupported architecture '${arch}'. Supported: arm64, x64.${NC}" >&2
exit 1
;;
esac

for tool in curl tar; do
if ! command -v "$tool" >/dev/null 2>&1; then
echo -e "${RED}Error: '${tool}' is required but not installed.${NC}" >&2
exit 1
fi
done

# A glibc binary will not start on musl, so pick the matching build rather than
# installing something that dies at exec.
is_musl=false
if [ -f /etc/alpine-release ]; then
is_musl=true
elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
is_musl=true
fi

# Non-baseline x64 builds require AVX2. This variant publishes no baseline
# asset, so say that rather than installing a binary that SIGILLs on first run.
# (Adding it is one entry in packages/bcode-serve/script/build.ts if ever needed.)
# Only trip when /proc/cpuinfo is actually readable — unknown is not "absent".
if [ "$arch" = "x64" ] && [ -r /proc/cpuinfo ] && ! grep -qwi avx2 /proc/cpuinfo; then
echo -e "${RED}Error: this CPU lacks AVX2, and no baseline ${VARIANT} build is published.${NC}" >&2
echo -e "${MUTED}Use https://bcode.sh/install, which ships a baseline binary.${NC}" >&2
exit 1
fi

target="${os}-${arch}"
[ "$is_musl" = true ] && target="${target}-musl"
filename="${APP}-${target}-${VARIANT}.tar.gz"

if [ -z "$requested_version" ]; then
url="https://github.com/${REPO}/releases/latest/download/${filename}"
specific_version=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \
| sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p' || true)
if [ -z "$specific_version" ]; then
echo -e "${RED}Failed to resolve the latest version.${NC}" >&2
echo -e "${MUTED}If the repo is private, pin a version: --version <ver>${NC}" >&2
exit 1
fi
else
specific_version="${requested_version#v}"
url="https://github.com/${REPO}/releases/download/v${specific_version}/${filename}"
fi

echo -e "${MUTED}Installing ${NC}${APP} ${MUTED}(${VARIANT} build) version: ${NC}${specific_version}"

tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/bcode_bytecode_install.XXXXXX")
staged=""
cleanup() {
rm -rf "$tmp_dir"
[ -n "${staged:-}" ] && rm -f "$staged"
return 0
}
trap cleanup EXIT

# Fail loudly rather than unpacking a 404 body. Releases predating this variant
# legitimately lack the asset, so say that instead of erroring out of tar.
if ! curl -fsSL -o "${tmp_dir}/${filename}" "$url"; then
echo -e "${RED}Error: could not download ${filename} for v${specific_version}.${NC}" >&2
echo -e "${MUTED}URL: ${url}${NC}" >&2
echo -e "${MUTED}Releases predating the ${VARIANT} variant do not publish this asset.${NC}" >&2
echo -e "${MUTED}Check https://github.com/${REPO}/releases for one that does, or use https://bcode.sh/install.${NC}" >&2
exit 1
fi

tar -xzf "${tmp_dir}/${filename}" -C "$tmp_dir"

if [ ! -f "${tmp_dir}/${APP}" ]; then
echo -e "${RED}Error: archive did not contain a '${APP}' binary.${NC}" >&2
exit 1
fi

# Stage inside the install dir, validate, then swap into place.
#
# Validating before the swap keeps a corrupt or wrong-libc download from
# replacing a working bcode. Staging in the install dir rather than the temp dir
# keeps that safety without requiring an exec-capable /tmp — `noexec` there is
# common hardening, and the install dir has to allow exec regardless. It also
# makes the final step a same-filesystem `mv`, so the swap is atomic and no
# reader can observe a half-written binary.
mkdir -p "$install_dir"
staged=$(mktemp "${install_dir}/.${APP}.XXXXXX")
mv "${tmp_dir}/${APP}" "$staged"
chmod 755 "$staged"

if ! installed_version=$("$staged" --version 2>/dev/null); then
echo -e "${RED}Error: downloaded binary failed to run; leaving any existing install untouched.${NC}" >&2
echo -e "${MUTED}If ${install_dir} is mounted noexec, install elsewhere with --install-dir.${NC}" >&2
exit 1
fi

mv "$staged" "${install_dir}/${APP}"
staged=""

echo -e "${MUTED}Installed ${NC}${install_dir}/${APP}${MUTED} (${installed_version})${NC}"
echo -e "${MUTED}This build provides '${APP} serve' only.${NC}"
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"lint": "oxlint",
"typecheck": "bun turbo typecheck --filter='@browser-use/browsercode-core...' --filter='@browser-use/bcode-browser' --filter='@browser-use/bcode-laminar'",
"typecheck": "bun turbo typecheck --filter='@browser-use/browsercode-core...' --filter='@browser-use/bcode-browser' --filter='@browser-use/bcode-laminar' --filter='@browser-use/bcode-serve'",
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
"postinstall": "bun run --cwd packages/core fix-node-pty",
"prepare": "husky",
Expand Down
25 changes: 25 additions & 0 deletions packages/bcode-serve/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.0.0",
"name": "@browser-use/bcode-serve",
"description": "Serve-only bcode binary variant for headless containers",
"type": "module",
"license": "MIT",
"private": true,
"scripts": {
"typecheck": "tsgo --noEmit",
"build": "bun run script/build.ts"
},
"dependencies": {
"@browser-use/bcode-browser": "workspace:*",
"@browser-use/browsercode-core": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/script": "workspace:*",
"yargs": "18.0.0"
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@types/yargs": "17.0.33"
}
}
Loading
Loading