Skip to content

Release

Release #6

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g. 0.3.14-pspdfkit.1)"
required: true
env:
CARGO_TERM_COLOR: always
LIBKRUNFW_VERSION: "5.2.1"
LIBKRUNFW_ABI: "5"
permissions:
contents: write
packages: write
jobs:
# ---------------------------------------------------------------------------
# Build kernel.c on Linux for macOS libkrunfw linking
# ---------------------------------------------------------------------------
build-kernel:
name: Build kernel.c (aarch64)
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Cache kernel.c
id: cache-kernel
uses: actions/cache@v4
with:
path: vendor/libkrunfw/kernel.c
key: kernel-c-aarch64-${{ hashFiles('vendor/libkrunfw/**') }}
- name: Install kernel build deps
if: steps.cache-kernel.outputs.cache-hit != 'true'
run: sudo apt-get update && sudo apt-get install -y libcap-ng-dev gcc make flex bison libelf-dev bc python3-pyelftools
- name: Build kernel.c
if: steps.cache-kernel.outputs.cache-hit != 'true'
run: |
cd vendor/libkrunfw
make -j$(nproc)
- name: Upload kernel.c
uses: actions/upload-artifact@v4
with:
name: kernel-c-aarch64
path: vendor/libkrunfw/kernel.c
# ---------------------------------------------------------------------------
# Build agentd on Linux for macOS packaging
# ---------------------------------------------------------------------------
build-agentd-aarch64:
name: Build agentd (aarch64-linux-musl)
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install agentd build deps
run: sudo apt-get update && sudo apt-get install -y musl-tools
- name: Build agentd
run: |
rustup target add aarch64-unknown-linux-musl
cargo build --release --manifest-path crates/agentd/Cargo.toml --target aarch64-unknown-linux-musl
mkdir -p build
cp crates/agentd/target/aarch64-unknown-linux-musl/release/agentd build/agentd
- name: Upload agentd
uses: actions/upload-artifact@v4
with:
name: agentd-aarch64-linux-musl
path: build/agentd
# ---------------------------------------------------------------------------
# Build
# ---------------------------------------------------------------------------
build:
name: Build (${{ matrix.target }})
needs: [build-kernel, build-agentd-aarch64]
if: always()
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: linux-x86_64
runner: ubuntu-latest
arch: x86_64
os: linux
agentd_target: x86_64-unknown-linux-musl
libkrunfw_file: libkrunfw.so.5.2.1
libkrunfw_asset: libkrunfw-linux-x86_64.so
napi_target: x86_64-unknown-linux-gnu
node_file: microsandbox.linux-x64-gnu.node
npm_dir: linux-x64-gnu
- target: linux-aarch64
runner: ubuntu-24.04-arm
arch: aarch64
os: linux
agentd_target: aarch64-unknown-linux-musl
libkrunfw_file: libkrunfw.so.5.2.1
libkrunfw_asset: libkrunfw-linux-aarch64.so
napi_target: aarch64-unknown-linux-gnu
node_file: microsandbox.linux-arm64-gnu.node
npm_dir: linux-arm64-gnu
- target: darwin-aarch64
runner: macos-14
arch: aarch64
os: darwin
agentd_target: ""
libkrunfw_file: libkrunfw.5.dylib
libkrunfw_asset: libkrunfw-darwin-aarch64.dylib
napi_target: aarch64-apple-darwin
node_file: microsandbox.darwin-arm64.node
npm_dir: darwin-arm64
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
# -- Linux build deps --
- name: Install build deps (Linux)
if: matrix.os == 'linux'
run: sudo apt-get update && sudo apt-get install -y musl-tools libcap-ng-dev gcc make flex bison libelf-dev bc python3-pyelftools
# -- agentd (Linux: native musl) --
- name: Build agentd (musl)
if: matrix.os == 'linux'
run: |
rustup target add ${{ matrix.agentd_target }}
cargo build --release --manifest-path crates/agentd/Cargo.toml --target ${{ matrix.agentd_target }}
mkdir -p build
cp crates/agentd/target/${{ matrix.agentd_target }}/release/agentd build/agentd
# -- agentd (macOS: download prebuilt Linux artifact) --
- name: Download agentd (macOS)
if: matrix.os == 'darwin'
uses: actions/download-artifact@v4
with:
name: agentd-aarch64-linux-musl
path: build/
# -- libkrunfw (cached) --
- name: Cache libkrunfw
id: cache-libkrunfw
uses: actions/cache@v4
with:
path: build/libkrunfw*
key: libkrunfw-${{ matrix.target }}-${{ hashFiles('vendor/libkrunfw/**') }}
- name: Build libkrunfw (Linux)
if: steps.cache-libkrunfw.outputs.cache-hit != 'true' && matrix.os == 'linux'
run: |
cd vendor/libkrunfw
make -j$(nproc)
cd ../..
mkdir -p build
cp vendor/libkrunfw/libkrunfw.so.${{ env.LIBKRUNFW_VERSION }} build/
cd build
ln -sf libkrunfw.so.${{ env.LIBKRUNFW_VERSION }} libkrunfw.so.${{ env.LIBKRUNFW_ABI }}
ln -sf libkrunfw.so.${{ env.LIBKRUNFW_ABI }} libkrunfw.so
- name: Download kernel.c (macOS)
if: steps.cache-libkrunfw.outputs.cache-hit != 'true' && matrix.os == 'darwin'
uses: actions/download-artifact@v4
with:
name: kernel-c-aarch64
path: vendor/libkrunfw/
- name: Build libkrunfw (macOS)
if: steps.cache-libkrunfw.outputs.cache-hit != 'true' && matrix.os == 'darwin'
run: |
cd vendor/libkrunfw
cc -fPIC -DABI_VERSION=${{ env.LIBKRUNFW_ABI }} -shared -o libkrunfw.${{ env.LIBKRUNFW_ABI }}.dylib kernel.c
cd ../..
mkdir -p build
cp vendor/libkrunfw/libkrunfw.${{ env.LIBKRUNFW_ABI }}.dylib build/
cd build
ln -sf libkrunfw.${{ env.LIBKRUNFW_ABI }}.dylib libkrunfw.dylib
# -- msb --
- name: Build msb
run: |
cargo build --release --no-default-features --features net -p microsandbox-cli
mkdir -p build
cp target/release/msb build/msb
# -- macOS codesign --
- name: Codesign msb
if: matrix.os == 'darwin'
run: codesign --entitlements msb-entitlements.plist --force -s - build/msb
# -- Node SDK --
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Build Node SDK
working-directory: sdk/node-ts
run: |
npm ci
npx napi build --release --platform --js index.cjs --dts index.d.cts --target ${{ matrix.napi_target }}
npx tsc
- name: Upload Node SDK artifacts
uses: actions/upload-artifact@v4
with:
name: node-sdk-${{ matrix.npm_dir }}
path: |
sdk/node-ts/${{ matrix.node_file }}
sdk/node-ts/index.cjs
sdk/node-ts/index.d.cts
sdk/node-ts/dist/
# -- Python SDK --
- uses: astral-sh/setup-uv@v3
with:
enable-cache: true
cache-dependency-glob: "sdk/python/uv.lock"
- name: Stage runtime bundle (Python SDK)
run: |
mkdir -p sdk/python/microsandbox/_bundled/bin
mkdir -p sdk/python/microsandbox/_bundled/lib
cp build/msb sdk/python/microsandbox/_bundled/bin/
cp build/${{ matrix.libkrunfw_file }} sdk/python/microsandbox/_bundled/lib/
cd sdk/python/microsandbox/_bundled/lib
if [ "${{ matrix.os }}" = "darwin" ]; then
ln -sf ${{ matrix.libkrunfw_file }} libkrunfw.dylib
else
ln -sf ${{ matrix.libkrunfw_file }} libkrunfw.so.${{ env.LIBKRUNFW_ABI }}
ln -sf libkrunfw.so.${{ env.LIBKRUNFW_ABI }} libkrunfw.so
fi
- name: Build Python wheel
uses: PyO3/maturin-action@v1
with:
working-directory: sdk/python
command: build
args: --release --out dist
manylinux: ${{ matrix.os == 'linux' && '2_28' || 'off' }}
before-script-linux: dnf install -y libcap-ng-devel
- name: Upload Python SDK wheel
uses: actions/upload-artifact@v4
with:
name: python-sdk-${{ matrix.target }}
path: sdk/python/dist/*.whl
# -- Stage release artifacts --
- name: Stage artifacts
run: |
mkdir -p artifacts
# Standalone msb
cp build/msb artifacts/msb-${{ matrix.target }}
# Standalone libkrunfw
cp build/${{ matrix.libkrunfw_file }} artifacts/${{ matrix.libkrunfw_asset }}
# Standalone agentd (Linux only — guest binary)
if [ "${{ matrix.os }}" = "linux" ]; then
cp build/agentd artifacts/agentd-${{ matrix.arch }}
fi
# Bundle: msb + libkrunfw
tar -czf artifacts/microsandbox-${{ matrix.target }}.tar.gz \
-C build msb ${{ matrix.libkrunfw_file }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.target }}
path: artifacts/
# ---------------------------------------------------------------------------
# Assemble: collect all artifacts, generate checksums, create GitHub release
# ---------------------------------------------------------------------------
assemble:
name: Assemble Release
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: release-artifacts
pattern: release-*
merge-multiple: true
- name: Copy install script
if: hashFiles('scripts/install.sh') != ''
run: cp scripts/install.sh release-artifacts/install.sh
- name: Generate checksums
working-directory: release-artifacts
run: sha256sum * > checksums.sha256
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "v${{ inputs.version }}" \
--title "v${{ inputs.version }}" \
--generate-notes \
release-artifacts/*
# ---------------------------------------------------------------------------
# Upload Node SDK artifacts to GitHub Release
# ---------------------------------------------------------------------------
node-sdk-release:
name: Upload Node SDK to Release
needs: assemble
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Download Node SDK artifacts
uses: actions/download-artifact@v4
with:
path: node-artifacts
pattern: node-sdk-*
- name: Package platform tarballs
run: |
mkdir -p node-release
VERSION="${{ inputs.version }}"
RELEASE_BASE="https://github.com/${{ github.repository }}/releases/download/v${VERSION}"
# --- Platform packages (one per target) ---
# These contain only the native .node binary + a package.json with os/cpu
# fields. npm's optionalDependencies resolution will skip non-matching
# platforms automatically.
for dir in darwin-arm64 linux-x64-gnu linux-arm64-gnu; do
pkg_dir="sdk/node-ts/npm/${dir}"
# Patch version into platform package.json
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('${pkg_dir}/package.json', 'utf8'));
pkg.version = '${VERSION}';
fs.writeFileSync('${pkg_dir}/package.json', JSON.stringify(pkg, null, 2));
"
# Copy the .node binary into the platform package
cp node-artifacts/node-sdk-${dir}/microsandbox.*.node "${pkg_dir}/"
# Pack the platform package
npm pack --pack-destination node-release "./${pkg_dir}"
# npm pack produces <scope>-<name>-<version>.tgz; rename to a
# predictable name so the main SDK can reference it.
mv "node-release/superradcompany-microsandbox-${dir}-${VERSION}.tgz" \
"node-release/microsandbox-native-${dir}.tgz"
# Clean up for next iteration
rm -f ${pkg_dir}/microsandbox.*.node
git checkout "${pkg_dir}/package.json"
done
# --- Main SDK package (platform-independent, no .node files) ---
# Copy tsc output and generated JS (same across all builds)
cp -r node-artifacts/node-sdk-darwin-arm64/dist sdk/node-ts/
cp node-artifacts/node-sdk-darwin-arm64/index.cjs sdk/node-ts/
cp node-artifacts/node-sdk-darwin-arm64/index.d.cts sdk/node-ts/
# Patch version and optionalDependencies to point to platform tarballs
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('sdk/node-ts/package.json', 'utf8'));
pkg.version = '${VERSION}';
pkg.optionalDependencies = {
'@superradcompany/microsandbox-darwin-arm64': '${RELEASE_BASE}/microsandbox-native-darwin-arm64.tgz',
'@superradcompany/microsandbox-linux-x64-gnu': '${RELEASE_BASE}/microsandbox-native-linux-x64-gnu.tgz',
'@superradcompany/microsandbox-linux-arm64-gnu': '${RELEASE_BASE}/microsandbox-native-linux-arm64-gnu.tgz',
};
fs.writeFileSync('sdk/node-ts/package.json', JSON.stringify(pkg, null, 2));
"
npm pack --pack-destination node-release ./sdk/node-ts
mv "node-release/microsandbox-${VERSION}.tgz" "node-release/microsandbox-node-sdk.tgz"
git checkout sdk/node-ts/package.json
- name: Upload to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "v${{ inputs.version }}" node-release/*.tgz
# ---------------------------------------------------------------------------
# Upload Python SDK wheels to GitHub Release
# ---------------------------------------------------------------------------
python-sdk-release:
name: Upload Python wheels to Release
needs: assemble
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download Python SDK wheels
uses: actions/download-artifact@v4
with:
path: dist
pattern: python-sdk-*
merge-multiple: true
- name: List wheels
run: ls -lh dist/
- name: Upload to GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "v${{ inputs.version }}" dist/*.whl
# ---------------------------------------------------------------------------
# Publish Docker image to GHCR
# ---------------------------------------------------------------------------
# Build per-arch Docker images and push digests
# ---------------------------------------------------------------------------
docker-build:
name: Docker (${{ matrix.arch }})
needs: build
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-latest
artifact: release-linux-x86_64
msb_asset: msb-linux-x86_64
libkrunfw_asset: libkrunfw-linux-x86_64.so
- arch: arm64
runner: ubuntu-24.04-arm
artifact: release-linux-aarch64
msb_asset: msb-linux-aarch64
libkrunfw_asset: libkrunfw-linux-aarch64.so
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ${{ matrix.artifact }}
path: release-artifacts
- name: Stage binaries
run: |
mkdir -p packaging/docker/build/${{ matrix.arch }}
cp release-artifacts/${{ matrix.msb_asset }} packaging/docker/build/${{ matrix.arch }}/msb
cp release-artifacts/${{ matrix.libkrunfw_asset }} packaging/docker/build/${{ matrix.arch }}/libkrunfw.so.${{ env.LIBKRUNFW_VERSION }}
chmod +x packaging/docker/build/${{ matrix.arch }}/msb
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
id: push
with:
context: packaging/docker
platforms: linux/${{ matrix.arch }}
cache-from: type=gha,scope=${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
outputs: type=image,name=ghcr.io/pspdfkit-labs/microsandbox,push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
echo "${{ steps.push.outputs.digest }}" > /tmp/digests/${{ matrix.arch }}.txt
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: docker-digest-${{ matrix.arch }}
path: /tmp/digests/${{ matrix.arch }}.txt
# ---------------------------------------------------------------------------
# Combine per-arch images into a multi-arch manifest
# ---------------------------------------------------------------------------
docker-manifest:
name: Docker manifest
needs: docker-build
runs-on: ubuntu-latest
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: digests
pattern: docker-digest-*
merge-multiple: true
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/pspdfkit-labs/microsandbox
tags: |
type=raw,value=${{ inputs.version }}
type=raw,value=latest
- name: Create multi-arch manifest
run: |
AMD64_DIGEST=$(cat digests/amd64.txt)
ARM64_DIGEST=$(cat digests/arm64.txt)
IMAGE=ghcr.io/pspdfkit-labs/microsandbox
for TAG in $(echo "${{ steps.meta.outputs.tags }}"); do
docker buildx imagetools create -t "$TAG" \
"${IMAGE}@${AMD64_DIGEST}" \
"${IMAGE}@${ARM64_DIGEST}"
done