feat: add macOS VM support via Apple Virtualization.framework#90
feat: add macOS VM support via Apple Virtualization.framework#90
Conversation
This comment has been minimized.
This comment has been minimized.
d8e95da to
4866301
Compare
This comment has been minimized.
This comment has been minimized.
4866301 to
38e0ed9
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
6f2b159 to
cfe4832
Compare
This comment has been minimized.
This comment has been minimized.
Split platform-specific code into _linux.go and _darwin.go files across resources, network, devices, ingress, vmm, and vm_metrics packages. Add hypervisor abstraction with registration pattern (RegisterSocketName, RegisterVsockDialerFactory, RegisterClientFactory) to decouple instance management from specific hypervisor implementations. Add "vz" to the OpenAPI hypervisor type enum, erofs disk format support, and insecure registry option for builds. No behavioral changes on Linux. macOS can now compile but has no VM functionality yet. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add vz hypervisor implementation that runs VMs on macOS using Apple's Virtualization.framework via a codesigned subprocess (vz-shim). Includes vsock-based guest communication, shared directory mounts for disk access, and macOS-native networking via vmnet. Key components: - cmd/vz-shim: subprocess that creates and manages vz VMs - lib/hypervisor/vz: starter, client, and vsock dialer for vz - Makefile targets: build-darwin, test-darwin, dev-darwin, sign-darwin - CI: macOS runner for test-darwin - scripts/install.sh: macOS support (launchd, Homebrew, codesign) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Embed vz.entitlements as a Go resource and write it to a temp file at runtime for codesigning, replacing the broken entitlementsPath() that looked for the file next to the executable - Add vz-shim copy step in .air.darwin.toml so the go:embed directive can find the binary during dev builds - Add --entitlements flag to codesign in install.sh download path so binaries receive the virtualization entitlement - Prepend /opt/homebrew/opt/e2fsprogs/sbin to launchd plist PATH so mkfs.ext4 from keg-only e2fsprogs is found at runtime Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
disk_darwin.go and disk_linux.go were unified into disk.go in PR #89 but snuck back in during the rebase as new files with no conflicts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nstall - Read from bufio.Reader instead of raw conn in vsock proxy to prevent silent data loss when the buffered reader consumed beyond the newline - Replace cmd.Process.Release() with go cmd.Wait() to properly reap vz-shim child processes instead of leaving zombies - Update hypervisor README to reflect vz subprocess model (not in-process) - Remove vz-shim from install/uninstall scripts (it's embedded in hypeman-api and extracted at runtime) - Add CLI smoke tests (hypeman ps, hypeman images) to e2e install test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract JWT_SECRET/PORT with grep instead of sourcing the config file, which breaks on macOS where paths contain spaces - Skip CLI smoke tests gracefully when CLI binary is not installed (e.g., no darwin/arm64 release available) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Builder images are now auto-built on startup, so manual push workflow and the -registry-push flag are no longer needed. The underlying repo_access JWT infrastructure remains for other registry auth flows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The vz-shim embed is darwin-only (build tag), so the directory isn't needed on Linux. On macOS the Makefile creates it before compiling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests pull, run, exec, stop, and rm using the CLI against a real alpine VM to verify the full stack works end-to-end after install. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The .gitkeep was removed so the directory no longer exists in the repo. The Makefile needs to mkdir -p before copying the built binary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The macOS CLI install on line 653 used bare 'install' while all other binary installs to $INSTALL_DIR used '$SUDO install'. When /usr/local/bin isn't writable and $SUDO is set to 'sudo', this caused a permission error that aborted the script (due to set -e) after the service was already running, leaving a partial installation. Applied via @cursor push command
CLI releases use goreleaser naming ("macos" not "darwin", .zip not
.tar.gz). Fix artifact lookup and extraction to handle both formats.
Make CLI presence a hard fail in e2e test — if the install script
can't install the CLI, that's a real failure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLI doesn't have an 'images' subcommand. The VM lifecycle tests (pull, run, exec, stop, rm) cover real functionality. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Image pulls are async — 'hypeman pull' returns immediately with status:pending. Retry 'hypeman run' in a loop until the image is available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove "Alternative Commands" section (make dev covers it) - Remove known limitations that are implementation details or wrong: disk format is handled automatically, snapshots aren't supported, network ingress is internal, vz-shim is a subprocess not in-process - Keep disk format and snapshots as brief notes - Makefile: 'run' target comment says "for agents" not "for testing" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Requirements: remove "Production"/"Experimental" labels - Quick Start: "Linux and macOS supported" - CLI section: reword for local-first usage, remove "remote" framing - Remove entire "macOS Support" section (platform details belong in DEVELOPMENT.md, not the user-facing README) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
87e6ddb to
c42aad6
Compare
sjmiller609
left a comment
There was a problem hiding this comment.
Just nits or possible additional color, looks good
hiroTamada
left a comment
There was a problem hiding this comment.
Solid PR. Clean hypervisor abstraction, good use of build tags, thorough docs and E2E coverage. The vz-shim subprocess design (surviving hypeman restarts, CH-compatible control API) is well thought out. The API handler refactor to use GetVsockDialer is a nice decoupling.
A few minor nits inline, nothing blocking.
| </dict> | ||
| </plist> | ||
| ENTITLEMENTS | ||
| codesign --force --sign - --entitlements "$ENTITLEMENTS_TMP" "${TMP_DIR}/${BINARY_NAME}" 2>/dev/null || true |
There was a problem hiding this comment.
nit: this silently swallows codesign failures. if signing fails, the binary won't have the virtualization entitlement and will crash at runtime with a confusing error. consider at least warning on failure instead of || true.
| |------------|----------|---------------|-------------------| | ||
| | Cloud Hypervisor | Linux | External process | HTTP API over Unix socket | | ||
| | QEMU | Linux | External process | QMP over Unix socket | | ||
| | vz | macOS | Subprocess (vz-shim) | gRPC over Unix socket | |
There was a problem hiding this comment.
nit: vz control interface is HTTP over Unix socket (matching the Cloud Hypervisor API pattern), not gRPC.
|
|
||
| # Determine CLI path | ||
| if [ "$OS" = "darwin" ]; then | ||
| HYPEMAN_CMD="/usr/local/bin/hypeman" |
There was a problem hiding this comment.
nit: both branches set the same path (/usr/local/bin/hypeman), so the if/else is a no-op.
This comment has been minimized.
This comment has been minimized.
| s.vm.Stop() | ||
| } | ||
| // Process will exit when VM stops (monitored in main) | ||
| }() |
There was a problem hiding this comment.
VMM shutdown may not terminate shim
Medium Severity
handleVMMShutdown returns success but only calls vm.Stop() when s.vm.CanStop() is true. If the VM is already non-stoppable (for example, it already stopped), the handler does nothing to end vz-shim, so the control process can remain alive with stale state after a shutdown request.
|
Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.
Or push these changes by commenting: Preview (1e49227e7d)diff --git a/cmd/vz-shim/main.go b/cmd/vz-shim/main.go
--- a/cmd/vz-shim/main.go
+++ b/cmd/vz-shim/main.go
@@ -61,7 +61,7 @@
slog.Info("VM started", "vcpus", config.VCPUs, "memory_mb", config.MemoryBytes/1024/1024)
// Create the shim server
- server := NewShimServer(vm, vmConfig)
+ server := NewShimServer(vm, vmConfig, cancel)
// Start control socket listener (remove stale socket from previous run)
os.Remove(config.ControlSocket)
diff --git a/cmd/vz-shim/server.go b/cmd/vz-shim/server.go
--- a/cmd/vz-shim/server.go
+++ b/cmd/vz-shim/server.go
@@ -4,6 +4,7 @@
import (
"bufio"
+ "context"
"encoding/json"
"fmt"
"io"
@@ -19,14 +20,16 @@
type ShimServer struct {
vm *vz.VirtualMachine
vmConfig *vz.VirtualMachineConfiguration
+ cancel context.CancelFunc
mu sync.RWMutex
}
// NewShimServer creates a new shim server.
-func NewShimServer(vm *vz.VirtualMachine, vmConfig *vz.VirtualMachineConfiguration) *ShimServer {
+func NewShimServer(vm *vz.VirtualMachine, vmConfig *vz.VirtualMachineConfiguration, cancel context.CancelFunc) *ShimServer {
return &ShimServer{
vm: vm,
vmConfig: vmConfig,
+ cancel: cancel,
}
}
@@ -151,7 +154,9 @@
if s.vm.CanStop() {
s.vm.Stop()
}
- // Process will exit when VM stops (monitored in main)
+ // Ensure process terminates even if VM is already stopped
+ // (e.g. CanStop() returned false because the VM already stopped).
+ s.cancel()
}()
} |
…egration tests Changes based on PR review feedback: - Reduce vz HTTP client timeout from 30s to 10s (local Unix socket) - Add comment on 2GB memory safety default in vz-shim - Fix graceful shutdown to only send ACPI power button without immediate force-kill fallback, aligning with CH/QEMU semantics - Add macOS vz integration tests (TestVZBasicLifecycle, TestVZExecAndShutdown) Test infrastructure improvements: - Use short /tmp/ paths for vz test temp dirs to avoid macOS 104-byte Unix socket path limit (t.TempDir() paths are too long) - Capture vz-shim stderr and log file contents in error messages for better diagnostics when shim fails to start Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1e82ed4 to
7d29895
Compare



Summary
vz-shim)build-darwin,test-darwin,dev-darwin,sign-darwintest-darwinscripts/install.shandscripts/uninstall.shwith macOS support (launchd, Homebrew PATH, codesign)Depends on #89 (cross-platform foundation) — merge that first, then rebase this onto
main.Test plan
test-darwin)make build-darwin && make sign-darwinsucceeds on macOSscripts/e2e-install-test.sh)🤖 Generated with Claude Code
Note
High Risk
Large cross-cutting change touching hypervisor lifecycle, process spawning/codesigning, CI, and install/uninstall scripts; regressions could impact VM startup/guest connectivity or production installs on both platforms.
Overview
Adds experimental native macOS (Apple Silicon) support via a new
vzhypervisor implementation that runs VMs through a detached, codesignedvz-shimsubprocess (HTTP control API + Unix-socket vsock proxy) and embeds/signs the shim + required entitlements at build time.Updates API/instance flows to be hypervisor-agnostic for guest vsock access (handlers now call
InstanceManager.GetVsockDialer), adjusts instance creation forvz-specific vsock socket paths and kernel console args, and adds macOS-focused tests/docs.Extends tooling and distribution for macOS: new
maketargets (build-darwin,sign-darwin,dev-darwin,test-darwin), Darwinairconfig, a macOS GitHub Actions job + install E2E test, and installer/uninstaller support for macOS (launchd, macOS paths, codesigning, CLI artifact handling, Docker socket detection, optional builder image build).Written by Cursor Bugbot for commit 7d29895. This will update automatically on new commits. Configure here.