How vphone-cli Configures Its Virtual Machine: A Complete Technical Deep Dive
vphone-cli configures its virtual machine by leveraging Apple's Virtualization.framework through a Swift wrapper that builds a VZVirtualMachineConfiguration with CPU, memory, hardware model, graphics device, vsock channel, and optional USB passthrough.
The vphone-cli project demonstrates how to run virtualized iOS devices on Apple Silicon Macs. This article examines the exact VM configuration mechanism, walking through the source files and architecture that make this possible.
Core Architecture Overview
vphone-cli follows a layered design built on top of Apple's native virtualization APIs. The configuration pipeline spans five primary Swift files in the sources/vphone-cli/ directory, each handling a distinct responsibility in the VM lifecycle.
Key Source Files
| File | Responsibility |
|---|---|
| VPhoneVirtualMachine.swift | Builds and manages the VZVirtualMachine instance |
| VPhoneHardwareModel.swift | Supplies the private iOS hardware model via runtime API calls |
| VPhoneVirtualMachineView.swift | Hosts the VZVirtualMachineView UI surface |
| VPhoneControl.swift | Manages the vsock control channel for host-guest communication |
| VPhoneAppDelegate.swift | Entry point that parses CLI arguments and orchestrates VM startup |
Virtual Machine Configuration Pipeline
The VM configuration process follows a strict five-phase pipeline from CLI arguments to running virtualized iPhone.
Phase 1: CLI Argument Parsing
VPhoneAppDelegate.swift uses ArgumentParser to transform command-line flags into structured options. The VPhoneVirtualMachine.Options struct captures all tunable parameters:
import ArgumentParser
struct Options {
var cpus: Int
var memoryGB: Int
var gpuMode: GPUMode // .headless or .windowed
var bootMode: BootMode // .gui or .dfu
var usbPassthrough: [String]
}
Common invocations include vphone-cli boot --cpus 4 --memory 8 --gpu windowed for a standard graphical session, or vphone-cli boot --gpu headless for CI/automation scenarios.
Phase 2: Hardware Model Resolution
Before constructing the VM, VPhoneVirtualMachine calls VPhoneHardwareModel.make() to obtain the required iPhone hardware model. This is where vphone-cli diverges from standard macOS virtualization.
In VPhoneHardwareModel.swift, the code uses the Dynamic library (https://github.com/mhdhejazi/Dynamic) to call private Virtualization.framework APIs at runtime:
import Dynamic
class VPhoneHardwareModel {
static func make() throws -> VZMacHardwareModel {
// Load the PV=3 hardware model for Apple Silicon iOS VMs
let modelData = try Data(contentsOf: Bundle.main.url(
forResource: "vphone.entitlements",
withExtension: nil
)!)
// Runtime invocation of private API via Dynamic
let hardwareModel = Dynamic._VZMacHardwareModel(
data: modelData,
options: [:]
)
return hardwareModel.asAnyObject as! VZMacHardwareModel
}
}
The vphone.entitlements file bundled in the app resources contains the device tree and entitlement blob that identifies this as an iPhone-class VM rather than a generic macOS guest.
Phase 3: Configuration Assembly
The VPhoneVirtualMachine.init(options:) method in VPhoneVirtualMachine.swift assembles the complete VZVirtualMachineConfiguration:
import Virtualization
class VPhoneVirtualMachine {
let virtualMachine: VZVirtualMachine
let virtualMachineConfiguration: VZVirtualMachineConfiguration
init(options: Options) throws {
let config = VZVirtualMachineConfiguration()
// Compute resources
config.cpuCount = options.cpus
config.memorySize = UInt64(options.memoryGB) * 1024 * 1024 * 1024
// Hardware model (iOS-specific, via private API)
config.hardwareModel = try VPhoneHardwareModel.make()
// Graphics device configuration
let graphicsConfiguration = VZMacGraphicsDeviceConfiguration()
graphicsConfiguration.isHeadless = (options.gpuMode == .headless)
config.graphicsDevices = [graphicsConfiguration]
// Vsock device for host-guest control channel
let vsockConfig = VZVirtioVsockDeviceConfiguration()
vsockConfig.port = 1337
config.serialPorts = [vsockConfig]
// Optional USB controller for device passthrough
if !options.usbPassthrough.isEmpty {
let usbConfig = VZUSBControllerConfiguration()
// USB device attachments configured per product/vendor ID
config.usbControllers = [usbConfig]
}
// Validate before instantiation
try config.validate()
self.virtualMachineConfiguration = config
self.virtualMachine = VZVirtualMachine(configuration: config)
}
}
Each configuration property maps directly to Virtualization.framework capabilities:
cpuCountandmemorySize: Exposed hardware resources visible to the iOS guesthardwareModel: The critical iPhone device identity (PV=3) that enables iOS bootinggraphicsDevices:VZMacGraphicsDeviceConfigurationwith headless/windowed toggleserialPorts: Actually configuresVZVirtioVsockDeviceConfigurationfor the control channelusbControllers: Optional passthrough for debugging hardware interaction
Phase 4: VM Instantiation and Startup
With a validated configuration, the VM object is created and started asynchronously:
extension VPhoneVirtualMachine {
func start(completion: ((Result<Void, Error>) -> Void)? = nil) {
virtualMachine.start { result in
completion?(result)
}
}
var state: VZVirtualMachineState {
virtualMachine.state
}
}
The VZVirtualMachine state machine transitions through .starting, .running, .stopped, or .error states. VPhoneVirtualMachineView observes these through a delegate implementation.
Phase 5: UI Integration and Event Forwarding
VPhoneVirtualMachineView.swift embeds the native VZVirtualMachineView into an AppKit window and forwards input:
import Cocoa
class VPhoneVirtualMachineView: NSView {
private let vmView: VZVirtualMachineView
init(virtualMachine: VZVirtualMachine) {
self.vmView = VZVirtualMachineView()
self.vmView.virtualMachine = virtualMachine
super.init(frame: .zero)
self.addSubview(vmView)
// Auto-layout constraints...
}
override func mouseDown(with event: NSEvent) {
vmView.send(event)
}
override func keyDown(with event: NSEvent) {
vmView.send(event)
}
}
This integration enables the interactive "boot (GUI)" target that users invoke with make boot.
Host-Guest Communication via Vsock
A critical aspect of VM configuration is the vsock control channel on port 1337. VPhoneControl.swift implements a length-prefixed JSON protocol over this channel to communicate with vphoned, the daemon running inside the iOS guest:
import Foundation
class VPhoneControl {
private let vsockDevice: VZVirtioVsockDevice
init(vm: VPhoneVirtualMachine) {
self.vsockDevice = vm.virtualMachineConfiguration.serialPorts
.first { $0 is VZVirtioVsockDeviceConfiguration } as! VZVirtioVsockDevice
}
func send(json: [String: Any], completion: @escaping (Result<[String: Any], Error>) -> Void) {
// Serialize JSON with length prefix
let data = try! JSONSerialization.data(withJSONObject: json)
var lengthPrefix = UInt32(data.count).bigEndian
let packet = Data(bytes: &lengthPrefix, count: 4) + data
// Transmit via vsock port 1337
vsockDevice.write(packet, to: 1337) { result in
// Handle response...
}
}
}
This channel supports operations like:
- Application installation (IPA sideloading)
- Screenshot capture
- System logs streaming
- Process lifecycle management
Inspecting a Running VM Configuration
You can programmatically inspect the active configuration of a VPhoneVirtualMachine instance:
let vm = try VPhoneVirtualMachine(options: options)
let config = vm.virtualMachineConfiguration
print("CPU cores: \(config.cpuCount)")
print("Memory: \(config.memorySize / 1_073_741_824) GiB")
if let hwModel = config.hardwareModel {
print("Hardware: \(hwModel.dataRepresentation.count) bytes model data")
}
for (index, graphics) in config.graphicsDevices.enumerated() {
if let macGraphics = graphics as? VZMacGraphicsDeviceConfiguration {
print("Graphics [\(index)]: \(macGraphics.isHeadless ? "headless" : "windowed")")
}
}
for (index, serial) in config.serialPorts.enumerated() {
if let vsock = serial as? VZVirtioVsockDeviceConfiguration {
print("Vsock [\(index)]: port \(vsock.port)")
}
}
Build Integration and Usage
The project uses a Makefile with Swift Package Manager targets. Key commands that exercise the VM configuration:
# Build the CLI
make build
# Boot with GUI (uses windowed GPU mode)
make boot
# Boot headless for automation
./.build/debug/vphone-cli boot --gpu headless
# Install IPA via vsock control channel
./.build/debug/vphone-cli install ./app.ipa
The Swift Package declares platform requirements as .macOS(.v14) since Virtualization.framework's iOS guest support requires macOS Sonoma or later.
Summary
- vphone-cli configures VMs through
VPhoneVirtualMachine.swift, which constructs aVZVirtualMachineConfigurationwith CPU, memory, hardware model, graphics, and vsock settings. - Hardware model resolution requires private APIs, accessed via the Dynamic library in
VPhoneHardwareModel.swiftto obtain the iPhone-specific PV=3 model. - Graphics configuration supports headless and windowed modes through
VZMacGraphicsDeviceConfiguration, enabling both CI and interactive use cases. - The vsock channel on port 1337 provides structured host-guest communication for control operations via
VPhoneControl.swift. - UI integration in
VPhoneVirtualMachineView.swiftwrapsVZVirtualMachineViewfor touch and keyboard event forwarding to the iOS guest.
Frequently Asked Questions
What virtualization technology does vphone-cli use?
vphone-cli uses Apple's Virtualization.framework, specifically the private iOS guest support available on Apple Silicon Macs running macOS Sonoma or later. The framework provides the underlying VZVirtualMachine, VZVirtualMachineConfiguration, and VZMacGraphicsDeviceConfiguration APIs that vphone-cli wraps.
Why does vphone-cli need private APIs for hardware model configuration?
Standard Virtualization.framework APIs only expose macOS guest support. iOS virtualization requires a PV=3 hardware model with specific device tree structures and entitlements that Apple does not publicly document. vphone-cli uses the Dynamic library to call these private APIs at runtime, loading the required model data from its bundled vphone.entitlements resource.
How does the vsock control channel work between host and guest?
The VM configuration includes a VZVirtioVsockDeviceConfiguration on port 1337. The host-side VPhoneControl class implements a length-prefixed JSON protocol over this channel, while the iOS guest runs vphoned (a daemon inside the virtualized system) that receives and responds to commands. This enables IPA installation, screenshots, and log streaming without network stack dependencies.
Can vphone-cli run on Intel Macs or older macOS versions?
No. iOS guest virtualization requires Apple Silicon (M1/M2/M3) and macOS 14 (Sonoma) or later. The Virtualization.framework APIs for iOS guests are unavailable on Intel hardware and were introduced in macOS 14. The Package.swift enforces these constraints through .macOS(.v14) platform requirements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →