Skip to content

Fix the release verify picking a musl binary on glibc runners #2

Fix the release verify picking a musl binary on glibc runners

Fix the release verify picking a musl binary on glibc runners #2

name: utmstack-release
# Builds the branded `utmstack` binaries for every platform the installers can
# request, and publishes them with a checksums.txt the installers verify.
#
# The installer downloads "$APP-$target" (e.g. utmstack-linux-x64.tar.gz), so a
# target missing here is a 404 for those users. The -baseline variants matter:
# without them, any x64 machine lacking AVX2 — which includes a lot of cloud
# VMs — cannot install.
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
version:
description: "Version to build (without the leading v)"
required: true
permissions:
contents: write
jobs:
build:
name: ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# macOS. Built natively so PyInstaller/Bun ad-hoc signing applies —
# an unsigned arm64 binary will not execute on Apple Silicon.
- { runner: macos-14, target: darwin-arm64, ext: zip }
- { runner: macos-15-intel, target: darwin-x64, ext: zip, baseline: true }
# Linux. x64 builds on 22.04 (glibc 2.35) so it runs on Ubuntu 22.04+
# and RHEL 9+. arm64 has no 22.04 ARM runner we can rely on, so it
# builds on 24.04 (glibc 2.39) and needs Ubuntu 24.04+ / Debian 13+.
- { runner: ubuntu-22.04, target: linux-x64, ext: tar.gz, baseline: true }
- { runner: ubuntu-24.04-arm, target: linux-arm64, ext: tar.gz }
# Windows, both architectures.
- { runner: windows-2022, target: windows-x64, ext: zip, baseline: true }
- { runner: windows-11-arm, target: windows-arm64, ext: zip }
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
- name: Resolve version
id: version
shell: bash
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
echo "value=${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
else
echo "value=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
fi
- name: Install dependencies
run: bun install
- name: Build
working-directory: packages/opencode
shell: bash
env:
OPENCODE_VERSION: ${{ steps.version.outputs.value }}
run: |
# --baseline additionally emits the no-AVX2 variant for x64 targets.
if [ "${{ matrix.baseline }}" = "true" ]; then
bun run script/build.ts --single --baseline
else
bun run script/build.ts --single
fi
ls -la dist/
- name: Verify the binary is branded and self-contained
shell: bash
working-directory: packages/opencode
run: |
set -eu
# Address the exact matrix target. `find | head -1` picked whichever
# directory sorted first, which on x64 runs was the musl variant —
# a musl-linked binary cannot execute on a glibc runner (missing ELF
# interpreter, which surfaces as "No such file or directory" / 127).
DIST="dist/opencode-${{ matrix.target }}"
BIN="$DIST/bin/utmstack"
[ -f "$BIN" ] || BIN="$DIST/bin/utmstack.exe"
[ -f "$BIN" ] || { echo "no binary at $DIST/bin"; ls -R dist | head -40; exit 1; }
echo "binary: $BIN"
got=$("$BIN" --version | tr -d '\r')
[ "$got" = "${{ steps.version.outputs.value }}" ] || {
echo "version mismatch: got '$got'"; exit 1; }
"$BIN" --help 2>&1 | grep -q "utmstack completion" || {
echo "help output is not branded"; exit 1; }
# First run must seed config and extract the embedded skills/agents.
TMPCFG=$(mktemp -d)
XDG_CONFIG_HOME="$TMPCFG" APPDATA="$TMPCFG" "$BIN" --help >/dev/null 2>&1 || true
XDG_CONFIG_HOME="$TMPCFG" APPDATA="$TMPCFG" "$BIN" mcp list >/dev/null 2>&1 || true
test -f "$TMPCFG/utmstack/utmstack.json" || {
echo "default config was not seeded"; exit 1; }
skills=$(ls "$TMPCFG/utmstack/skills" 2>/dev/null | wc -l | tr -d ' ')
agents=$(ls "$TMPCFG/utmstack/agent"/*.md 2>/dev/null | wc -l | tr -d ' ')
echo "seeded config, $skills skills, $agents agents"
[ "$skills" -gt 0 ] || { echo "no skills extracted"; exit 1; }
[ "$agents" -gt 0 ] || { echo "no agents extracted"; exit 1; }
grep -q '"utmstack-mcp"' "$TMPCFG/utmstack/utmstack.json" || {
echo "default config does not reference utmstack-mcp"; exit 1; }
- name: Package
shell: bash
working-directory: packages/opencode
run: |
set -eu
# Absolute output dir. A relative "../.." from inside dist/<name>/bin
# is five levels from the repo root and is trivially miscounted.
OUT="$GITHUB_WORKSPACE/out"
mkdir -p "$OUT"
for dir in dist/*/; do
name=$(basename "$dir")
[ -d "$dir/bin" ] || continue
# The build emits musl variants for free, but they are untested and
# unadvertised, and the installer refuses musl outright. Publishing
# them would promise support we have not verified.
case "$name" in *-musl*) echo "skipping $name (musl, not published)"; continue ;; esac
asset=$(echo "$name" | sed 's/^opencode-/utmstack-/')
if [ "${{ matrix.ext }}" = "tar.gz" ]; then
tar -czf "$OUT/${asset}.tar.gz" -C "$dir/bin" .
else
# COPYFILE_DISABLE keeps macOS AppleDouble "._*" files out of the
# zip; -X drops extended attributes. Both are no-ops on Windows.
(cd "$dir/bin" && env COPYFILE_DISABLE=1 7z a -tzip "$OUT/${asset}.zip" . >/dev/null)
fi
done
ls -lh "$OUT"
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
path: out/*
release:
needs: build
runs-on: ubuntu-22.04
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@v4
with: { path: artifacts }
- name: Collect and checksum
run: |
mkdir -p out
find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' \) -exec cp {} out/ \;
cd out && sha256sum * > checksums.txt
echo "Publishing:"; cat checksums.txt
- uses: softprops/action-gh-release@v2
with:
files: out/*
generate_release_notes: true