From 8abfdf1df75c3968af190ec0f757bb4adb516458 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Tue, 19 May 2026 22:49:48 +0100 Subject: [PATCH 1/2] ci: add manual workflow to probe macOS Virtualization.framework Adds a workflow_dispatch-only job that runs on an Apple Silicon (arm64) GitHub-hosted macOS runner and reports whether the Virtualization framework is available. Probes layer from cheapest to most authoritative: runner identity dump, arm64 assertion, kern.hv_support sysctl, framework bundle presence, and finally VZVirtualMachine.isSupported via an inline Swift program. Writes a result table to the job step summary. --- .../workflows/check-macos-virtualization.yml | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .github/workflows/check-macos-virtualization.yml diff --git a/.github/workflows/check-macos-virtualization.yml b/.github/workflows/check-macos-virtualization.yml new file mode 100644 index 0000000..1578263 --- /dev/null +++ b/.github/workflows/check-macos-virtualization.yml @@ -0,0 +1,127 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +# Manual probe: confirm the Virtualization.framework is available on a +# GitHub-hosted Apple Silicon (ARM64) macOS runner. +# +# Apple's Virtualization.framework is the macOS analogue of KVM/WHP that +# hyperlight needs for micro-VMs. This job inspects the runner so we can +# verify before betting on macOS hosting (see /memories/azure-hosting-kvm.md +# for why "just use platform X" warrants a real check first). + +name: Check macOS Virtualization Framework + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + check-virtualization: + name: Inspect Virtualization.framework on ARM macOS + runs-on: macos-latest + + steps: + - name: Report runner identity + run: | + echo "::group::Runner identity" + echo "OS: $(sw_vers -productName) $(sw_vers -productVersion) (build $(sw_vers -buildVersion))" + echo "Architecture: $(uname -m)" + echo "Kernel: $(uname -srv)" + echo "::endgroup::" + + - name: Assert Apple Silicon (arm64) + run: | + arch="$(uname -m)" + if [ "$arch" != "arm64" ]; then + echo "::error::Expected arm64 runner, got '$arch'" + exit 1 + fi + echo "Confirmed ARM64 runner." + + - name: Check hypervisor support via sysctl + run: | + echo "::group::sysctl hv/vmm" + # kern.hv_support is the canonical "hypervisor available" flag. + hv_support="$(sysctl -n kern.hv_support 2>/dev/null || echo missing)" + echo "kern.hv_support = ${hv_support}" + # Useful neighbours for diagnostics; not all present on every release. + sysctl -a 2>/dev/null | grep -E '^(kern\.hv_|hw\.optional\.arm|machdep\.cpu\.brand_string)' || true + echo "::endgroup::" + + if [ "$hv_support" != "1" ]; then + echo "::error::kern.hv_support is not 1 — hypervisor not advertised by kernel." + exit 1 + fi + + - name: Locate Virtualization.framework + run: | + fw="/System/Library/Frameworks/Virtualization.framework" + if [ ! -d "$fw" ]; then + echo "::error::Virtualization.framework not present at $fw" + exit 1 + fi + echo "Found framework bundle: $fw" + ls -la "$fw" + # Best-effort version read; not fatal if Info.plist layout changes. + if [ -f "$fw/Resources/Info.plist" ]; then + /usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$fw/Resources/Info.plist" 2>/dev/null \ + | sed 's/^/Framework version: /' || true + fi + + - name: Probe VZVirtualMachine.isSupported via Swift + # The framework being on disk doesn't guarantee the runtime says + # "yes you can boot a VM here". The authoritative check is + # VZVirtualMachine.isSupported, exposed by Apple's Swift API. + run: | + probe="$(mktemp -t vz-probe.XXXXXX).swift" + cat > "$probe" <<'SWIFT' + import Foundation + #if canImport(Virtualization) + import Virtualization + let supported = VZVirtualMachine.isSupported + FileHandle.standardOutput.write(Data("VZVirtualMachine.isSupported = \(supported)\n".utf8)) + exit(supported ? 0 : 2) + #else + FileHandle.standardError.write(Data("Virtualization module not importable on this runner\n".utf8)) + exit(3) + #endif + SWIFT + + echo "Running probe: $probe" + set +e + swift "$probe" + rc=$? + set -e + + case "$rc" in + 0) + echo "Virtualization framework is ENABLED on this runner." + ;; + 2) + echo "::error::Virtualization.framework loaded but VZVirtualMachine.isSupported == false" + exit 1 + ;; + 3) + echo "::error::Swift could not import the Virtualization module" + exit 1 + ;; + *) + echo "::error::Swift probe failed with exit code $rc" + exit 1 + ;; + esac + + - name: Summary + if: always() + run: | + { + echo "### macOS Virtualization Framework check" + echo "" + echo "| Property | Value |" + echo "| --- | --- |" + echo "| Runner label | macos-latest |" + echo "| OS | $(sw_vers -productName) $(sw_vers -productVersion) |" + echo "| Architecture | $(uname -m) |" + echo "| kern.hv_support | $(sysctl -n kern.hv_support 2>/dev/null || echo missing) |" + } >> "$GITHUB_STEP_SUMMARY" From 1c57208d8734023cca20a18170076ac64438b386 Mon Sep 17 00:00:00 2001 From: Simon Davies Date: Tue, 19 May 2026 22:57:28 +0100 Subject: [PATCH 2/2] ci(vz-check): make VZVirtualMachine.isSupported the only gate On Apple Silicon kern.hv_support does not exist, so the previous hard-fail was wrong. Demote sysctl inspection to diagnostic-only (now also captures kern.hv_vmm_present and CPU brand), let the Swift VZVirtualMachine.isSupported probe be the sole pass/fail signal, and record each step's findings to GITHUB_OUTPUT so the always()-summary renders an accurate table even when later steps fail. --- .../workflows/check-macos-virtualization.yml | 107 +++++++++++++++--- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/.github/workflows/check-macos-virtualization.yml b/.github/workflows/check-macos-virtualization.yml index 1578263..062fba7 100644 --- a/.github/workflows/check-macos-virtualization.yml +++ b/.github/workflows/check-macos-virtualization.yml @@ -7,6 +7,14 @@ # hyperlight needs for micro-VMs. This job inspects the runner so we can # verify before betting on macOS hosting (see /memories/azure-hosting-kvm.md # for why "just use platform X" warrants a real check first). +# +# Authoritative pass/fail: VZVirtualMachine.isSupported (Apple's own runtime +# verdict). The sysctl dump is diagnostic only — on Apple Silicon some keys +# (notably kern.hv_support) simply do not exist; treating their absence as a +# failure is wrong. We also capture kern.hv_vmm_present which tells us whether +# this kernel itself is running inside a hypervisor (true for GitHub-hosted +# macOS runners) — relevant because nested-virt support is silicon/OS +# version dependent. name: Check macOS Virtualization Framework @@ -23,13 +31,30 @@ jobs: steps: - name: Report runner identity + id: identity run: | + os_name="$(sw_vers -productName)" + os_version="$(sw_vers -productVersion)" + os_build="$(sw_vers -buildVersion)" + arch="$(uname -m)" + kernel="$(uname -srv)" + cpu_brand="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo unknown)" + echo "::group::Runner identity" - echo "OS: $(sw_vers -productName) $(sw_vers -productVersion) (build $(sw_vers -buildVersion))" - echo "Architecture: $(uname -m)" - echo "Kernel: $(uname -srv)" + echo "OS: ${os_name} ${os_version} (build ${os_build})" + echo "Architecture: ${arch}" + echo "Kernel: ${kernel}" + echo "CPU brand: ${cpu_brand}" echo "::endgroup::" + { + echo "os_name=${os_name}" + echo "os_version=${os_version}" + echo "os_build=${os_build}" + echo "arch=${arch}" + echo "cpu_brand=${cpu_brand}" + } >> "$GITHUB_OUTPUT" + - name: Assert Apple Silicon (arm64) run: | arch="$(uname -m)" @@ -39,40 +64,63 @@ jobs: fi echo "Confirmed ARM64 runner." - - name: Check hypervisor support via sysctl + - name: Capture hypervisor sysctls (diagnostic) + id: sysctls + # Diagnostic only — does NOT gate the job. On Apple Silicon + # kern.hv_support does not exist; kern.hv_vmm_present indicates the + # *kernel* itself is running inside a hypervisor (expected on + # GitHub-hosted runners since they are virtualised). The real + # pass/fail is VZVirtualMachine.isSupported in a later step. run: | - echo "::group::sysctl hv/vmm" - # kern.hv_support is the canonical "hypervisor available" flag. hv_support="$(sysctl -n kern.hv_support 2>/dev/null || echo missing)" - echo "kern.hv_support = ${hv_support}" - # Useful neighbours for diagnostics; not all present on every release. - sysctl -a 2>/dev/null | grep -E '^(kern\.hv_|hw\.optional\.arm|machdep\.cpu\.brand_string)' || true + hv_vmm_present="$(sysctl -n kern.hv_vmm_present 2>/dev/null || echo missing)" + + echo "::group::Hypervisor sysctls" + echo "kern.hv_support = ${hv_support} (Intel-era key; 'missing' is expected on arm64)" + echo "kern.hv_vmm_present = ${hv_vmm_present} (1 = this kernel is itself running inside a hypervisor)" + echo "" + echo "Full kern.hv_* / hw.optional.arm / cpu brand dump:" + sysctl -a 2>/dev/null \ + | grep -E '^(kern\.hv_|hw\.optional\.arm|machdep\.cpu\.brand_string)' \ + || true echo "::endgroup::" - if [ "$hv_support" != "1" ]; then - echo "::error::kern.hv_support is not 1 — hypervisor not advertised by kernel." - exit 1 - fi + { + echo "hv_support=${hv_support}" + echo "hv_vmm_present=${hv_vmm_present}" + } >> "$GITHUB_OUTPUT" - name: Locate Virtualization.framework + id: framework run: | fw="/System/Library/Frameworks/Virtualization.framework" if [ ! -d "$fw" ]; then echo "::error::Virtualization.framework not present at $fw" + echo "framework_present=false" >> "$GITHUB_OUTPUT" exit 1 fi echo "Found framework bundle: $fw" ls -la "$fw" + + fw_version="unknown" # Best-effort version read; not fatal if Info.plist layout changes. if [ -f "$fw/Resources/Info.plist" ]; then - /usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$fw/Resources/Info.plist" 2>/dev/null \ - | sed 's/^/Framework version: /' || true + fw_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \ + "$fw/Resources/Info.plist" 2>/dev/null || echo unknown)" + echo "Framework version: ${fw_version}" fi + { + echo "framework_present=true" + echo "framework_version=${fw_version}" + } >> "$GITHUB_OUTPUT" + - name: Probe VZVirtualMachine.isSupported via Swift + id: vz_probe # The framework being on disk doesn't guarantee the runtime says # "yes you can boot a VM here". The authoritative check is # VZVirtualMachine.isSupported, exposed by Apple's Swift API. + # This step is the SINGLE source of truth for pass/fail. run: | probe="$(mktemp -t vz-probe.XXXXXX).swift" cat > "$probe" <<'SWIFT' @@ -94,19 +142,37 @@ jobs: rc=$? set -e + # Record the verdict in step output BEFORE potentially failing, so + # the always()-summary at the end can render an accurate value. case "$rc" in 0) + { + echo "vz_supported=true" + echo "vz_status=enabled" + } >> "$GITHUB_OUTPUT" echo "Virtualization framework is ENABLED on this runner." ;; 2) + { + echo "vz_supported=false" + echo "vz_status=isSupported==false" + } >> "$GITHUB_OUTPUT" echo "::error::Virtualization.framework loaded but VZVirtualMachine.isSupported == false" exit 1 ;; 3) + { + echo "vz_supported=false" + echo "vz_status=module-not-importable" + } >> "$GITHUB_OUTPUT" echo "::error::Swift could not import the Virtualization module" exit 1 ;; *) + { + echo "vz_supported=false" + echo "vz_status=probe-error-${rc}" + } >> "$GITHUB_OUTPUT" echo "::error::Swift probe failed with exit code $rc" exit 1 ;; @@ -121,7 +187,12 @@ jobs: echo "| Property | Value |" echo "| --- | --- |" echo "| Runner label | macos-latest |" - echo "| OS | $(sw_vers -productName) $(sw_vers -productVersion) |" - echo "| Architecture | $(uname -m) |" - echo "| kern.hv_support | $(sysctl -n kern.hv_support 2>/dev/null || echo missing) |" + echo "| OS | ${{ steps.identity.outputs.os_name }} ${{ steps.identity.outputs.os_version }} (build ${{ steps.identity.outputs.os_build }}) |" + echo "| Architecture | ${{ steps.identity.outputs.arch }} |" + echo "| CPU | ${{ steps.identity.outputs.cpu_brand }} |" + echo "| kern.hv_support | ${{ steps.sysctls.outputs.hv_support }} _(Intel-era; 'missing' is normal on arm64)_ |" + echo "| kern.hv_vmm_present | ${{ steps.sysctls.outputs.hv_vmm_present }} _(1 = runner is itself a VM)_ |" + echo "| Virtualization.framework present | ${{ steps.framework.outputs.framework_present || 'unknown' }} |" + echo "| Virtualization.framework version | ${{ steps.framework.outputs.framework_version || 'unknown' }} |" + echo "| **VZVirtualMachine.isSupported** | **${{ steps.vz_probe.outputs.vz_supported || 'not-run' }}** (${{ steps.vz_probe.outputs.vz_status || 'n/a' }}) |" } >> "$GITHUB_STEP_SUMMARY"