8000 fix(release): use built minimald when packing initramfs by twitchyliquid64 · Pull Request #623 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content
Merged
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
124 changes: 75 additions & 49 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ jobs:
# native glibc build against a real libkrun. Materialize libkrun (a cache
# fetch keyed by the pinned upstream commit, NOT a from-source build) into
# a flat prefix that minvmd's build.rs link-searches + rpaths against.
#
# Reuse the static mip built above (MIP=) instead of letting the script
# compile a second, debug mip just to drive this cache fetch.
env:
MIP: ${{ github.workspace }}/target/x86_64-unknown-linux-musl/release/mip-linux-amd64
run: ./scripts/fetch-libkrun.sh "$HOME/.krun" x86_64
- name: Build native minvmd binary
# Native host target (x86_64-unknown-linux-gnu), NOT musl: the binary
Expand Down Expand Up @@ -244,34 +249,25 @@ jobs:
path: target/release/minimal-macos-arm64
retention-days: 7

build-release-guest-artifacts:
# Guest microVM artifacts shipped in the release: the raw guest kernel
# Image, the ext4 rootfs image, the initramfs (minimald as pid-1 /init),
# and the pinned gvproxy switch binary. These are already produced
# piecemeal in the CI lanes (ci-macos.yml, ci-linux-kvm.yml,
# ci-gvproxy.yml); this job gathers them into the release.
fetch-release-guest-artifacts:
# Fetched guest microVM artifacts shipped in the release: the raw guest
# kernel Image, the ext4 rootfs image, and the pinned gvproxy switch
# binary. These are already produced piecemeal in the CI lanes
# (ci-macos.yml, ci-linux-kvm.yml, ci-gvproxy.yml); this job gathers them
# into the release. The initramfs is packed separately in
# build-release-initramfs, which reuses the minimald-linux-* binaries.
#
# None require the target host's arch to produce: the kernel and rootfs are
# cache pulls keyed by the pinned upstream commit (fetch-artifact.sh pulls
# any guest arch on an x86_64 runner), the initramfs cross-compiles minimald
# to static musl for the target arch, and gvproxy is a pin-verified download
# of the upstream release binary. So a single x86_64 runner emits both the
# amd64 and arm64 variants, covering every release host
# any guest arch on an x86_64 runner), and gvproxy is a pin-verified
# download of the upstream release binary. So a single x86_64 runner emits
# both the amd64 and arm64 variants, covering every release host
# (linux-amd64/arm64, macos-arm64). Files are arch-suffixed so
# download-artifact's merge-multiple basename-flatten in the release job
# doesn't collide.
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 35
steps:
- name: Free Disk Space
# Two cross (Docker) initramfs builds plus the CLI build that drives
# the rootfs cache pulls; reclaim the tool cache for headroom.
uses: endersonmenezes/free-disk-space@v3 # Use @main for latest, @v3 for stable
with:
remove_android: true
remove_dotnet: true
remove_haskell: true
remove_tool_cache: true
- uses: actions/checkout@v7
with:
persist-credentials: false
Expand All @@ -281,15 +277,11 @@ jobs:
# protoc + libprotobuf-dev: the `mip` CLI build that drives the rootfs
# cache pull (the well-known protos live in libprotobuf-dev, only a
# `recommends` of protobuf-compiler, so name it under
# --no-install-recommends). cpio: packs the initramfs.
# --no-install-recommends).
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
protobuf-compiler libprotobuf-dev cpio
- name: Install cross
uses: taiki-e/install-action@v2
with:
tool: cross
protobuf-compiler libprotobuf-dev
- uses: actions/cache@v6
with:
path: |
Expand All @@ -300,10 +292,9 @@ jobs:
target/
key: ${{ runner.os }}-guest-artifacts-release-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-guest-artifacts-release-
# NOTE: fetch-artifact.sh takes the kernel-style arch (x86_64/aarch64)
# and build-initramfs.sh takes a rust target triple; only the emitted
# file/artifact names use the amd64/arm64 suffixes the release binaries
# already use.
# NOTE: fetch-artifact.sh takes the kernel-style arch (x86_64/aarch64);
# only the emitted file/artifact names use the amd64/arm64 suffixes the
# release binaries already use.
- name: Materialize guest kernel (raw Image, amd64)
run: ./scripts/fetch-artifact.sh virtio-kernel "$RUNNER_TEMP/vmlinuz-amd64" x86_64
- name: Materialize guest kernel (raw Image, arm64)
Expand All @@ -312,10 +303,6 @@ jobs:
run: ./scripts/fetch-artifact.sh minvmd-rootfs "$RUNNER_TEMP/rootfs-amd64.img" x86_64
- name: Materialize guest rootfs (ext4 image, arm64)
run: ./scripts/fetch-artifact.sh minvmd-rootfs "$RUNNER_TEMP/rootfs-arm64.img" aarch64
- name: Build guest initramfs (amd64)
run: ./scripts/build-initramfs.sh "$RUNNER_TEMP/initramfs-amd64.cpio" x86_64-unknown-linux-musl
- name: Build guest initramfs (arm64)
run: ./scripts/build-initramfs.sh "$RUNNER_TEMP/initramfs-arm64.cpio" aarch64-unknown-linux-musl
- name: Fetch pinned gvproxy (linux-amd64)
run: ./scripts/fetch-gvproxy.sh "$RUNNER_TEMP/gvproxy-linux-amd64" gvproxy-linux-amd64
- name: Fetch pinned gvproxy (linux-arm64)
Expand Down Expand Up @@ -350,20 +337,6 @@ jobs:
path: ${{ runner.temp }}/rootfs-arm64.img
if-no-files-found: error
retention-days: 7
- name: Upload initramfs (amd64)
uses: actions/upload-artifact@v7
with:
name: minimald-initramfs-amd64
path: ${{ runner.temp }}/initramfs-amd64.cpio
if-no-files-found: error
retention-days: 7
- name: Upload initramfs (arm64)
uses: actions/upload-artifact@v7
with:
name: minimald-initramfs-arm64
path: ${{ runner.temp }}/initramfs-arm64.cpio
if-no-files-found: error
retention-days: 7
- name: Upload gvproxy (linux-amd64)
uses: actions/upload-artifact@v7
with:
Expand All @@ -386,6 +359,58 @@ jobs:
if-no-files-found: error
retention-days: 7

build-release-initramfs:
# Pack the guest initramfs (minimald as pid-1 /init) from the SAME static
# musl minimald binaries the build-release-linux-* jobs already built and
# ship, instead of recompiling minimald just for the initramfs. Downloads
# those artifacts and stamps each into a newc cpio via build-initramfs.sh's
# MINIMALD_BIN mode (no cargo/cross toolchain needed — just cpio). Files are
# arch-suffixed to match the amd64/arm64 naming and avoid a
# merge-multiple basename collision in the release job.
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [build-release-linux-amd64, build-release-linux-arm64]
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Install cpio
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cpio
- name: Download minimald (amd64)
uses: actions/download-artifact@v8
with:
name: minimald-linux-amd64
path: prebuilt
- name: Download minimald (arm64)
uses: actions/download-artifact@v8
with:
name: minimald-linux-arm64
path: prebuilt
- name: Pack initramfs (amd64)
run: |
chmod +x prebuilt/minimald-linux-amd64
MINIMALD_BIN="$PWD/prebuilt/minimald-linux-amd64" \
./scripts/build-initramfs.sh "$RUNNER_TEMP/initramfs-amd64.cpio"
- name: Pack initramfs (arm64)
run: |
chmod +x prebuilt/minimald-linux-arm64
MINIMALD_BIN="$PWD/prebuilt/minimald-linux-arm64" \
./scripts/build-initramfs.sh "$RUNNER_TEMP/initramfs-arm64.cpio"
- name: Upload initramfs (amd64)
uses: actions/upload-artifact@v7
with:
name: minimald-initramfs-amd64
path: ${{ runner.temp }}/initramfs-amd64.cpio
if-no-files-found: error
retention-days: 7
- name: Upload initramfs (arm64)
uses: actions/upload-artifact@v7
with:
name: minimald-initramfs-arm64
path: ${{ runner.temp }}/initramfs-arm64.cpio
if-no-files-found: error
retention-days: 7

release:
runs-on: ubuntu-latest
timeout-minutes: 30
Expand All @@ -395,7 +420,8 @@ jobs:
build-release-linux-amd64,
build-release-linux-arm64,
build-release-macos-arm64,
build-release-guest-artifacts,
fetch-release-guest-artifacts,
build-release-initramfs,
]
permissions:
contents: write
Expand Down
86 changes: 50 additions & 36 deletions scripts/build-initramfs.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
#!/bin/sh
# Build the Stage-2 guest initramfs: cross-compile minimald to a static aarch64
# binary and pack it as the initramfs `/init` in a newc cpio. The kernel runs
# `/init` (= minimald) as pid-1; minimald mounts the generic guest rootfs
# (/dev/vda) and chroots into it. No minimald is baked into the rootfs.
# Build the Stage-2 guest initramfs: obtain a static minimald binary and pack it
# as the initramfs `/init` in a newc cpio. The kernel runs `/init` (= minimald)
# as pid-1; minimald mounts the generic guest rootfs (/dev/vda) and chroots into
# it. No minimald is baked into the rootfs.
#
# Toolchain selection (auto-detected): when the host arch matches the target
# arch and a native static-musl toolchain is present (the `*-linux-musl` rustup
# target plus a matching `*-linux-musl-gcc` linker), build natively — same-arch
# musl needs no container. Otherwise fall back to `cross` (Docker) for the musl
# toolchain. Set FORCE_CROSS=1 to always use `cross`.
# Set MINIMALD_BIN to an existing static minimald binary to pack it directly and
# skip compilation entirely (TARGET/FEATURES are ignored in this mode). The
# release pipeline uses this to reuse the exact minimald-linux-* binary it ships,
# instead of recompiling a separate one just for the initramfs.
#
# Otherwise minimald is compiled here. Toolchain selection (auto-detected): when
# the host arch matches the target arch and a native static-musl toolchain is
# present (the `*-linux-musl` rustup target plus a matching `*-linux-musl-gcc`
# linker), build natively — same-arch musl needs no container. Otherwise fall
# back to `cross` (Docker) for the musl toolchain. Set FORCE_CROSS=1 to always
# use `cross`.
#
# Set FEATURES to a comma-separated cargo feature list to compile the guest
# minimald with extra features (e.g. FEATURES=networking-proxy,networking-wg for
Expand All @@ -20,40 +26,48 @@ set -eu
DEST="${1:?usage: build-initramfs.sh <dest-cpio> [rust-target]}"
TARGET="${2:-aarch64-unknown-linux-musl}"
FEATURES="${FEATURES:-}"
MINIMALD_BIN="${MINIMALD_BIN:-}"

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

# Compile flags shared by both toolchains. The `initramfs` profile drops the
# release LTO + codegen-units=1 (which the guest binary doesn't need) to compile
# much faster.
set -- build -p minimald --profile initramfs --target "$TARGET"
[ -n "$FEATURES" ] && set -- "$@" --features "$FEATURES"
if [ -n "$MINIMALD_BIN" ]; then
# Prebuilt-binary mode: pack the supplied minimald as /init, no compilation.
[ -x "$MINIMALD_BIN" ] || { echo "MINIMALD_BIN not an executable file: $MINIMALD_BIN" >&2; exit 1; }
BIN="$MINIMALD_BIN"
echo "build-initramfs: packing prebuilt minimald ($BIN)" >&2
else
# Compile flags shared by both toolchains. The `initramfs` profile drops the
# release LTO + codegen-units=1 (which the guest binary doesn't need) to
# compile much faster.
set -- build -p minimald --profile initramfs --target "$TARGET"
[ -n "$FEATURES" ] && set -- "$@" --features "$FEATURES"

HOST_ARCH="$(uname -m)"
TARGET_ARCH="${TARGET%%-*}"
MUSL_CC="${TARGET_ARCH}-linux-musl-gcc"
HOST_ARCH="$(uname -m)"
TARGET_ARCH="${TARGET%%-*}"
MUSL_CC="${TARGET_ARCH}-linux-musl-gcc"

if [ "${FORCE_CROSS:-0}" != "1" ] \
&& [ "$HOST_ARCH" = "$TARGET_ARCH" ] \
&& rustup target list --installed 2>/dev/null | grep -qx "$TARGET" \
&& command -v "$MUSL_CC" >/dev/null 2>&1; then
# Native same-arch static-musl build. cargo derives the linker override var
# from the target triple (uppercased, non-alphanumerics -> `_`); cc-rs (used by
# ring's build script to compile its C/asm) reads `CC_<triple>` with the triple
# lower-cased and dashes -> `_`. Set both so the musl toolchain is used for the
# link *and* for any C compiled into the guest binary.
LINKER_VAR="CARGO_TARGET_$(echo "$TARGET" | tr 'a-z-' 'A-Z_')_LINKER"
CC_VAR="CC_$(echo "$TARGET" | tr '-' '_')"
echo "build-initramfs: native musl build ($TARGET, cc/linker $MUSL_CC)" >&2
env "$LINKER_VAR=$MUSL_CC" "$CC_VAR=$MUSL_CC" cargo "$@"
else
echo "build-initramfs: cross build ($TARGET)" >&2
command -v cross >/dev/null || cargo install cross --locked
cross "$@"
if [ "${FORCE_CROSS:-0}" != "1" ] \
&& [ "$HOST_ARCH" = "$TARGET_ARCH" ] \
&& rustup target list --installed 2>/dev/null | grep -qx "$TARGET" \
&& command -v "$MUSL_CC" >/dev/null 2>&1; then
# Native same-arch static-musl build. cargo derives the linker override var
# from the target triple (uppercased, non-alphanumerics -> `_`); cc-rs (used
# by ring's build script to compile its C/asm) reads `CC_<triple>` with the
# triple lower-cased and dashes -> `_`. Set both so the musl toolchain is
# used for the link *and* for any C compiled into the guest binary.
LINKER_VAR="CARGO_TARGET_$(echo "$TARGET" | tr 'a-z-' 'A-Z_')_LINKER"
CC_VAR="CC_$(echo "$TARGET" | tr '-' '_')"
echo "build-initramfs: native musl build ($TARGET, cc/linker $MUSL_CC)" >&2
env "$LINKER_VAR=$MUSL_CC" "$CC_VAR=$MUSL_CC" cargo "$@"
else
echo "build-initramfs: cross build ($TARGET)" >&2
command -v cross >/dev/null || cargo install cross --locked
cross "$@"
fi
BIN="$ROOT/target/$TARGET/initramfs/minimald"
[ -x "$BIN" ] || { echo "minimald not built at $BIN" >&2; exit 1; }
fi
BIN="$ROOT/target/$TARGET/initramfs/minimald"
[ -x "$BIN" ] || { echo "minimald not built at $BIN" >&2; exit 1; }

STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
Expand Down
20 changes: 15 additions & 5 deletions scripts/fetch-artifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
# On macOS the CLI runs inside a VM and only the project dir syncs to the host;
# use the local dev flow in crates/minvmd/README.md instead.
#
# Requires: a Rust toolchain + protoc on the host (the CI job installs them).
# Set MIP to a prebuilt mip binary to run it directly and skip the compile — e.g.
# the release pipeline reuses the static mip it already built rather than
# recompiling a debug one just to drive the cache fetch.
#
# Requires: a Rust toolchain + protoc on the host, unless MIP is supplied (the CI
# job installs them).
#
# Usage: scripts/fetch-artifact.sh <output-name> <dest-path> [arch]
# arch defaults to aarch64 (the boot runner is Apple Silicon).
Expand All @@ -33,10 +38,15 @@ esac
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

# Build the CLI from source (debug: this drives a cache fetch, so CLI CPU is not
# the bottleneck). Incremental, so a second invocation in the same job is cheap.
cargo build -p mip
MIP="$ROOT/target/debug/mip"
# Use a prebuilt mip if provided, else build the CLI from source (debug: this
# drives a cache fetch, so CLI CPU is not the bottleneck; incremental, so a
# second invocation in the same job is cheap).
if [ -n "${MIP:-}" ]; then
[ -x "$MIP" ] || { echo "MIP not an executable file: $MIP" >&2; exit 1; }
else
cargo build -p mip
MIP="$ROOT/target/debug/mip"
fi

mkdir -p "$(dirname "$DEST")"
"$MIP" materialize --output "$DEST" --arch "$ARCH" "$OUTPUT"
Expand Down
20 changes: 15 additions & 5 deletions scripts/fetch-libkrun.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
# LIBKRUN_PREFIX="$PREFIX" # minvmd build.rs link-search dir (+ rpath)
# LD_LIBRARY_PATH="$PREFIX" # runtime: libkrun.so -> libkrunfw.so.5
#
# Set MIP to a prebuilt mip binary to run it directly and skip the compile — e.g.
# the release pipeline reuses the static mip it already built rather than
# recompiling a debug one just to drive the cache fetch.
#
# Linux-only (package materialization runs the build pipeline natively on Linux).
# Requires: a Rust toolchain + protoc + jq on the host (the CI job installs them).
# Requires: jq, plus a Rust toolchain + protoc unless MIP is supplied (the CI job
# installs them).
#
# Usage: scripts/fetch-libkrun.sh <prefix-dir> [arch]
# arch defaults to aarch64 (matches scripts/fetch-artifact.sh).
Expand All @@ -36,10 +41,15 @@ esac
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

# Build the CLI from source (debug: this drives a cache fetch, so CLI CPU is not
# the bottleneck). Incremental, so a second invocation in the same job is cheap.
cargo build -p mip
MIP="$ROOT/target/debug/mip"
# Use a prebuilt mip if provided, else build the CLI from source (debug: this
# drives a cache fetch, so CLI CPU is not the bottleneck; incremental, so a
# second invocation in the same job is cheap).
if [ -n 5F2B "${MIP:-}" ]; then
[ -x "$MIP" ] || { echo "MIP not an executable file: $MIP" >&2; exit 1; }
else
cargo build -p mip
MIP="$ROOT/target/debug/mip"
fi

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
Expand Down
Loading
0