guix system on the macos virtualization framework
Resources
Started with swift code examples from:
- https://developer.apple.com/documentation/virtualization/running_gui_linux_in_a_virtual_machine_on_a_mac
- https://github.com/traderepublic/Cilicon
- https://github.com/cirruslabs/tart/blob/546238d9df688442d1f74185aff3152408d5a358/Sources/tart/Commands/Run.swift#L483
- https://gist.github.com/karwa/5207e232ac9ec53f0276252ab5e3ee07
filesystem corruption bug
Running into kernel panic caused by file system corruption
Sway software rendering
- https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/docs/env_vars.md
- Encountered visual glitches with llvmpipe
- pixman renderer worked better, but scaling was bad
VM’s assigned IP
- https://github.com/utmapp/UTM/issues/3294
- https://discussions.apple.com/thread/7993351
- https://github.com/lima-vm/lima/pull/1207
- Indicates multiple vm won’t be able to talk to eachother, probably not a huge issue and can always ssh bridge
Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleExecutable</key> <string>guix</string> <key>CFBundleIconFile</key> <string>guix.icns</string> <key>CFBundleGetInfoString</key> <string>guix alpha</string> <key>CFBundleIdentifier</key> <string>com.akirakyle.guix</string> <key>CFBundleName</key> <string>Guix</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> </dict> </plist>
entitlements.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.virtualization</key> <true/> </dict> </plist>
doit.sh
swiftc vm-appkit.swift -o ../Guix.app/Contents/MacOS/guix codesign --entitlements entitlements.plist --verify -s - ../Guix.app
ls -R Guix.app
vm-appkit.swift
import AppKit import Virtualization let basePath = NSHomeDirectory() + "/virt/" func vmConfig() throws -> VZVirtualMachineConfiguration { let virtualMachineConfiguration = VZVirtualMachineConfiguration() virtualMachineConfiguration.cpuCount = 4 virtualMachineConfiguration.memorySize = (4 * 1024 * 1024 * 1024) as UInt64 // 4 GiB let platform = VZGenericPlatformConfiguration() let machineIdentifierData = try Data(contentsOf: URL(fileURLWithPath: basePath + "MachineIdentifier")) platform.machineIdentifier = VZGenericMachineIdentifier(dataRepresentation: machineIdentifierData)! virtualMachineConfiguration.platform = platform let bootloader = VZEFIBootLoader() bootloader.variableStore = VZEFIVariableStore(url: URL(fileURLWithPath: basePath + "NVRAM")) virtualMachineConfiguration.bootLoader = bootloader let guixDiskAttachment = try VZDiskImageStorageDeviceAttachment( url: URL(fileURLWithPath: basePath + "guix.img"), readOnly: false, cachingMode: .cached, synchronizationMode: .full) //let homeDiskAttachment = try VZDiskImageStorageDeviceAttachment( // url: URL(fileURLWithPath: basePath + "Disk.img"), readOnly: false, // cachingMode: .cached, synchronizationMode: .full) virtualMachineConfiguration.storageDevices = [ VZVirtioBlockDeviceConfiguration(attachment: guixDiskAttachment) //VZVirtioBlockDeviceConfiguration(attachment: homeDiskAttachment) ] let shareURL = URL(fileURLWithPath: NSHomeDirectory() + "/vm-share") let sharedDirectory = VZSharedDirectory(url: shareURL, readOnly: false) let singleDirectoryShare = VZSingleDirectoryShare(directory: sharedDirectory) let sharingConfiguration = VZVirtioFileSystemDeviceConfiguration(tag: "share") sharingConfiguration.share = singleDirectoryShare virtualMachineConfiguration.directorySharingDevices = [ sharingConfiguration ] let networkDevice = VZVirtioNetworkDeviceConfiguration() networkDevice.attachment = VZNATNetworkDeviceAttachment() virtualMachineConfiguration.networkDevices = [networkDevice] virtualMachineConfiguration.keyboards = [VZUSBKeyboardConfiguration()] virtualMachineConfiguration.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()] let graphicsDevice = VZVirtioGraphicsDeviceConfiguration() graphicsDevice.scanouts = [ //VZVirtioGraphicsScanoutConfiguration(widthInPixels: 2880, heightInPixels: 1800) VZVirtioGraphicsScanoutConfiguration(widthInPixels: 800, heightInPixels: 600) ] virtualMachineConfiguration.graphicsDevices = [graphicsDevice] try virtualMachineConfiguration.validate() return virtualMachineConfiguration } class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { var vm = VZVirtualMachine(configuration: try! vmConfig()) let vmView = VZVirtualMachineView() let window = NSWindow() func applicationDidFinishLaunching(_ aNotification: Notification) { Task { try await self.vm.start() } vm.delegate = self vmView.virtualMachine = vm //vmView.layerContentsPlacement = NSView.LayerContentsPlacement.center //vmView.wantsLayer = false //self.vmView.capturesSystemKeys = true //self.vmView.automaticallyReconfiguresDisplay = true //print("vmView.frame.size is \(vmView.frame)") //vmView.frame.size = NSMakeSize(1440, 900) self.window.setContentSize(NSSize(width:800, height:600)) //self.window.setContentSize(vmView.frame.size) self.window.styleMask = [.titled, .closable, .resizable] //self.window.styleMask = [.titled, .fullSizeContentView] self.window.title = "VM" window.contentView = vmView //window.contentView?.addSubview(vmView) window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) print("started") //print("vmView is \(vmView)") //print("vmView.superview is \(vmView.superview!)") //print("vmView.subviews is \(vmView.subviews)") //print("vmView.frame is \(vmView.frame)") //print("vmView.bounds is \(vmView.bounds)") //print("centerScanRect is \(vmView.centerScanRect(vmView.frame))") //print("layerContentsPlacement is \(vmView.layerContentsPlacement)") //print("wantsLayer is \(vmView.wantsLayer)") //print("autoresizingMask is \(vmView.autoresizingMask)") //print("autoresizesSubviews is \(vmView.autoresizesSubviews)") //vmView.layerContentsPlacement = NSView.LayerContentsPlacement.center //print("layerContentsPlacement is \(vmView.layerContentsPlacement)") //let fbView = vmView.subviews[0] //print("fbView.frame is \(fbView.frame)") //print("fbView.bounds is \(fbView.bounds)") //print("centerScanRect is \(fbView.centerScanRect(fbView.frame))") //print("layerContentsPlacement is \(fbView.layerContentsPlacement)") //print("wantsLayer is \(fbView.wantsLayer)") //print("autoresizingMask is \(fbView.autoresizingMask)") //print("autoresizesSubviews is \(fbView.autoresizesSubviews)") //fbView.layerContentsPlacement = NSView.LayerContentsPlacement.center //print("layerContentsPlacement is \(fbView.layerContentsPlacement)") } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } func virtualMachine(_ virtualMachine: VZVirtualMachine, networkDevice: VZNetworkDevice, attachmentWasDisconnectedWithError error: Error) { print("network attachment disconnected with error: \(error.localizedDescription)") } func virtualMachine(_ virtualMachine: VZVirtualMachine, didStopWithError error: Error) { print("vm stopped with error: \(error.localizedDescription)") exit(-1) } func guestDidStop(_ virtualMachine: VZVirtualMachine) { print("vm stopped by guest") exit(0) } } let app = NSApplication.shared let del = AppDelegate() app.delegate = del app.run()
mk-vm-files.swift
import Virtualization let basePath = NSHomeDirectory() + "/virt/" let machineIdentifierPath = basePath + "MachineIdentifier" let efiVariableStorePath = basePath + "NVRAM" let mainDiskImagePath = basePath + "Disk.img" let mainDiskSize = 32 * 1024 * 1024 * 1024 as UInt64 // 32 GiB let machineIdentifier = VZGenericMachineIdentifier() try machineIdentifier.dataRepresentation.write(to: URL(fileURLWithPath: machineIdentifierPath)) let efiVariableStore = try VZEFIVariableStore(creatingVariableStoreAt: URL(fileURLWithPath: efiVariableStorePath)) let diskCreated = FileManager.default.createFile(atPath: mainDiskImagePath, contents: nil, attributes: nil) let mainDiskFileHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: mainDiskImagePath)) try mainDiskFileHandle.truncate(atOffset: mainDiskSize)
Bootstrap guix
https://www.debian.org/CD/http-ftp/ used netinstaller to install debian on Disk.img
python3 -m http.server to transfer guix-sys-config
apt install parted apt install dosfstools apt install guix guix pull parted /dev/vdb mklabel gpt mkpart primary ext4 512MiB 100% mkpart primary fat32 0% 512MiB set 2 esp on mkfs.fat -n boot -F32 /dev/vdb2 mkfs.ext4 -L guixsd /dev/vdb1 mount LABEL=guixsd /mnt mkdir -p /mnt/boot mount LABEL=boot /mnt/boot wget 192.168.65.1:8000/guix-sys-config.scm
Potentially next time easier to just use my server to create the system image directly via https://guix.gnu.org/manual/devel/en/guix.html#System-Images
guix-sys-config.scm
(use-modules (gnu) (guix packages) (guix utils) (gnu bootloader)) (use-service-modules linux networking ssh desktop dbus nfs) (use-package-modules disk certs wget version-control rsync compression ssh admin vim tmux virtualization bootloaders) (operating-system (host-name "data") (timezone "America/Denver") (locale "en_US.utf8") (keyboard-layout (keyboard-layout "us" "dvorak")) (bootloader (bootloader-configuration (bootloader grub-efi-bootloader) (targets '("/boot")) (keyboard-layout keyboard-layout))) (file-systems (append (list (file-system (device (file-system-label "boot")) (mount-point "/boot") (type "vfat")) (file-system (device (file-system-label "guixsd")) (mount-point "/") (type "ext4"))) %base-file-systems)) (users (cons (user-account (name "akyle") (group "users") (supplementary-groups '("wheel" "audio" "video"))) %base-user-accounts)) ;; Globally-installed packages. (packages (append (list parted wget git rsync unzip openssh-sans-x vim htop tmux) %base-packages)) (services (append (list (service ntp-service-type) (service dhcp-client-service-type) (service openssh-service-type (openssh-configuration (openssh openssh-sans-x) (port-number 15213) (password-authentication? #f) (use-pam? #f))) ) (modify-services %base-services (guix-service-type config => (guix-configuration (inherit config) (extra-options '("--gc-keep-derivations=yes" "--gc-keep-outputs=yes")) ))) )))