Files
2026-07-13 13:13:07 +08:00

461 lines
17 KiB
YAML

name: Release
on:
push:
tags: ['v*']
env:
CARGO_TERM_COLOR: always
jobs:
# ==========================================================================
# Run CI checks first
# ==========================================================================
ci:
uses: ./.github/workflows/ci.yml
# ==========================================================================
# Build agent binaries (both architectures)
# ==========================================================================
build-agent:
name: Build agent (${{ matrix.arch }})
needs: ci
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: x86_64
target: x86_64-unknown-linux-musl
linker_pkg: ""
linker_env: ""
- arch: aarch64
target: aarch64-unknown-linux-musl
linker_pkg: gcc-aarch64-linux-gnu
linker_env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: agent-${{ matrix.arch }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: agent-${{ matrix.arch }}-cargo-
- name: Install cross-compilation tools
if: matrix.linker_pkg != ''
run: |
sudo apt-get update
sudo apt-get install -y musl-tools ${{ matrix.linker_pkg }}
- name: Install musl-tools
if: matrix.linker_pkg == ''
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
- name: Build agent
run: |
${{ matrix.linker_env }} \
cargo build --profile release-small -p smolvm-agent --target ${{ matrix.target }}
- name: Verify static binary
run: file target/${{ matrix.target }}/release-small/smolvm-agent
- uses: actions/upload-artifact@v7
with:
name: agent-${{ matrix.arch }}
path: target/${{ matrix.target }}/release-small/smolvm-agent
# ==========================================================================
# Build agent rootfs (both architectures)
# ==========================================================================
build-rootfs:
name: Build rootfs (${{ matrix.arch }})
needs: build-agent
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: aarch64
- arch: x86_64
steps:
- uses: actions/checkout@v6
- name: Download agent binary
uses: actions/download-artifact@v8
with:
name: agent-${{ matrix.arch }}
path: /tmp/agent/
- name: Build rootfs
run: |
chmod +x /tmp/agent/smolvm-agent
AGENT_BINARY=/tmp/agent/smolvm-agent \
./scripts/build-agent-rootfs.sh --arch ${{ matrix.arch }} --no-build-agent
- name: Package rootfs as tarball
run: |
# Tar preserves ownership/permissions without needing read access
# to restricted dirs like /var/run/chrony (owned by chrony:chrony 700)
sudo tar -cf target/agent-rootfs.tar -C target/agent-rootfs .
- uses: actions/upload-artifact@v7
with:
name: rootfs-${{ matrix.arch }}
path: target/agent-rootfs.tar
# ==========================================================================
# Build distribution tarballs (per platform)
# ==========================================================================
build-dist:
name: Build dist (${{ matrix.platform }})
needs: [build-agent, build-rootfs]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- platform: darwin-arm64
runner: macos-14
rootfs_arch: aarch64
agent_arch: aarch64
lib_dir: lib
# Linux dist binaries build on 22.04 (glibc 2.35), NOT ubuntu-latest
# (24.04 = glibc 2.39). Symbol versioning is backward-compatible only,
# so the build host's glibc is the MINIMUM a user needs at runtime —
# a 2.39-built binary dies on 22.04 with "GLIBC_2.39 not found". 2.35
# covers Ubuntu 22.04+ / Debian 12+ / RHEL 9+.
- platform: linux-x86_64
runner: ubuntu-22.04
rootfs_arch: x86_64
agent_arch: x86_64
lib_dir: lib/linux-x86_64
- platform: linux-arm64
runner: ubuntu-22.04-arm
rootfs_arch: aarch64
agent_arch: aarch64
lib_dir: lib/linux-aarch64
steps:
- uses: actions/checkout@v6
with:
lfs: true
submodules: ${{ startsWith(matrix.platform, 'linux-') && 'recursive' || 'false' }}
- name: Pull LFS files
run: git lfs pull
- uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
# Include lib dir hash so libkrun updates bust the cache
key: dist-${{ matrix.platform }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles(format('{0}/*', matrix.lib_dir)) }}
restore-keys: dist-${{ matrix.platform }}-cargo-
# Force relink if cached binary was linked against a different libkrun
- name: Clean stale build artifacts
run: rm -f target/release/smolvm target/release/deps/smolvm-*
- name: Install build dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
# patchelf: build-dist.sh strips libkrun's hard libvirglrenderer NEEDED
# so non-GPU Linux hosts can dlopen it (GPU stays optional at runtime).
# Without patchelf the strip is silently skipped and `smolvm run` fails
# with "libvirglrenderer.so.1: cannot open shared object file".
sudo apt-get install -y build-essential libssl-dev pkg-config e2fsprogs patchelf
- name: Install build dependencies (macOS)
if: runner.os == 'macOS'
run: brew install e2fsprogs
- name: Verify LFS libraries
run: |
echo "Library directory: ${{ matrix.lib_dir }}"
ls -la ${{ matrix.lib_dir }}/
file ${{ matrix.lib_dir }}/*
- name: Download rootfs
uses: actions/download-artifact@v8
with:
name: rootfs-${{ matrix.rootfs_arch }}
path: /tmp/rootfs-dl/
- name: Extract rootfs tarball
run: |
mkdir -p target/agent-rootfs
sudo tar -xf /tmp/rootfs-dl/agent-rootfs.tar -C target/agent-rootfs
- name: Download agent binary
uses: actions/download-artifact@v8
with:
name: agent-${{ matrix.agent_arch }}
path: /tmp/agent/
- name: Place agent binary for build-dist.sh
run: |
mkdir -p target/release-small
cp /tmp/agent/smolvm-agent target/release-small/smolvm-agent
chmod +x target/release-small/smolvm-agent
# Import Developer ID certificate if available (macOS only).
# Set secrets APPLE_CERTIFICATE_P12 (base64-encoded .p12) and
# APPLE_CERTIFICATE_PASSWORD in your GitHub repo settings.
- name: Import signing certificate (macOS)
if: runner.os == 'macOS' && env.APPLE_CERTIFICATE_P12 != ''
env:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
echo "$APPLE_CERTIFICATE_P12" | base64 --decode > /tmp/certificate.p12
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security import /tmp/certificate.p12 -k build.keychain \
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: \
-s -k "$KEYCHAIN_PASSWORD" build.keychain
rm /tmp/certificate.p12
# Check out the unified `smol` CLI (a separate repo) into ./smol so
# build-dist.sh builds and bundles it alongside the engine — one tarball
# serves both `smol` and `smolvm`. Requires repo secret SMOL_REPO_TOKEN
# with read access to smol-machines/smol. If unset, the step is skipped and
# an engine-only tarball is produced. Ref is left at the smol default
# branch (its versioning is independent of the engine tag); pin a ref here
# if you want reproducible smol-CLI releases.
- name: Check out smol CLI source
if: env.SMOL_REPO_TOKEN != ''
uses: actions/checkout@v6
with:
repository: smol-machines/smol
path: smol
token: ${{ secrets.SMOL_REPO_TOKEN }}
env:
SMOL_REPO_TOKEN: ${{ secrets.SMOL_REPO_TOKEN }}
# No separate guest-init build: libkrun 2.0.0 embeds the guest init
# (krun-init) directly in libkrun.so (INIT_BLOB is on by default), so the
# old per-arch `cc init/init.c` step is gone — init.c no longer exists in
# the 2.0.0 submodule, and a VM boots from the embedded init with no
# external init.krun (verified). build-dist.sh no longer bundles init.krun.
- name: Build distribution tarball
run: ./scripts/build-dist.sh --skip-agent-build
env:
LIB_DIR: ${{ matrix.lib_dir }}
CODESIGN_IDENTITY: ${{ secrets.APPLE_CERTIFICATE_P12 != '' && secrets.CODESIGN_IDENTITY || '-' }}
# Notarize the macOS binary if Developer ID signing was used.
# Requires secrets: APPLE_ID, APPLE_TEAM_ID, APPLE_APP_PASSWORD.
- name: Notarize (macOS)
if: runner.os == 'macOS' && env.APPLE_CERTIFICATE_P12 != ''
env:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
run: |
TARBALL=$(ls dist/smolvm-*.tar.gz)
xcrun notarytool submit "$TARBALL" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_PASSWORD" \
--wait
- name: Verify bundled libraries match source
run: |
VERSION="$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2)"
DIST_LIB="dist/smolvm-${VERSION}-${{ matrix.platform }}/lib"
echo "Source libraries:"
ls -la ${{ matrix.lib_dir }}/
echo "Bundled libraries:"
ls -la "$DIST_LIB"/
# Verify sizes match for all bundled libraries (catches stale LFS or copy issues).
# Covers both required libs (libkrun, libkrunfw) and optional GPU libs.
for lib in "$DIST_LIB"/lib*; do
[[ -L "$lib" ]] && continue # skip symlinks
name=$(basename "$lib")
src="${{ matrix.lib_dir }}/$name"
if [[ -f "$src" ]]; then
src_size=$(wc -c < "$src")
dst_size=$(wc -c < "$lib")
if [[ "$src_size" != "$dst_size" ]]; then
echo "ERROR: $name size mismatch: source=$src_size bundled=$dst_size"
exit 1
fi
echo "OK: $name ($dst_size bytes)"
fi
done
- name: List distribution contents
run: |
VERSION="$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2)"
ls -la "dist/smolvm-${VERSION}-"*/
- uses: actions/upload-artifact@v7
with:
name: dist-${{ matrix.platform }}
path: dist/smolvm-*.tar.gz
# ==========================================================================
# Build Windows distribution (cross-compiled; zipped)
# ==========================================================================
build-dist-windows:
name: Build dist (windows-x86_64)
needs: [build-agent, build-rootfs]
runs-on: ubuntu-latest
env:
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
steps:
- uses: actions/checkout@v6
with:
lfs: true
- name: Pull LFS files
run: git lfs pull
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-gnu
- name: Install build dependencies
run: |
sudo apt-get update
# mingw: cross-link smolvm.exe; clang/libclang: bindgen in build deps
# (matches the CI windows job); e2fsprogs: pre-format disk templates
# (Windows has no host mkfs.ext4); zip: package the dist.
sudo apt-get install -y gcc-mingw-w64-x86-64 clang libclang-dev e2fsprogs zip
- name: Verify Windows libraries
run: |
ls -la lib/windows-x86_64/
file lib/windows-x86_64/*.dll
- name: Download rootfs (x86_64)
uses: actions/download-artifact@v8
with:
name: rootfs-x86_64
path: /tmp/rootfs-dl/
- name: Extract rootfs
run: |
mkdir -p target/agent-rootfs
sudo tar -xf /tmp/rootfs-dl/agent-rootfs.tar -C target/agent-rootfs
- name: Download agent binary (x86_64)
uses: actions/download-artifact@v8
with:
name: agent-x86_64
path: /tmp/agent/
# The rootfs artifact ships agent-less; inject the freshly built agent and
# repoint /sbin/init at it (same as the Unix build-dist.sh path).
- name: Inject agent into rootfs
run: |
sudo cp /tmp/agent/smolvm-agent target/agent-rootfs/usr/local/bin/smolvm-agent
sudo chmod +x target/agent-rootfs/usr/local/bin/smolvm-agent
sudo ln -sf /usr/local/bin/smolvm-agent target/agent-rootfs/sbin/init
- name: Build Windows distribution
run: ./scripts/build-dist-windows.sh
env:
LIB_DIR: lib/windows-x86_64
- uses: actions/upload-artifact@v7
with:
name: dist-windows-x86_64
path: dist/smolvm-*-windows-x86_64.zip
# ==========================================================================
# Create GitHub Release
# ==========================================================================
release:
name: Create Release
needs: [build-dist, build-dist-windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Download all dist artifacts
uses: actions/download-artifact@v8
with:
pattern: dist-*
path: dist/
merge-multiple: true
- name: List release artifacts
run: ls -la dist/
- name: Generate checksums
run: |
cd dist
sha256sum *.tar.gz *.zip > checksums.sha256
cat checksums.sha256
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}" \
--title "smolvm ${{ github.ref_name }}" \
--generate-notes \
dist/*.tar.gz \
dist/*.zip \
dist/checksums.sha256
# Ping the Homebrew tap to bump its formula to this release immediately,
# instead of waiting for its scheduled poll. Best-effort: if the token
# secret isn't configured, this no-ops (the tap's cron picks the release up
# within a few hours anyway) and never fails the release. The token is a
# fine-grained PAT with Contents:write on smol-machines/homebrew-tap.
- name: Notify Homebrew tap of the new release
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_DISPATCH_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
if [ -z "${GH_TOKEN:-}" ]; then
echo "HOMEBREW_TAP_DISPATCH_TOKEN not set — skipping instant bump (the tap polls on a schedule)."
exit 0
fi
version="${TAG#v}"
gh api --method POST repos/smol-machines/homebrew-tap/dispatches \
-f event_type=smolvm-release \
-f "client_payload[version]=$version"
echo "Dispatched smolvm-release ($version) to the Homebrew tap."
# Build + publish the Arch/pacman packages for this release. Invoked directly
# (not via the `release: published` event) because that event never fires for a
# release created with the default GITHUB_TOKEN — the same GitHub restriction
# the Homebrew step above works around. Runs after the release exists so the
# PKGBUILD can download its release binaries.
publish-pacman:
name: Publish pacman repo
needs: release
permissions:
contents: write
uses: ./.github/workflows/pacman-repo.yml
with:
tag: ${{ github.ref_name }}