Releases: IngmarStein/havm
Release list
havm 1.0.0
The first stable release of havm — a zero-config CLI for running Home Assistant OS on Apple Silicon using Apple's native Virtualization framework.
The 0.x series was the public preview; 1.0.0 marks the CLI, config schema, and data layout as stable. havm run is still one command from download to boot, and this release does not touch your existing disk image, NVRAM, or machine identifier.
Highlights
- Zero-config first boot — downloads the HA OS image, decompresses it (statically linked liblzma; no
xzbinary needed), copies it to a 32 GiB sparse APFS disk, and boots over UEFI. No kernel extraction, no initrd, no hand-tuned command line. - Persistent and headless — the EFI variable store and a stable machine identifier survive reboots, so your MAC address doesn't move. Built to run under
brew servicesand start at login. - Bridge networking by default — the VM gets its own LAN address so Home Assistant discovery works; falls back to NAT at runtime if the binary lacks the
com.apple.vm.networkingentitlement. - Graceful shutdown — on SIGTERM/Ctrl+C: Supervisor API → debug SSH on 22222 →
ha host shutdown, with force-stop only as a last resort. - USB accessory passthrough (macOS 27+) — attach coordinators and other USB devices from a menu bar item; they hot-attach to the running VM.
- SSH access — an optional virtual CONFIG disk carries your
authorized_keys, imported on boot for root SSH on port 22222. - Prometheus metrics — a built-in HTTP endpoint for VM and USB state.
havm import-utm— bring an existing UTM Home Assistant VM over.--console— attach to the guest's virtio console (hvc0) for debugging.
Requirements
- macOS 15 or later on Apple Silicon
- USB accessory passthrough requires macOS 27 or later
- Bridge networking requires the
com.apple.vm.networkingentitlement (Apple approval); prebuilt releases are signed with it, self-compiled ad-hoc builds fall back to NAT
Install
brew install ingmarstein/havm/havm
havm runOr build from source (Xcode 27+, Swift 6.4) with ./scripts/build.sh release.
Known limitations
- Shutdown is SSH/API-driven. HA OS on aarch64 uses PSCI, so the ACPI power button is ignored. With neither
ha.api_tokennorssh.authorized_keysconfigured, havm can only force-stop the VM. ha host shutdownneeds the SSH add-on on port 22; the debug SSH on 22222 runsshutdown -h nowdirectly and is the more reliable path.- USB passthrough needs macOS 27 — on macOS 15–26 discovery is skipped with a log message.
- UTM import is partial — QEMU-based VMs (UTM's default for x86 guests) can't be imported, and SSH keys configured in UTM don't carry over.
Full documentation: https://ingmarstein.github.io/havm/
havm 0.3.1
What's new in v0.3.1
Fixed: guest IP resolution broke the "ready" detection
havm run could report Guest reachable at fe80:: and then time out
waiting for the Home Assistant web UI, because the hostname resolver
result was copied into a too-small buffer — IPv6 addresses came back
truncated (first 8 bytes plus stack garbage). macOS 15's mDNS resolver
returns the IPv6 link-local record for homeassistant.local first,
which surfaced the bug; it could in principle hit any macOS version.
The resolver now copies the address correctly, walks the full result
list, and prefers IPv4 — the address type the guest reliably gets
via DHCP. Link-local IPv6 (fe80::/10) results are skipped (they need
a scope ID to be usable); a global IPv6 address is used as a fallback
on IPv6-only setups, and the banner URL is bracketed accordingly
(http://[...]:8123).
If you were affected, the guest itself was always fine — only the
reported address/URL was wrong. As an interim workaround,
network.guest_hostname: <IPv4> in ~/.config/havm/config.yml skips
mDNS entirely.
Full Changelog: v0.3.0...v0.3.1
havm 0.3.0
What's new in v0.3.0
Runs on macOS 15 or later
The minimum deployment target drops from macOS 27 (Golden Gate) to
macOS 15 (Sequoia). All core functionality — EFI boot, bridge/NAT
networking, the interactive serial console, the SSH-key CONFIG disk,
graceful shutdown, and Prometheus metrics — works unchanged on
macOS 15 and 26.
USB accessory passthrough (the menu bar hot-attach of Zigbee
coordinators etc.) remains a macOS 27 feature: it depends on the
AccessoryAccess framework and VZUSBPassthroughDevice. On macOS 15–26,
havm run logs a message and continues without it. The CONFIG disk is
unaffected — SSH key import still works on every supported release.
New --log-level option
havm run --log-level {debug,info,warning,error} overrides
logging.level from the config file — handy for debugging a Homebrew
service without touching its config.
Reliability & diagnostics
logging.level: errornow maps to.error(previously mapped to
.critical, which suppressed all output).- An unexpected VM stop now exits with status 1, so launchd
(brew services) reliably restarts the process. - A warning is logged when the Home Assistant web UI doesn't respond
within 5 minutes of the guest becoming reachable. - Config hot-reload parse failures, PID file write/remove errors, MAC
address regeneration, and console cmdline patch failures are now
logged instead of failing silently. MemorySizeparsing rejects values at the exact 2^64 boundary.
Dependencies
- swift-log 1.15.0
Full Changelog: v0.2.4...v0.3.0
havm 0.2.4
What's new in v0.2.4
Config file overrides now actually apply
Keys in config.yml like vm.cpu_count, vm.memory_size,
vm.disk_size, and haos.release_channel were silently ignored: the
YAML parser matches keys literally, and these fields had no mapping for
their documented snake_case names. Settings such as a custom memory size
therefore had no effect even when the config file was in the right
place. They're now honored — and configs written by havm import-utm
(always snake_case) work too.
havm run reports which config file it uses
On startup havm run now logs the exact config path it looked at,
including (not found — using defaults) when there's no file — so a
config placed in the wrong location (e.g. ~/.config/havm/config.yml
when running as a Homebrew service) is immediately obvious.
More robust shutdown & networking
- A timeout now bounds the SSH-based shutdown step, so a stalled guest
kernel can no longer hang the shutdown defer block indefinitely. - DHCP lease results are cached, avoiding repeated reads of
/var/db/dhcpd_leaseswhile the guest is still booting. - Clearer message when bridge networking falls back to NAT (an empty
interface list definitively means thecom.apple.vm.networking
entitlement is missing).
Documentation
- The docs site supports deep links to sections (e.g.
#data-layout)
with copy-link buttons on every heading. havm import-utmnow documents that only Apple Virtualization-based
UTM VMs can be imported; QEMU-based VMs are not supported.- Clarified that the config location differs when running as a Homebrew
service. - Documented that the USB accessory menu bar app name is set by macOS
(the launching terminal, or "unknown app"), not by havm.
What's Changed
- chore(deps): bump actions/cache from 4 to 6 by @dependabot[bot] in #4
New Contributors
- @dependabot[bot] made their first contribution in #4
Full Changelog: v0.2.2...v0.2.4
v0.2.3
What's new in v0.2.3
Console boot messages (--console)
The --console flag now shows the full Linux kernel boot log on your
terminal — no more blank screen during startup. havm automatically
patches the kernel command line to enable the virtio serial console, so
you can follow the boot process from the first systemd message onwards.
Fresh installs also boot directly to multi-user mode instead of dropping
to a rescue shell prompt.
Graceful console exit
Typing poweroff in the guest now exits havm cleanly. Previously the
process would hang after the VM stopped.
Human-readable disk sizes
Resize messages now show "919 MiB" instead of "963661824 B". Fractional
values use one decimal place; whole numbers stay clean ("4 GiB", not
"4.0 GiB").
Faster disk image setup
Fresh installs now use APFS copy-on-write cloning (clonefile(2))
instead of a full copy, making setup nearly instant and preserving the
sparse-file space savings.
Metrics improvements
Dual-stack Prometheus endpoint on both 127.0.0.1:9210 and [::1]:9210
by default. Local endpoint reuse allows multiple listeners to share the
same port. IPv6 addresses in log output are now correctly bracketed.
Performance & modernisation
DispatchSourceTimer replaces asyncAfter for the boot-phase poll loop,
DispatchSemaphore replaced with async continuation, YAML config parsed
only once, JSON log double-allocation eliminated, signalShutdown data
race fixed. NSString bridging replaced with modern URL APIs, XCTest
migrated to Swift Testing, and NSLock replaced with Mutex.
Other fixes
- USB VID/PID extraction from accessory descriptors
- Health poll timeout prevents infinite wait on unreachable guests
- Observer
/ping(port 4357) polled for earlier readiness signal - SIGHUP triggers graceful restart for config hot-reload
cleanup --allremoves everything including downloads and cache
Full Changelog: v0.2.2...v0.2.3
havm 0.2.2
What's new in 0.2.2
Boot experience
- The ASCII art boot banner now only appears in interactive terminals — no more splash screen in launchd or Homebrew service logs.
- The banner is written to stderr, keeping stdout clean for JSON log output and
--consolemode.
Reliability
- Force-stop errors from the virtualization framework are no longer logged as errors when the stop is intentional (e.g., on Ctrl+C after SSH shutdown).
- The NVRAM EFI variable store is now properly flushed to disk on exit, preventing GRUB boot state loss after unclean shutdowns.
Documentation
- Added a Grafana example dashboard with 8 panels covering VM status, USB devices, and disk usage.
Internal
- Replaced the swift-prometheus dependency with a minimal ~20-line serializer, dropping swift-atomics and one whole Swift target.
- Merged HavmRuntime into HavmCore — one less module boundary.
- IPv6 address literals and DNS resolution are now supported for SSH connections and API calls.
Full Changelog: v0.2.1...v0.2.2
havm 0.2.1
What's new
--consoledebug flag — connects your terminal directly to the VM's serial console (/dev/hvc0) viaVZVirtioConsoleDeviceSerialPortConfiguration. Get a root shell without SSH or network — useful for debugging networking issues. Terminal is set to raw mode withONLCRfor proper CR-LF translation. Typepoweroffto shut down, or send SIGTERM from another terminal. Forces text log format to keep stdout clean.
Full Changelog: v0.2.0...v0.2.1
havm 0.2.0
v0.2.0
Breaking changes
- Bridge networking is now the default. The VM gets a LAN-reachable IP
for mDNS discovery (homeassistant.local) and local integrations. Self‑compiled
binaries without thecom.apple.vm.networkingentitlement automatically fall
back to NAT with a warning. Explicittype: natin config.yml overrides as
before.
Added
havm_disk_usage_bytesPrometheus gauge. Tracks the main disk image's
logical (configured) and allocated (actual APFS) size. Computed on scrape,
no timer overhead.
Changed
-
Event-driven idle — zero polling, zero run-loop drain. After guest boot
completes, no GCD timers, noCFRunLoopRun, norun(mode:before:). Signals
and VZ delegate callbacks arrive through GCD dispatch sources. VM stop is
detected viaVZVirtualMachineDelegateinstead of state polling. -
GitHub API requests use ETag conditional caching. Persists the
ETag
between runs; subsequent requests return304 Not Modifiedwhich costs zero
rate-limit tokens. Only counts against the rate limit when a new HA OS
release is actually published. -
Rate-limited errors show the reset time. The
x-ratelimit-resetUnix
timestamp is parsed and translated into a human‑readable duration
("try again in 6m 24s").
Fixed
- Setup errors use the structured logger instead of raw
fputsto stderr,
matching the log format configured for the session.
havm 0.1.5
Changes
- Restored entropy device and memory balloon — accidentally removed in an earlier CONFIG disk fix. Entropy provides random numbers to the guest kernel for crypto/ASLR; the balloon lets macOS reclaim idle guest memory under pressure.
Shutdown improvements
- Skip REST API shutdown when
api_tokenis empty — no more 401 warnings when a token isn't configured - Graceful shutdown hints — prints a tip listing all three methods when none succeed and no token is set
Documentation site
- New GitHub Pages site at ingmarstein.github.io/havm — dark-themed landing page with real terminal output, feature cards, and multi-page docs
- Documented Homebrew service paths —
/opt/homebrew/etc/havm/config.ymland/opt/homebrew/var/lib/havm/
Full Changelog: v0.1.4...v0.1.5
havm 0.1.4
What's new
- Prometheus metrics — Built-in HTTP endpoint (
GET /metrics) exposing
VM state and USB accessory count. Enable withmetrics.enabled: truein
your config. Scrape with Prometheus and feed into Grafana dashboards or
alerting rules. Includes a/healthendpoint for liveness checks. - Shell completions — bash, zsh, and fish completions via Homebrew's
generate_completions_from_executable. Works out of the box with no
config changes. - Smaller binary — Release binary is ~2.2 MB (was ~4.5 MB). Symbol
tables are stripped before codesigning.
Internal
- Bumped
swift-metricsandswift-prometheusto latest versions - Removed redundant example config file (README is the canonical reference)