Skip to content

Commit d8e95da

Browse files
rgarciaclaude
andcommitted
feat: add macOS VM support via Apple Virtualization.framework
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>
1 parent b730167 commit d8e95da

33 files changed

+2634
-246
lines changed

.air.darwin.toml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
root = "."
2+
testdata_dir = "testdata"
3+
tmp_dir = "tmp"
4+
5+
[build]
6+
args_bin = []
7+
bin = "./tmp/main"
8+
# Build for macOS with vz support, then sign with entitlements
9+
# Also builds and signs vz-shim (subprocess that hosts vz VMs)
10+
cmd = "make build-embedded && go build -o ./tmp/vz-shim ./cmd/vz-shim && codesign --sign - --entitlements vz.entitlements --force ./tmp/vz-shim && go build -tags containers_image_openpgp -o ./tmp/main ./cmd/api && codesign --sign - --entitlements vz.entitlements --force ./tmp/main"
11+
delay = 1000
12+
exclude_dir = ["assets", "tmp", "vendor", "testdata", "bin", "scripts", "data", "kernel"]
13+
exclude_file = []
14+
exclude_regex = ["_test.go"]
15+
exclude_unchanged = false
16+
follow_symlink = false
17+
# No sudo needed on macOS - vz doesn't require root
18+
full_bin = "./tmp/main"
19+
include_dir = []
20+
include_ext = ["go", "tpl", "tmpl", "html", "yaml"]
21+
include_file = []
22+
log = "build-errors.log"
23+
poll = false
24+
poll_interval = 0
25+
post_cmd = []
26+
kill_delay = '1s'
27+
rerun = false
28+
rerun_delay = 500
29+
send_interrupt = true
30+
stop_on_error = false
31+
32+
[color]
33+
app = ""
34+
build = "yellow"
35+
main = "magenta"
36+
runner = "green"
37+
watcher = "cyan"
38+
39+
[log]
40+
main_only = false
41+
time = false
42+
43+
[misc]
44+
clean_on_exit = false
45+
46+
[screen]
47+
clear_on_rebuild = false
48+
keep_scroll = true

.env.darwin.example

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# =============================================================================
2+
# macOS (Darwin) Configuration for Hypeman
3+
# =============================================================================
4+
# Copy this file to .env and customize for your environment.
5+
#
6+
# Key differences from Linux (.env.example):
7+
# - DEFAULT_HYPERVISOR: Use "vz" (Virtualization.framework) instead of cloud-hypervisor/qemu
8+
# - DATA_DIR: Uses macOS conventions (~/Library/Application Support)
9+
# - Network settings: BRIDGE_NAME, SUBNET_CIDR, etc. are IGNORED (vz uses NAT)
10+
# - Rate limiting: Not supported on macOS (no tc/HTB equivalent)
11+
# - GPU passthrough: Not supported on macOS
12+
# =============================================================================
13+
14+
# Required
15+
JWT_SECRET=dev-secret-change-me
16+
17+
# Data directory - use macOS conventions
18+
# Note: ~ expands to $HOME at runtime
19+
DATA_DIR=~/Library/Application Support/hypeman
20+
21+
# Server configuration
22+
PORT=8080
23+
24+
# Logging
25+
LOG_LEVEL=debug
26+
27+
# =============================================================================
28+
# Hypervisor Configuration (IMPORTANT FOR MACOS)
29+
# =============================================================================
30+
# On macOS, use "vz" (Virtualization.framework)
31+
# - "cloud-hypervisor" and "qemu" are NOT supported on macOS
32+
DEFAULT_HYPERVISOR=vz
33+
34+
# =============================================================================
35+
# Network Configuration (DIFFERENT ON MACOS)
36+
# =============================================================================
37+
# On macOS with vz, network is handled automatically via NAT:
38+
# - VMs get IP addresses from 192.168.64.0/24 via DHCP
39+
# - No TAP devices, bridges, or iptables needed
40+
# - The following settings are IGNORED on macOS:
41+
# BRIDGE_NAME, SUBNET_CIDR, SUBNET_GATEWAY, UPLINK_INTERFACE
42+
43+
# DNS Server for VMs (used by guest for resolution)
44+
DNS_SERVER=8.8.8.8
45+
46+
# =============================================================================
47+
# Caddy / Ingress Configuration
48+
# =============================================================================
49+
CADDY_LISTEN_ADDRESS=0.0.0.0
50+
CADDY_ADMIN_ADDRESS=127.0.0.1
51+
CADDY_ADMIN_PORT=2019
52+
# Note: 5353 is used by mDNSResponder (Bonjour) on macOS, using 5354 instead
53+
INTERNAL_DNS_PORT=5354
54+
CADDY_STOP_ON_SHUTDOWN=false
55+
56+
# =============================================================================
57+
# Build System Configuration
58+
# =============================================================================
59+
# For builds on macOS with vz, the registry URL needs to be accessible from
60+
# NAT VMs. Since vz uses 192.168.64.0/24 for NAT, the host is at 192.168.64.1.
61+
#
62+
# IMPORTANT: "host.docker.internal" does NOT work in vz VMs - that's a Docker
63+
# Desktop-specific hostname. Use the NAT gateway IP instead.
64+
#
65+
# Registry URL (the host's hypeman API, accessible from VMs)
66+
REGISTRY_URL=192.168.64.1:8080
67+
# Use HTTP (not HTTPS) since hypeman's internal registry uses plaintext
68+
REGISTRY_INSECURE=true
69+
70+
BUILDER_IMAGE=hypeman/builder:latest
71+
MAX_CONCURRENT_SOURCE_BUILDS=2
72+
BUILD_TIMEOUT=600
73+
74+
# =============================================================================
75+
# Resource Limits (same as Linux)
76+
# =============================================================================
77+
# Per-instance limits
78+
MAX_VCPUS_PER_INSTANCE=4
79+
MAX_MEMORY_PER_INSTANCE=8GB
80+
81+
# Aggregate limits (0 or empty = unlimited)
82+
# MAX_TOTAL_VOLUME_STORAGE=
83+
84+
# =============================================================================
85+
# OpenTelemetry (optional, same as Linux)
86+
# =============================================================================
87+
# OTEL_ENABLED=false
88+
# OTEL_ENDPOINT=127.0.0.1:4317
89+
# OTEL_SERVICE_NAME=hypeman
90+
# OTEL_INSECURE=true
91+
# ENV=dev
92+
93+
# =============================================================================
94+
# TLS / ACME Configuration (same as Linux)
95+
# =============================================================================
96+
# ACME_EMAIL=admin@example.com
97+
# ACME_DNS_PROVIDER=cloudflare
98+
# TLS_ALLOWED_DOMAINS=*.example.com
99+
# CLOUDFLARE_API_TOKEN=
100+
101+
# =============================================================================
102+
# macOS Limitations
103+
# =============================================================================
104+
# The following features are NOT AVAILABLE on macOS:
105+
#
106+
# 1. GPU Passthrough (VFIO, mdev)
107+
# - GPU_PROFILE_CACHE_TTL is ignored
108+
# - Device registration/binding will fail
109+
#
110+
# 2. Network Rate Limiting
111+
# - UPLOAD_BURST_MULTIPLIER, DOWNLOAD_BURST_MULTIPLIER are ignored
112+
# - No tc/HTB equivalent on macOS
113+
#
114+
# 3. CPU/Memory Hotplug
115+
# - Resize operations not supported
116+
#
117+
# 4. Disk I/O Limiting
118+
# - DISK_IO_LIMIT, OVERSUB_DISK_IO are ignored
119+
#
120+
# 5. Snapshots (requires macOS 14+ on Apple Silicon)
121+
# - SaveMachineStateToPath/RestoreMachineStateFromURL require macOS 14+
122+
# - Only supported on ARM64 (Apple Silicon) Macs

.github/workflows/test.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,64 @@ jobs:
5555
TLS_TEST_DOMAIN: "test.hypeman-development.com"
5656
TLS_ALLOWED_DOMAINS: '*.hypeman-development.com'
5757
run: make test
58+
59+
test-darwin:
60+
runs-on: [self-hosted, macos, arm64]
61+
concurrency:
62+
group: macos-ci-test-${{ github.ref }}
63+
cancel-in-progress: true
64+
env:
65+
DATA_DIR: /tmp/hypeman-ci-${{ github.run_id }}
66+
steps:
67+
- uses: actions/checkout@v4
68+
- uses: actions/setup-go@v6
69+
with:
70+
go-version: '1.25'
71+
cache: false
72+
- name: Login to Docker Hub
73+
uses: docker/login-action@v3
74+
with:
75+
username: ${{ secrets.DOCKERHUB_USERNAME }}
76+
password: ${{ secrets.DOCKERHUB_PASSWORD }}
77+
- name: Install dependencies
78+
run: |
79+
brew list e2fsprogs &>/dev/null || brew install e2fsprogs
80+
brew list erofs-utils &>/dev/null || brew install erofs-utils
81+
go mod download
82+
- name: Create run-scoped data directory
83+
run: mkdir -p "$DATA_DIR"
84+
- name: Generate OpenAPI code
85+
run: make oapi-generate
86+
- name: Build
87+
run: make build
88+
- name: Run tests
89+
env:
90+
DEFAULT_HYPERVISOR: vz
91+
JWT_SECRET: ci-test-secret
92+
run: make test
93+
- name: Cleanup
94+
if: always()
95+
run: |
96+
pkill -f "vz-shim.*$DATA_DIR" || true
97+
rm -rf "$DATA_DIR"
98+
make clean
99+
100+
e2e-install:
101+
runs-on: [self-hosted, macos, arm64]
102+
needs: test-darwin
103+
concurrency:
104+
group: macos-ci-e2e-${{ github.ref }}
105+
cancel-in-progress: true
106+
steps:
107+
- uses: actions/checkout@v4
108+
- uses: actions/setup-go@v6
109+
with:
110+
go-version: '1.25'
111+
cache: false
112+
- name: Install dependencies
113+
run: brew list caddy &>/dev/null || brew install caddy
114+
- name: Run E2E install test
115+
run: bash scripts/e2e-install-test.sh
116+
- name: Cleanup on failure
117+
if: failure()
118+
run: bash scripts/uninstall.sh || true

0 commit comments

Comments
 (0)