aecb0ab76b
updates tailscale/corp#24197 tailmac run now supports the --share option which will allow you to specify a directory on the host which can be mounted in the guest using mount_virtiofs vmshare <path>. Signed-off-by: Jonathan Nobels <jonathan@tailscale.com> |
||
---|---|---|
.. | ||
LICENSE | ||
Swift | ||
TailMac.xcodeproj | ||
Host.entitlements | ||
Makefile | ||
README.md | ||
TailMac.entitlements |
Lightweight macOS VM's for tstest and natlab
This utility is designed to provide custom virtual machine tooling support for macOS. The intent is to quickly create and spin up small, preconfigured virtual machines, for executing integration and unit tests.
The primary driver is to provide support for VZVirtioNetworkDeviceConfiguration which is not supported by other popular macOS VM hosts. This also gives us the freedom to fully customize and script all virtual machine setup and interaction. VZVirtioNetworkDeviceConfiguration lets us directly inject and sink network traffic for simulating various network conditions, protocols, and topologies and ensure that the TailScale clients handle all of these situations correctly.
This may also be used as a drop-in replacement for UTM or Tart on ARM Macs for quickly spinning up test VMs. It has the added benefit that, unlike UTM which uses AppleScript, it can be run via SSH.
This uses Virtualization.framework which only supports arm64. The binaries only build for arm64.
Components
The application is built in two components:
The tailmac command line utility is used to set up and configure VM instances. The Host.app does the heavy lifting.
You will typically initiate all interactions via the tailmac command-line util.
For a full list of options:
tailmac -h
Building
% make all
Will build both the tailmac command line util and Host.app. You will need a developer account. The default bundle identifiers default to TailScale owned ids, so if you don't have (or aren't using) a TailScale dev account, you will need to change this. This should build automatically as long as you have a valid developer cert. Signing is automatic. The binaries both require the virtualization entitlement, so they do need to be signed.
There are separate recipes in the makefile to rebuild the individual components if needed.
All binaries are copied to the bin directory.
Locations
All vm images, restore images, block device files, save states, and other supporting files are persisted at ~/VM.bundle
Each vm gets its own directory. These can be archived for posterity to preserve a particular image and/or state. The mere existence of a directory containing all of the required files in ~/VM.bundle is sufficient for tailmac to be able to see and run it. ~/VM.bundle and it's contents is tailmac's state. No other state is maintained elsewhere.
Each vm has its own custom configuration which can be modified while the vm is idle. It's simple JSON - you may modify this directly, or using 'tailmac configure'.
Installing
Default a parameters
- The default virtio socket device port is 51009
- The default server socket for the virtual network device is /tmp/qemu-dgram.sock
- The default memory size is 4Gb
- The default mac address for the socket based networking is 52:cc:cc:cc:cc:01
- The default mac address for the standard ethernet interface is 52:cc:cc:cc:ce:01
Creating and managing VMs
You generally perform all interactions via the tailmac command line util. A NAT ethernet device is provided so you can ssh into your instance. The ethernet IP will be dhcp assigned by the host and can be determined by parsing the contents of /var/db/dhcpd_leases
Creation
To create a new VM (this will grab a restore image for what apples deems a 'latest; if needed). Restore images are large (on the order of 10 Gb) and installation after downloading takes a few minutes. If you wish to use a custom restore image, specify it with the --image option. If RestoreImage.ipsw exists in ~/VM.bundle, it will be used. macOS versions from 12 to 15 have been tested and appear to work correctly.
tailmac create --id my_vm_id
With a custom restore image and parameters:
tailmac create --id my_custom_vm_id --image "/images/macos_ventura.ipsw" --mac 52:cc:cc:cc:cc:07 --mem 8000000000 --sock "/temp/custom.sock" --port 52345
A typical workflow would be to create single VM, manually set it up the way you wish including the installation of any required client side software (tailscaled or the client-side test harness for example) then clone that images as required and back up your images for future use.
Fetching and persisting pre-configured images is left as an exercise for the reader (for now). A previously used image can simply be copied to the ~/VM.bundle directory under a unique path and tailmac will automatically pick it up. No versioning is supported so old images may stop working in the future.
To delete a VM image, you may simply remove it's directory under ~/VM.bundle or
tailmac delete --id my_stale_vm
Note that the disk size is fixed, but should be sufficient (perhaps even excessive) for most lightweight workflows.
Restore Images
To refresh an existing restore image:
tailmac refresh
Restore images can also be obtained directly from Apple for all macOS releases. Note Apple restore images are raw installs, and the OS will require configuration, user setup, etc before being useful. Cloning a vm after clicking through the setup, creating a user and disabling things like the lock screen and enabling auto-login will save you time in the future.
Cloning
To clone an existing vm (this will clone the mac and port as well)
tailmac clone --id old_vm_id --target-id new_vm_id
Configuration
To reconfigure a existing vm:
tailmac configure --id vm_id --mac 11:22:33:44:55:66 --port 12345 --ethermac 22:33:44:55:66:77 -sock "/tmp/my.sock"
Running a VM
To list the available VM images
tailmac ls
To launch an VM
tailmac run --id machine_1
You may invoke multiple vms, but the limit on the number of concurrent instances is on the order of 2. Use the --tail option to watch the stdout of the Host.app process. There is currently no way to list the running VM instances, but invoking stop or halt for a vm instance that is not running is perfectly safe.
To gracefully stop a running VM and save its state (this is a fire and forget thing):
tailmac stop --id machine_1
Manually closing a VM's window will save the VM's state (if possible) and is the equivalent of running 'tailmac stop --id vm_id'
To halt a running vm without saving its state:
tailmac halt --id machine_1