Skip to content

Commit fbae1bf

Browse files
authored
feat: hash-based manual release workflow (#20)
* feat: hash-based manual release workflow Mirror the manual build/release pipeline from e2b-dev/fc-versions, but identify each kernel build by a content hash of its inputs (configs + optional patches). Changing a flag or adding a patch yields a new artifact named vmlinux-<version>_<sha256[:7]>. - scripts/validate.py: resolves version names, computes hashes, builds the matrix and skips arches whose artifact is already in the GitHub release. - build.sh: now accepts <kernel_version> [arch], computes the same version_name (or honors VERSION_NAME), supports patches/<version>/. - .github/workflows/release.yml: workflow_dispatch with validate -> build -> publish (GH release) -> deploy (GCS), skipping work whose artifacts already exist. - Drops the old build-on-every-push workflow; releases are explicit. * fix: preserve version_name in artifact, force checkout - upload-artifact: keep version_name in the artifact tree so the publish step's ./builds/<version_name>/<arch>/vmlinux.bin lookup resolves after merge-multiple download. - build.sh: git checkout -f so a dirty tree from a prior iteration's apply_patches doesn't abort multi-version local builds. * fix: hash only top-level *.patch files apply_patches only processes patches/<v>/*.patch, so the hash inputs must match. Otherwise unrelated/nested files in patches/<v>/ would silently change the version_name without affecting the build. * fix: align bash kernel_versions.txt parser with python Strip inline comments and trim whitespace so build.sh and scripts/validate.py produce identical version names for entries like `6.1.158 # stable`. * refactor: simplify to "build everything, calver release per run" - workflow_dispatch with no inputs; pick the branch in the GitHub UI. - Always build every entry in kernel_versions.txt for amd64 and arm64 in parallel (one runner per arch). - One release per run, tagged YYYY.MM.DD (with .N suffix on collision), with every binary uploaded as vmlinux-<version>-<arch>.bin (plus the legacy vmlinux-<version>.bin for amd64). - Same binaries pushed to GCS under kernels/vmlinux-<version>/... Drops the per-build content-hash version_name machinery and the validate.py helper that fed it. * fix: calver suffix sequence base, .1, .2 (was skipping .1) * feat: also build on pull_request, gate publish on workflow_dispatch Open PRs now run the build matrix and upload kernel binaries as workflow artifacts so reviewers can grab and inspect them from the PR's checks tab. Release/GCS publishing remains manual-only. * feat: parallelize build per (kernel_version, arch) Generate the build matrix dynamically from kernel_versions.txt so each (version, arch) pair runs on its own runner. With N versions × 2 archs that's 2N parallel jobs (e.g. 2 versions -> 4 runners). build.sh already supports single-version mode, so no script changes; the matrix job emits a JSON include list that the build job fans out over. * fix: scope id-token to publish, harden version handling, idempotent calver - Drop workflow-level id-token: write; only the publish job (which needs OIDC for GCP auth) gets id-token: write. PR builds no longer receive a token they can exchange for cloud creds. - Validate kernel versions against [0-9]+(\.[0-9]+)+ in the matrix job and pass matrix.version to build.sh via env vars instead of inline YAML expression interpolation, so a malicious kernel_versions.txt entry can't shell-inject into the runner. - Calver tag picker now also checks local and remote git tags, not just GH releases, so retries after a partial publish no longer pick a tag that's already been pushed. * fix: emit Build kernels (x86_64|arm64) checks for branch protection The 'Main' ruleset on this repo requires status checks named exactly 'Build kernels (x86_64)' and 'Build kernels (arm64)' (left over from the previous workflow). The new per-(version, arch) jobs use different names, so those required checks never reported and PRs couldn't merge. Add a tiny aggregator job whose name interpolates to those exact two strings and that gates on the build matrix's combined result, so parallelism stays per-(version, arch) but branch protection is satisfied.
1 parent f371098 commit fbae1bf

4 files changed

Lines changed: 275 additions & 184 deletions

File tree

.github/workflows/fc-kernels.yml

Lines changed: 0 additions & 118 deletions
This file was deleted.

.github/workflows/release.yml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
name: Build & Release
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
matrix:
12+
runs-on: ubuntu-24.04
13+
permissions:
14+
contents: read
15+
outputs:
16+
build_matrix: ${{ steps.gen.outputs.matrix }}
17+
steps:
18+
- uses: actions/checkout@v4
19+
- id: gen
20+
run: |
21+
python3 - >> "$GITHUB_OUTPUT" <<'PY'
22+
import json
23+
import re
24+
import sys
25+
VERSION_RE = re.compile(r"^[0-9]+(?:\.[0-9]+)+$")
26+
versions = []
27+
for line in open("kernel_versions.txt").read().splitlines():
28+
v = line.split("#", 1)[0].strip()
29+
if not v:
30+
continue
31+
if not VERSION_RE.match(v):
32+
sys.exit(f"::error::invalid kernel version in kernel_versions.txt: {v!r}")
33+
versions.append(v)
34+
include = []
35+
for v in versions:
36+
include.append({"version": v, "arch": "amd64", "target_arch": "x86_64", "runner": "ubuntu-24.04"})
37+
include.append({"version": v, "arch": "arm64", "target_arch": "arm64", "runner": "ubuntu-24.04-arm"})
38+
print(f"matrix={json.dumps({'include': include})}")
39+
PY
40+
41+
build:
42+
needs: matrix
43+
name: Build ${{ matrix.version }} (${{ matrix.arch }})
44+
strategy:
45+
fail-fast: false
46+
matrix: ${{ fromJson(needs.matrix.outputs.build_matrix) }}
47+
runs-on: ${{ matrix.runner }}
48+
permissions:
49+
contents: read
50+
steps:
51+
- uses: actions/checkout@v4
52+
53+
- name: Build kernel
54+
env:
55+
KERNEL_VERSION: ${{ matrix.version }}
56+
KERNEL_TARGET_ARCH: ${{ matrix.target_arch }}
57+
run: sudo ./build.sh "$KERNEL_VERSION" "$KERNEL_TARGET_ARCH"
58+
59+
- uses: actions/upload-artifact@v4
60+
with:
61+
name: kernel-${{ matrix.version }}-${{ matrix.arch }}
62+
path: ./builds
63+
retention-days: 7
64+
65+
# Aggregator with the exact name required by the `Main` ruleset on this
66+
# repo. Per-(version, arch) jobs above run in parallel; this single check
67+
# gates the whole arch on their combined success.
68+
build-status:
69+
needs: build
70+
if: always()
71+
name: Build kernels (${{ matrix.target_arch }})
72+
strategy:
73+
matrix:
74+
target_arch: [x86_64, arm64]
75+
runs-on: ubuntu-24.04
76+
permissions:
77+
contents: read
78+
steps:
79+
- run: |
80+
if [[ "${{ needs.build.result }}" != "success" ]]; then
81+
echo "build matrix did not succeed (result=${{ needs.build.result }})"
82+
exit 1
83+
fi
84+
85+
publish:
86+
needs: build
87+
if: github.event_name == 'workflow_dispatch'
88+
runs-on: ubuntu-24.04
89+
permissions:
90+
contents: write
91+
id-token: write
92+
steps:
93+
- uses: actions/checkout@v4
94+
with:
95+
fetch-depth: 0
96+
97+
- uses: actions/download-artifact@v4
98+
with:
99+
path: ./builds
100+
merge-multiple: true
101+
102+
- name: Prepare release assets
103+
run: |
104+
set -euo pipefail
105+
mkdir -p release-assets
106+
for dir in ./builds/vmlinux-*/; do
107+
name=$(basename "$dir")
108+
[ -f "$dir/amd64/vmlinux.bin" ] && cp "$dir/amd64/vmlinux.bin" "release-assets/${name}-amd64.bin"
109+
[ -f "$dir/arm64/vmlinux.bin" ] && cp "$dir/arm64/vmlinux.bin" "release-assets/${name}-arm64.bin"
110+
# Legacy non-arch-suffixed asset (= amd64) for backwards compat.
111+
[ -f "$dir/vmlinux.bin" ] && cp "$dir/vmlinux.bin" "release-assets/${name}.bin"
112+
done
113+
ls -la release-assets/
114+
115+
- name: Pick calver tag
116+
id: tag
117+
env:
118+
GH_TOKEN: ${{ github.token }}
119+
run: |
120+
set -euo pipefail
121+
base="$(date -u +%Y.%m.%d)"
122+
tag="$base"
123+
n=0
124+
while gh release view "$tag" >/dev/null 2>&1 \
125+
|| git rev-parse "refs/tags/$tag" >/dev/null 2>&1 \
126+
|| git ls-remote --exit-code --tags origin "refs/tags/$tag" >/dev/null 2>&1; do
127+
n=$((n + 1))
128+
tag="${base}.${n}"
129+
done
130+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
131+
132+
- name: Create release
133+
env:
134+
GH_TOKEN: ${{ github.token }}
135+
run: |
136+
set -euo pipefail
137+
git config user.name "github-actions[bot]"
138+
git config user.email "github-actions[bot]@users.noreply.github.com"
139+
git tag "${{ steps.tag.outputs.tag }}"
140+
git push origin "${{ steps.tag.outputs.tag }}"
141+
gh release create "${{ steps.tag.outputs.tag }}" \
142+
--title "Kernels ${{ steps.tag.outputs.tag }}" \
143+
--notes "Built from commit ${{ github.sha }} on ${{ github.ref_name }}" \
144+
./release-assets/*
145+
146+
- uses: google-github-actions/auth@v2
147+
with:
148+
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
149+
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT_EMAIL }}
150+
151+
- uses: google-github-actions/upload-cloud-storage@v2
152+
with:
153+
path: ./builds
154+
destination: ${{ vars.GCP_BUCKET_NAME }}/kernels
155+
gzip: false
156+
parent: false

README.md

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,57 @@
22

33
## Overview
44

5-
This project automates the building of custom Linux kernels for Firecracker microVMs, using the same kernel sources as official Firecracker repo and custom configuration files. It supports building specific kernel versions and uploading the resulting binaries to a Google Cloud Storage (GCS) bucket.
5+
This project builds custom Linux kernels for Firecracker microVMs from the same kernel sources as the official Firecracker repo, using the configuration files (and optional patches) that live in this repo.
66

77
## Prerequisites
88

99
- Linux environment (for building kernels)
1010

11-
## Building Kernels
11+
## Building locally
1212

1313
1. **Configure kernel versions:**
14-
- Edit `kernel_versions.txt` to specify which kernel versions to build (one per line, e.g., `6.1.102`).
15-
- Place the corresponding config file in `configs/` (e.g., `configs/6.1.102.config`).
14+
- Edit `kernel_versions.txt` to specify which kernel versions to build (one per line, e.g. `6.1.158`).
15+
- Place the corresponding config(s) in `configs/x86_64/<version>.config` and `configs/arm64/<version>.config`.
16+
- (Optional) Drop `*.patch` files into `patches/<version>/` to apply on top of the upstream tree before build.
1617

1718
2. **Build:**
1819
```sh
19-
make build
20-
# or directly
21-
./build.sh
20+
make build # builds all versions in kernel_versions.txt for x86_64
21+
make build-arm64 # same, for arm64
22+
./build.sh 6.1.158 # build a single version (x86_64)
23+
./build.sh 6.1.158 arm64
2224
```
23-
The built kernels will be placed in `builds/vmlinux-<version>/<arch>/vmlinux.bin` where `<arch>` is `amd64` or `arm64` (Go/OCI convention). For x86_64 backward compatibility, a legacy copy is also placed at `builds/vmlinux-<version>/vmlinux.bin`.
2425

25-
## Development Workflow
26-
- On every push, GitHub Actions will automatically build the kernels and save it as an artifact.
26+
Output: `builds/vmlinux-<version>/<arch>/vmlinux.bin` where `<arch>` is `amd64` or `arm64` (Go/OCI convention). For x86_64 a legacy copy is also placed at `builds/vmlinux-<version>/vmlinux.bin`.
27+
28+
## CI / Releasing
29+
30+
The **Build & Release** workflow runs in two modes:
31+
32+
- **On every pull request**: builds every (kernel version × arch) combination from `kernel_versions.txt` in parallel (one runner per pair) and uploads the binaries as workflow artifacts (downloadable from the PR's checks tab) so reviewers can inspect them. No release or GCS upload happens.
33+
- **Manually (workflow_dispatch)**: pick the branch in the GitHub UI and run. It does the same build as a PR and additionally creates a GitHub release tagged `YYYY.MM.DD` (with a `.N` suffix for additional runs the same day) containing every binary, and uploads them to GCS.
34+
35+
Release asset naming for that commit:
36+
37+
```
38+
vmlinux-<version>-amd64.bin
39+
vmlinux-<version>-arm64.bin
40+
vmlinux-<version>.bin # legacy (= amd64) for backwards compat
41+
```
42+
43+
4. The same binaries are uploaded to GCS at `gs://$GCP_BUCKET_NAME/kernels/vmlinux-<version>/<arch>/vmlinux.bin`.
44+
45+
## New kernel in E2B's infra
46+
_Note: these steps should give you a new kernel on your self-hosted E2B using https://github.com/e2b-dev/infra_
47+
48+
- Run the release workflow on the branch with the new config/patch.
49+
- Update `DefaultKernelVersion` in [packages/api/internal/cfg/model.go](https://github.com/e2b-dev/infra/blob/main/packages/api/internal/cfg/model.go) if you changed the kernel version.
50+
- Build and deploy `api`.
2751

2852
## Architecture naming
2953

3054
Output directories use Go's `runtime.GOARCH` convention (`amd64`, `arm64`) so they match the infra orchestrator's `TargetArch()` path resolution. The build-time variable `TARGET_ARCH` (`x86_64`, `arm64`) is only used internally for config paths and cross-compilation flags.
3155

32-
## New Kernel in E2B's infra
33-
_Note: these steps should give you new kernel on your self-hosted E2B using https://github.com/e2b-dev/infra_
34-
35-
- Copy the kernel build in your project's object storage under `e2b-*-fc-kernels`
36-
- In [packages/api/internal/cfg/model.go](https://github.com/e2b-dev/infra/blob/main/packages/api/internal/cfg/model.go) update `DefaultKernelVersion`
37-
- Build and deploy `api`
38-
3956
## License
4057

41-
This project is licensed under the Apache License 2.0. See [LICENSE](LICENSE) for details.
58+
This project is licensed under the Apache License 2.0. See [LICENSE](LICENSE) for details.

0 commit comments

Comments
 (0)