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:

  • cpuCount and memorySize: Exposed hardware resources visible to the iOS guest
  • hardwareModel: The critical iPhone device identity (PV=3) that enables iOS booting
  • graphicsDevices: VZMacGraphicsDeviceConfiguration with headless/windowed toggle
  • serialPorts: Actually configures VZVirtioVsockDeviceConfiguration for the control channel
  • usbControllers: 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 a VZVirtualMachineConfiguration with CPU, memory, hardware model, graphics, and vsock settings.
  • Hardware model resolution requires private APIs, accessed via the Dynamic library in VPhoneHardwareModel.swift to 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.swift wraps VZVirtualMachineView for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →