initial commit

This commit is contained in:
Chad Nelson 2023-09-16 10:08:05 -06:00
commit bccff31856
11 changed files with 876 additions and 0 deletions

View file

@ -0,0 +1,94 @@
import Foundation
class FileDownloader {
static func loadFileSync(url: URL, completion: @escaping (String?, Error?) -> Void)
{
if #available(macOS 13.0, *) {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let guixUrl = URL(filePath: NSHomeDirectory() + "/.guix/guix.bundle/guix.tar.gz")
let destinationUrl = guixUrl
if FileManager().fileExists(atPath: destinationUrl.path)
{
print("File already exists [\(destinationUrl.path)]")
completion(destinationUrl.path, nil)
}
else if let dataFromURL = NSData(contentsOf: url)
{
if dataFromURL.write(to: destinationUrl, atomically: true)
{
print("file saved [\(destinationUrl.path)]")
completion(destinationUrl.path, nil)
}
else
{
print("error saving file")
let error = NSError(domain:"Error saving file", code:1001, userInfo:nil)
completion(destinationUrl.path, error)
}
}
else
{
let error = NSError(domain:"Error downloading file", code:1002, userInfo:nil)
completion(destinationUrl.path, error)
}
}
}
static func loadFileAsync(url: URL, completion: @escaping (String?, Error?) -> Void)
{
if #available(macOS 13.0, *) {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationUrl = documentsUrl.appendingPathComponent(url.lastPathComponent)
if FileManager().fileExists(atPath: destinationUrl.path)
{
print("File already exists [\(destinationUrl.path)]")
completion(destinationUrl.path, nil)
}
else
{
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = session.dataTask(with: request, completionHandler:
{
data, response, error in
if error == nil
{
if let response = response as? HTTPURLResponse
{
if response.statusCode == 200
{
if let data = data
{
if let _ = try? data.write(to: destinationUrl, options: Data.WritingOptions.atomic)
{
completion(destinationUrl.path, error)
}
else
{
completion(destinationUrl.path, error)
}
}
else
{
completion(destinationUrl.path, error)
}
}
}
}
else
{
completion(destinationUrl.path, error)
}
})
task.resume()
}
}
}
}

View file

@ -0,0 +1,8 @@
<?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>

View file

@ -0,0 +1,281 @@
/*
See LICENSE folder for this samples licensing information.
Abstract:
A command-line utility that runs Linux in a virtual machine.
*/
import Foundation
import Virtualization
class Delegate: NSObject {
}
class MySocketListenerDelegate: NSObject, VZVirtioSocketListenerDelegate {
func listener(_ listener: VZVirtioSocketListener, shouldAcceptNewConnection connection: VZVirtioSocketConnection, from device: VZVirtioSocketDevice) -> Bool {
// Implement your logic to decide whether to accept the new connection
// For example, you may always accept:
return true
// Or, you might want to check some properties of the connection or device and decide based on that
}
}
extension Delegate: VZVirtualMachineDelegate {
func guestDidStop(_ virtualMachine: VZVirtualMachine) {
print("The guest shut down. Exiting.")
exit(EXIT_SUCCESS)
}
}
if #available(macOS 13.0, *) {
let vmBundlePath = NSHomeDirectory() + "/.guix/guix.bundle/"
let mainDiskImagePath = vmBundlePath + "guix-raw.img"
let efiVariableStorePath = vmBundlePath + "NVRAM"
let machineIdentifierPath = vmBundlePath + "MachineIdentifier"
var needsInstall = true
// MARK: Parse the Command Line
// MARK: Create the Virtual Machine Configuration
print(shell("/bin/zsh ~/.guix/startup-script.sh"))
if !FileManager.default.fileExists(atPath: vmBundlePath) {
needsInstall = true
// createVMBundle()
print(shell("mkdir ~/.guix && mkdir ~/.guix/home"))
print(shell("cp -r /Applications/Guix.app/Contents/Resources/home/ssh-cert ~/.guix/"))
print(shell("cp /Applications/Guix.app/Contents/Resources/home/*.sh ~/.guix/"))
do {
try FileManager.default.createDirectory(atPath: vmBundlePath, withIntermediateDirectories: false)
} catch {
fatalError("Failed to create “GUI Linux VM.bundle.”")
}
print("The main system image is now downloading. This may take a few minutes...")
// createMainDiskImage()
let url = URL(string: "https://objectstorage.us-phoenix-1.oraclecloud.com/n/axfgkze2xif1/b/guix-system/o/msg-system-aarch64guix-raw.img.tar.gz")
FileDownloader.loadFileSync(url: url!) { (path, error) in
print("File downloaded to : \(path!)")
}
print(shell("tar -xvzf ~/.guix/guix.bundle/guix.tar.gz -C ~/.guix/guix.bundle/"))
//print(shell("/opt/homebrew/bin/qemu-img convert ~/.guix/guix.bundle/guix-raw.qcow2 ~/.guix/guix.bundle/guix-raw.img"))
// configureAndStartVirtualMachine()
} else {
needsInstall = false
// configureAndStartVirtualMachine()
}
let configuration = VZVirtualMachineConfiguration()
let platform = VZGenericPlatformConfiguration()
let bootloader = VZEFIBootLoader()
let disksArray = NSMutableArray()
//set cpu count
let totalAvailableCPUs = ProcessInfo.processInfo.processorCount
var virtualCPUCount = totalAvailableCPUs <= 1 ? 1 : totalAvailableCPUs - 1
virtualCPUCount = max(virtualCPUCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount)
virtualCPUCount = min(virtualCPUCount, VZVirtualMachineConfiguration.maximumAllowedCPUCount)
configuration.cpuCount = virtualCPUCount
//set memory size
var memorySize = (4 * 1024 * 1024 * 1024) as UInt64 // 4 GiB
memorySize = max(memorySize, VZVirtualMachineConfiguration.minimumAllowedMemorySize)
memorySize = min(memorySize, VZVirtualMachineConfiguration.maximumAllowedMemorySize)
configuration.memorySize = memorySize // 2 GiB
if needsInstall {
// This is a fresh install: Create a new machine identifier and EFI variable store,
// and configure a USB mass storage device to boot the ISO image.
let machineIdentifier = VZGenericMachineIdentifier()
// Store the machine identifier to disk so you can retrieve it for subsequent boots.
try! machineIdentifier.dataRepresentation.write(to: URL(fileURLWithPath: machineIdentifierPath))
platform.machineIdentifier = machineIdentifier
guard let efiVariableStore = try? VZEFIVariableStore(creatingVariableStoreAt: URL(fileURLWithPath: efiVariableStorePath)) else {
fatalError("Failed to create the EFI variable store.")
}
bootloader.variableStore = efiVariableStore
// disksArray.add(createUSBMassStorageDeviceConfiguration())
} else {
// The VM is booting from a disk image that already has the OS installed.
// Retrieve the machine identifier and EFI variable store that were saved to
// disk during installation.
// Retrieve the machine identifier.
guard let machineIdentifierData = try? Data(contentsOf: URL(fileURLWithPath: machineIdentifierPath)) else {
fatalError("Failed to retrieve the machine identifier data.")
}
guard let machineIdentifier = VZGenericMachineIdentifier(dataRepresentation: machineIdentifierData) else {
fatalError("Failed to create the machine identifier.")
}
platform.machineIdentifier = machineIdentifier
if !FileManager.default.fileExists(atPath: efiVariableStorePath) {
fatalError("EFI variable store does not exist.")
}
bootloader.variableStore = VZEFIVariableStore(url: URL(fileURLWithPath: efiVariableStorePath))
}
configuration.platform = platform
configuration.bootLoader = bootloader
// Disk management
guard let mainDiskAttachment = try? VZDiskImageStorageDeviceAttachment(url: URL(fileURLWithPath: mainDiskImagePath), readOnly: false) else {
fatalError("Failed to create main disk attachment.")
}
let mainDisk = VZVirtioBlockDeviceConfiguration(attachment: mainDiskAttachment)
disksArray.add(mainDisk)
guard let disks = disksArray as? [VZStorageDeviceConfiguration] else {
fatalError("Invalid disksArray.")
}
configuration.storageDevices = disks
// Setup network
let networkDevice = VZVirtioNetworkDeviceConfiguration()
networkDevice.attachment = VZNATNetworkDeviceAttachment()
configuration.networkDevices = [networkDevice]
// TODO: setup audioDevices
// Shared dirs
let projectsURL = URL(fileURLWithPath: "/Users")
let sharedDirectory = VZSharedDirectory(url: projectsURL, readOnly: false)
let singleDirectoryShare = VZSingleDirectoryShare(directory: sharedDirectory)
// Create the VZVirtioFileSystemDeviceConfiguration and assign it a unique tag.
let sharingConfiguration = VZVirtioFileSystemDeviceConfiguration(tag: "Users")
sharingConfiguration.share = singleDirectoryShare
configuration.directorySharingDevices = [sharingConfiguration]
//configuration.serialPorts = [ createConsoleConfiguration() ]
//configuration.bootLoader = createBootLoader(kernelURL: kernelURL, initialRamdiskURL: initialRamdiskURL)
let inputAudioDevice = VZVirtioSoundDeviceConfiguration()
let inputStream = VZVirtioSoundDeviceInputStreamConfiguration()
inputStream.source = VZHostAudioInputStreamSource()
inputAudioDevice.streams = [inputStream]
let outputAudioDevice = VZVirtioSoundDeviceConfiguration()
let outputStream = VZVirtioSoundDeviceOutputStreamConfiguration()
outputStream.sink = VZHostAudioOutputStreamSink()
outputAudioDevice.streams = [outputStream]
configuration.audioDevices = [inputAudioDevice, outputAudioDevice]
// configuration.socketDevices = [VZVirtioSocketDeviceConfiguration()]
//
//
do {
try configuration.validate()
} catch {
print("Failed to validate the virtual machine configuration. \(error)")
exit(EXIT_FAILURE)
}
// MARK: Instantiate and Start the Virtual Machine
let virtualMachine = VZVirtualMachine(configuration: configuration)
// let socketDevice = virtualMachine.socketDevices[0] as! VZVirtioSocketDevice
let delegate = Delegate()
// let socketListener = VZVirtioSocketListener()
// let socketDelegate = MySocketListenerDelegate()
// socketListener.delegate = socketDelegate
background(delay: 3.0, background: {
print(shell("/bin/zsh ~/.guix/running-script.sh"))
}, completion: {
print("Guix is now running and Graphical apps can be forwarded (if xquartz was installed)")
// when background job finishes, wait 3 seconds and do something in main thread
})
DispatchQueue.main.async {
virtualMachine.delegate = delegate
// socketDevice.setSocketListener(socketListener, forPort: 80)
virtualMachine.start { (result) in
if case let .failure(error) = result {
print("Failed to start the virtual machine. \(error)")
exit(EXIT_FAILURE)
}
// if case .success() = result {
//
// sleep(30)
//
// socketDevice.connect(toPort: 80) { result in
// dump(result)
// }
// }
}
}
RunLoop.main.run(until: Date.distantFuture)
// MARK: - Virtual Machine Delegate
func shell(_ command: String) -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.standardInput = nil
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
DispatchQueue.global(qos: .background).async {
background?()
if let completion = completion {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
completion()
})
}
}
}
} else {
print("only available on macos 13 or later")
}