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
60 changes: 48 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@ permissions:
contents: read

jobs:
linux:
name: linux build, unit, pack
hosted-linux:
name: hosted Linux build, unit, pack
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: "20"
cache: npm
- run: sudo apt-get update && sudo apt-get install -y libslirp-dev libglib2.0-dev
- run: npm ci --ignore-scripts
- run: npm run build
- run: npm run test:unit
- run: npm run pack:check

windows:
name: windows build, import, pack
hosted-windows-js:
name: hosted Windows JS-only build, import, pack
runs-on: windows-latest
env:
NODE_VMM_SKIP_NATIVE: "1"
Expand All @@ -36,14 +37,14 @@ jobs:
node-version: "20"
cache: npm
- run: npm ci --ignore-scripts
- run: npm run build
- run: npm run build:ts
- shell: pwsh
run: |
node -e "import('./dist/src/index.js').then((m)=>{ console.log(m.features()[0]); })"
- run: npm pack --dry-run --ignore-scripts

windows-whp-gate:
name: windows WHP native gate
self-hosted-windows-whp:
name: self-hosted Windows WHP native gate
runs-on: [self-hosted, windows, x64, whp]
if: github.event_name == 'workflow_dispatch'
steps:
Expand All @@ -54,13 +55,45 @@ jobs:
cache: npm
- run: npm ci --ignore-scripts
- run: npm run build
- shell: pwsh
- name: Check required WHP doctor probes
shell: pwsh
run: |
node -e "import('./dist/src/index.js').then(async (m)=>{ const doctor = await m.doctor(); console.log(JSON.stringify(doctor, null, 2)); if (!doctor.ok) process.exit(1); })"
node -e "import('./dist/src/kvm.js').then((m)=>{ const smoke = m.whpSmokeHlt(); console.log(JSON.stringify(smoke, null, 2)); if (smoke.exitReason !== 'hlt' || smoke.dirtyPages < 1) process.exit(1); })"
@'
import { doctor } from "./dist/src/index.js";

const result = await doctor();
console.log(JSON.stringify(result, null, 2));

kvm-release-gate:
name: kvm release gate
const checks = new Map(result.checks.map((check) => [check.name, check]));
const required = ["whp-api", "whp-dirty-pages"];
const failed = required
.map((name) => checks.get(name) ?? { name, ok: false, label: "missing required doctor check" })
.filter((check) => !check.ok);

if (failed.length > 0) {
const details = failed.map((check) => `${check.name}: ${check.label}`).join("; ");
console.error(`Required WHP doctor checks failed: ${details}`);
process.exit(1);
}
'@ | node --input-type=module -
- name: Run WHP dirty-page smoke
shell: pwsh
run: |
@'
import { whpSmokeHlt } from "./dist/src/kvm.js";

const smoke = whpSmokeHlt();
console.log(JSON.stringify(smoke, null, 2));

if (smoke.exitReason !== "hlt" || smoke.dirtyPages < 1 || smoke.dirtyTracking !== true) {
console.error("Required WHP dirty-page smoke failed");
process.exit(1);
}
'@ | node --input-type=module -
- run: node --test dist/test/native.test.js

self-hosted-linux-kvm:
name: self-hosted Linux KVM release gate
runs-on: [self-hosted, linux, x64, kvm]
if: github.event_name == 'workflow_dispatch'
steps:
Expand All @@ -69,6 +102,9 @@ jobs:
with:
node-version: "20"
cache: npm
- run: sudo apt-get update && sudo apt-get install -y libslirp-dev libglib2.0-dev
- run: npm ci --ignore-scripts
- run: npm run build
- run: node --test dist/test/native.test.js
- run: echo "NODE_VMM_KERNEL=$(npm run -s kernel:fetch)" >> "$GITHUB_ENV"
- run: npm run release:check
114 changes: 114 additions & 0 deletions .github/workflows/prebuilt-rootfs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: prebuilt-rootfs

# Builds prebuilt ext4 rootfs files for the most common images and
# attaches them to the matching GitHub Release. Lets Windows users
# `node-vmm run --image alpine:3.20` boot without a local WSL2 install,
# and lets anyone download a sandbox-ready rootfs without the OCI fetch
# overhead.
#
# Triggered on tag push (v*) and on workflow_dispatch. The build runs
# inside ubuntu-latest with sudo because the pipeline uses mkfs.ext4 +
# mount + chroot.

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Release tag to attach prebuilt rootfs files to"
required: true

permissions:
contents: write

jobs:
build:
name: Build ${{ matrix.image }} rootfs
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- image: alpine:3.20
slug: alpine-3.20
disk: 256
- image: node:20-alpine
slug: node-20-alpine
disk: 1024
- image: node:22-alpine
slug: node-22-alpine
disk: 1024
- image: oven/bun:1-alpine
slug: oven-bun-1-alpine
disk: 1024
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: "20"
cache: npm
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y g++ libslirp-dev e2fsprogs util-linux
- run: npm ci --ignore-scripts
- name: Build TS + native (Linux)
env:
NODE_VMM_FORCE_NATIVE_BUILD: "1"
run: npm run build
- name: Build prebuilt rootfs
run: |
mkdir -p dist-rootfs
sudo -E node scripts/build-prebuilt-rootfs.mjs \
--image "${{ matrix.image }}" \
--output "dist-rootfs/${{ matrix.slug }}.ext4" \
--disk-mib ${{ matrix.disk }} \
--slug "${{ matrix.slug }}"
- name: List generated assets
run: |
cd dist-rootfs
ls -la
- uses: actions/upload-artifact@v4
with:
name: rootfs-${{ matrix.slug }}
path: |
dist-rootfs/${{ matrix.slug }}.ext4.gz
dist-rootfs/${{ matrix.slug }}.ext4.manifest.json
if-no-files-found: error
retention-days: 1
compression-level: 0

release:
name: Attach rootfs prebuilds to release
needs: [build]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Stage release assets
run: |
set -euo pipefail
mkdir -p release
for dir in artifacts/rootfs-*; do
cp -v "$dir"/*.ext4.gz release/
cp -v "$dir"/*.ext4.manifest.json release/
done
ls -la release
- name: Determine release tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "ref=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "ref=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
fi
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.ref }}
files: |
release/*.ext4.gz
release/*.ext4.manifest.json
fail_on_unmatched_files: true
Loading
Loading