From 19a07d24d3c1207aa0809c77975087d92c070b0a Mon Sep 17 00:00:00 2001 From: f-trycua Date: Fri, 31 Jan 2025 16:09:20 +0100 Subject: [PATCH] Initial public release --- .gitignore | 76 ++ .vscode/launch.json | 227 ++++++ .vscode/tasks.json | 18 + CONTRIBUTING.md | 39 + LICENSE | 21 + Package.resolved | 69 ++ Package.swift | 35 + README.md | 140 ++++ docs/API-Reference.md | 226 ++++++ docs/Development.md | 45 ++ docs/FAQ.md | 37 + img/cli.png | Bin 0 -> 382457 bytes img/logo_black.png | Bin 0 -> 4799 bytes img/logo_white.png | Bin 0 -> 4260 bytes resources/lume.entitlements | 8 + scripts/build/build-debug.sh | 4 + scripts/build/build-release-notarized.sh | 99 +++ scripts/build/build-release.sh | 15 + scripts/ghcr/pull-ghcr.sh | 205 +++++ scripts/ghcr/push-ghcr.sh | 208 +++++ src/Commands/Clone.swift | 22 + src/Commands/Create.swift | 52 ++ src/Commands/Delete.swift | 31 + src/Commands/Get.swift | 21 + src/Commands/IPSW.swift | 20 + src/Commands/Images.swift | 19 + src/Commands/List.swift | 19 + src/Commands/Prune.swift | 19 + src/Commands/Pull.swift | 30 + src/Commands/Run.swift | 97 +++ src/Commands/Serve.swift | 17 + src/Commands/Set.swift | 34 + src/Commands/Stop.swift | 20 + .../ImageContainerRegistry.swift | 764 ++++++++++++++++++ src/ContainerRegistry/ImageList.swift | 4 + src/ContainerRegistry/ImagesPrinter.swift | 42 + src/Errors/Errors.swift | 157 ++++ src/FileSystem/Home.swift | 146 ++++ src/FileSystem/VMConfig.swift | 141 ++++ src/FileSystem/VMDirectory.swift | 181 +++++ src/LumeController.swift | 514 ++++++++++++ src/Main.swift | 43 + src/Server/HTTP.swift | 113 +++ src/Server/Handlers.swift | 316 ++++++++ src/Server/Requests.swift | 86 ++ src/Server/Responses.swift | 25 + src/Server/Server.swift | 281 +++++++ src/Utils/CommandRegistry.swift | 21 + src/Utils/CommandUtils.swift | 6 + src/Utils/Logger.swift | 29 + src/Utils/NetworkUtils.swift | 24 + src/Utils/Path.swift | 46 ++ src/Utils/ProcessRunner.swift | 15 + src/Utils/ProgressLogger.swift | 18 + src/Utils/String.swift | 7 + src/Utils/Utils.swift | 57 ++ src/VM/DarwinVM.swift | 84 ++ src/VM/LinuxVM.swift | 55 ++ src/VM/VM.swift | 390 +++++++++ src/VM/VMDetails.swift | 41 + src/VM/VMDetailsPrinter.swift | 61 ++ src/VM/VMDisplayResolution.swift | 28 + src/VM/VMFactory.swift | 37 + src/VNC/PassphraseGenerator.swift | 19 + src/VNC/VNCService.swift | 70 ++ src/Virtualization/DHCPLeaseParser.swift | 109 +++ src/Virtualization/DarwinImageLoader.swift | 113 +++ src/Virtualization/ImageLoaderFactory.swift | 17 + .../VMVirtualizationService.swift | 329 ++++++++ tests/Mocks/MockVM.swift | 30 + tests/Mocks/MockVMVirtualizationService.swift | 65 ++ tests/Mocks/MockVNCService.swift | 42 + tests/VMTests.swift | 187 +++++ tests/VMVirtualizationServiceTests.swift | 68 ++ tests/VNCServiceTests.swift | 86 ++ 75 files changed, 6740 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 README.md create mode 100644 docs/API-Reference.md create mode 100644 docs/Development.md create mode 100644 docs/FAQ.md create mode 100644 img/cli.png create mode 100644 img/logo_black.png create mode 100644 img/logo_white.png create mode 100644 resources/lume.entitlements create mode 100755 scripts/build/build-debug.sh create mode 100755 scripts/build/build-release-notarized.sh create mode 100755 scripts/build/build-release.sh create mode 100755 scripts/ghcr/pull-ghcr.sh create mode 100755 scripts/ghcr/push-ghcr.sh create mode 100644 src/Commands/Clone.swift create mode 100644 src/Commands/Create.swift create mode 100644 src/Commands/Delete.swift create mode 100644 src/Commands/Get.swift create mode 100644 src/Commands/IPSW.swift create mode 100644 src/Commands/Images.swift create mode 100644 src/Commands/List.swift create mode 100644 src/Commands/Prune.swift create mode 100644 src/Commands/Pull.swift create mode 100644 src/Commands/Run.swift create mode 100644 src/Commands/Serve.swift create mode 100644 src/Commands/Set.swift create mode 100644 src/Commands/Stop.swift create mode 100644 src/ContainerRegistry/ImageContainerRegistry.swift create mode 100644 src/ContainerRegistry/ImageList.swift create mode 100644 src/ContainerRegistry/ImagesPrinter.swift create mode 100644 src/Errors/Errors.swift create mode 100644 src/FileSystem/Home.swift create mode 100644 src/FileSystem/VMConfig.swift create mode 100644 src/FileSystem/VMDirectory.swift create mode 100644 src/LumeController.swift create mode 100644 src/Main.swift create mode 100644 src/Server/HTTP.swift create mode 100644 src/Server/Handlers.swift create mode 100644 src/Server/Requests.swift create mode 100644 src/Server/Responses.swift create mode 100644 src/Server/Server.swift create mode 100644 src/Utils/CommandRegistry.swift create mode 100644 src/Utils/CommandUtils.swift create mode 100644 src/Utils/Logger.swift create mode 100644 src/Utils/NetworkUtils.swift create mode 100644 src/Utils/Path.swift create mode 100644 src/Utils/ProcessRunner.swift create mode 100644 src/Utils/ProgressLogger.swift create mode 100644 src/Utils/String.swift create mode 100644 src/Utils/Utils.swift create mode 100644 src/VM/DarwinVM.swift create mode 100644 src/VM/LinuxVM.swift create mode 100644 src/VM/VM.swift create mode 100644 src/VM/VMDetails.swift create mode 100644 src/VM/VMDetailsPrinter.swift create mode 100644 src/VM/VMDisplayResolution.swift create mode 100644 src/VM/VMFactory.swift create mode 100644 src/VNC/PassphraseGenerator.swift create mode 100644 src/VNC/VNCService.swift create mode 100644 src/Virtualization/DHCPLeaseParser.swift create mode 100644 src/Virtualization/DarwinImageLoader.swift create mode 100644 src/Virtualization/ImageLoaderFactory.swift create mode 100644 src/Virtualization/VMVirtualizationService.swift create mode 100644 tests/Mocks/MockVM.swift create mode 100644 tests/Mocks/MockVMVirtualizationService.swift create mode 100644 tests/Mocks/MockVNCService.swift create mode 100644 tests/VMTests.swift create mode 100644 tests/VMVirtualizationServiceTests.swift create mode 100644 tests/VNCServiceTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..244c29a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +.DS_Store + +## User settings +xcuserdata/ + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Local environment variables +.env.local + +# Ignore folder +ignore + +# .release +.release/ + +# Swift Package Manager +.swiftpm/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..413ca02a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,227 @@ +{ + "configurations": [ + { + "type": "bashdb", + "request": "launch", + "name": "Bash-Debug (select script from list of sh files)", + "cwd": "${workspaceFolder}", + "program": "${command:SelectScriptName}", + "pathBash": "/opt/homebrew/bin/bash", + "args": [] + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "serve" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume serve", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "create", + "macos-vm", + "--cpu", + "4", + "--memory", + "4GB", + "--disk-size", + "40GB", + "--ipsw", + "/Users//Downloads/UniversalMac_15.2_24C101_Restore.ipsw" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume create --os macos --ipsw 'path/to/ipsw' (macos)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "create", + "macos-vm", + "--cpu", + "4", + "--memory", + "4GB", + "--disk-size", + "20GB", + "--ipsw", + "latest" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume create --os macos --ipsw latest (macos)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "create", + "linux-vm", + "--os", + "linux", + "--cpu", + "4", + "--memory", + "4GB", + "--disk-size", + "20GB" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume create --os linux (linux)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "pull", + "macos-sequoia-vanilla:15.2", + "--name", + "macos-vm-cloned" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume pull (macos)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "run", + "macos-vm", + "--shared-dir", + "/Users//repos/trycua/lume/shared_folder:rw", + "--start-vnc" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume run (macos)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "run", + "linux-vm", + "--start-vnc", + "--mount", + "/Users//Downloads/ubuntu-24.04.1-live-server-arm64.iso" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume run with setup mount (linux)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "run", + "linux-vm", + "--start-vnc" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume run (linux)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "get", + "macos-vm" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume get (macos)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "ls" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume ls", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "sourceLanguages": [ + "swift" + ], + "args": [ + "stop", + "macos-vm" + ], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume stop (macos)", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "build-debug" + }, + { + "type": "lldb", + "request": "launch", + "args": [], + "cwd": "${workspaceFolder:lume}", + "name": "Debug lume", + "program": "${workspaceFolder:lume}/.build/debug/lume", + "preLaunchTask": "swift: Build Debug lume" + }, + { + "type": "lldb", + "request": "launch", + "args": [], + "cwd": "${workspaceFolder:lume}", + "name": "Release lume", + "program": "${workspaceFolder:lume}/.build/release/lume", + "preLaunchTask": "swift: Build Release lume" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..0640efba --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build-debug", + "type": "shell", + "command": "${workspaceFolder:lume}/scripts/build/build-debug.sh", + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "silent" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1e4c2457 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to lume + +We deeply appreciate your interest in contributing to lume! Whether you're reporting bugs, suggesting enhancements, improving docs, or submitting pull requests, your contributions help improve the project for everyone. + +## Reporting Bugs + +If you've encountered a bug in the project, we encourage you to report it. Please follow these steps: + +1. **Check the Issue Tracker**: Before submitting a new bug report, please check our issue tracker to see if the bug has already been reported. +2. **Create a New Issue**: If the bug hasn't been reported, create a new issue with: + - A clear title and detailed description + - Steps to reproduce the issue + - Expected vs actual behavior + - Your environment (macOS version, lume version) + - Any relevant logs or error messages +3. **Label Your Issue**: Label your issue as a `bug` to help maintainers identify it quickly. + +## Suggesting Enhancements + +We're always looking for suggestions to make lume better. If you have an idea: + +1. **Check Existing Issues**: See if someone else has already suggested something similar. +2. **Create a New Issue**: If your enhancement is new, create an issue describing: + - The problem your enhancement solves + - How your enhancement would work + - Any potential implementation details + - Why this enhancement would benefit lume users + +## Documentation + +Documentation improvements are always welcome. You can: +- Fix typos or unclear explanations +- Add examples and use cases +- Improve API documentation +- Add tutorials or guides + +For detailed instructions on setting up your development environment and submitting code contributions, please see our [Development.md](docs/Development.md) guide. + +Feel free to join our [Discord community](https://discord.com/channels/1328377437301641247) to discuss ideas or get help with your contributions. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..dc94027f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 trycua + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 00000000..cc5d9688 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,69 @@ +{ + "originHash" : "81a9d169da3c391b981b894044911091d11285486aab463e32222490c931ba45", + "pins" : [ + { + "identity" : "dynamic", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mhdhejazi/Dynamic", + "state" : { + "branch" : "master", + "revision" : "772883073d044bc754d401cabb6574624eb3778f" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-cmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-cmark.git", + "state" : { + "revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53", + "version" : "0.5.0" + } + }, + { + "identity" : "swift-format", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-format.git", + "state" : { + "branch" : "release/5.10", + "revision" : "3191b8f3109730af449c6332d0b1ca6653b857a0" + } + }, + { + "identity" : "swift-markdown", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-markdown.git", + "state" : { + "revision" : "8f79cb175981458a0a27e76cb42fee8e17b1a993", + "version" : "0.5.0" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "branch" : "release/5.10", + "revision" : "cdd571f366a4298bb863a9dcfe1295bb595041d5" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..da6db543 --- /dev/null +++ b/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "lume", + platforms: [ + .macOS(.v14) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.1"), + .package(url: "https://github.com/apple/swift-format.git", branch: ("release/5.10")), + .package(url: "https://github.com/apple/swift-atomics.git", .upToNextMajor(from: "1.2.0")), + .package(url: "https://github.com/mhdhejazi/Dynamic", branch: "master") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "lume", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Atomics", package: "swift-atomics"), + .product(name: "Dynamic", package: "Dynamic") + ], + path: "src"), + .testTarget( + name: "lumeTests", + dependencies: [ + "lume" + ], + path: "tests") + ] +) diff --git a/README.md b/README.md new file mode 100644 index 00000000..145c3e7f --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +
+

+
+ + + + Shows my svg + +
+ + [![Swift 6](https://img.shields.io/badge/Swift_6-F54A2A?logo=swift&logoColor=white&labelColor=F54A2A)](#) + [![macOS](https://img.shields.io/badge/macOS-000000?logo=apple&logoColor=F0F0F0)](#) + [![Homebrew](https://img.shields.io/badge/Homebrew-FBB040?logo=homebrew&logoColor=fff)](#install) + [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?&logo=discord&logoColor=white)](https://discord.gg/8p56E2KJ) +

+
+ + +**lume** is a lightweight Command Line Interface and local API server to create, run and manage macOS and Linux virtual machines (VMs) with near-native performance on Apple Silicon, using Apple's `Virtualization.Framework`. + +### Run prebuilt macOS images in just 1 step + +
+lume cli +
+ + +```bash +lume run macos-sequoia-vanilla:latest +``` + +## Usage + +```bash +lume + +Commands: + lume create Create a new macOS or Linux VM + lume run Run a VM + lume ls List all VMs + lume get Get detailed information about a VM + lume set Modify VM configuration + lume stop Stop a running VM + lume delete Delete a VM + lume pull Pull a macOS image from container registry + lume clone Clone an existing VM + lume images List available macOS images in local cache + lume ipsw Get the latest macOS restore image URL + lume prune Remove cached images + lume serve Start the API server + +Options: + --help Show help [boolean] + --version Show version number [boolean] + +Command Options: + create: + --os Operating system to install (macOS or linux, default: macOS) + --cpu Number of CPU cores (default: 4) + --memory Memory size, e.g., 8GB (default: 4GB) + --disk-size Disk size, e.g., 50GB (default: 40GB) + --display Display resolution (default: 1024x768) + --ipsw Path to IPSW file or 'latest' for macOS VMs + + run: + --no-display Do not start the VNC client app + --shared-dir Share directory with VM (format: path[:ro|rw]) + --mount For Linux VMs only, attach a read-only disk image + + set: + --cpu New number of CPU cores + --memory New memory size + --disk-size New disk size + + delete: + --force Force deletion without confirmation + + pull: + --registry Container registry URL (default: ghcr.io) + --organization Organization to pull from (default: trycua) + + serve: + --port Port to listen on (default: 3000) +``` + +## Install + +```bash +brew tap trycua/lume +brew install lume +``` + +You can also download the `lume.pkg.tar.gz` archive from the [latest release](https://github.com/trycua/lume/releases), extract it, and install the package manually. + +## Prebuilt Images + +Pre-built images are available on [ghcr.io/trycua](https://github.com/orgs/trycua/packages). +These images come with an SSH server pre-configured and auto-login enabled. + +| Image | Tag | Description | Size | +|-------|------------|-------------|------| +| `macos-sequoia-vanilla` | `latest`, `15.2` | macOS Sonoma 15.2 | 40GB | +| `macos-sequoia-xcode` | `latest`, `15.2` | macOS Sonoma 15.2 with Xcode command line tools | 50GB | +| `ubuntu-vanilla` | `latest`, `24.04.1` | [Ubuntu Server for ARM 24.04.1 LTS](https://ubuntu.com/download/server/arm) with Ubuntu Desktop | 20GB | + +For additional disk space, resize the VM disk after pulling the image using the `lume set --disk-size ` command. + +## Local API Server + +`lume` exposes a local HTTP API server that listens on `http://localhost:3000/lume`, enabling automated management of VMs. + +```bash +lume serve +``` + +For detailed API documentation, please refer to [API Reference](docs/API-Reference.md). + +## Docs + +- [API Reference](docs/API-Reference.md) +- [Development](docs/Development.md) +- [FAQ](docs/FAQ.md) + +## Contributing + +We welcome and greatly appreciate contributions to lume! Whether you're improving documentation, adding new features, fixing bugs, or adding new VM images, your efforts help make lume better for everyone. For detailed instructions on how to contribute, please refer to our [Contributing Guidelines](CONTRIBUTING.md). + +Join our [Discord community](https://discord.com/channels/1328377437301641247) to discuss ideas or get assistance. + +## License + +lume is open-sourced under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Trademarks + +Apple, macOS, and Apple Silicon are trademarks of Apple Inc. Ubuntu and Canonical are registered trademarks of Canonical Ltd. This project is not affiliated with, endorsed by, or sponsored by Apple Inc. or Canonical Ltd. + +## Stargazers over time + +[![Stargazers over time](https://starchart.cc/trycua/lume.svg?variant=adaptive)](https://starchart.cc/trycua/lume) \ No newline at end of file diff --git a/docs/API-Reference.md b/docs/API-Reference.md new file mode 100644 index 00000000..512bbe3f --- /dev/null +++ b/docs/API-Reference.md @@ -0,0 +1,226 @@ +## API Reference + +
+Create VM - POST /vms + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "name": "lume_vm", + "os": "macOS", + "cpu": 2, + "memory": "4GB", + "diskSize": "64GB", + "display": "1024x768", + "ipsw": "latest" + }' \ + http://localhost:3000/lume/vms +``` +
+ +
+Run VM - POST /vms/:name/run + +```bash +# Basic run +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + http://localhost:3000/lume/vms/my-vm-name/run + +# Run with VNC client started and shared directory +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "noDisplay": false, + "sharedDirectory": [ + { + "hostPath": "~/Projects", + "readOnly": false + } + ] + }' \ + http://localhost:3000/lume/vms/lume_vm/run +``` +
+ +
+List VMs - GET /vms + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + http://localhost:3000/lume/vms +``` +``` +[ + { + "name": "my-vm", + "state": "stopped", + "os": "macOS", + "cpu": 2, + "memory": "4GB", + "diskSize": "64GB" + }, + { + "name": "my-vm-2", + "state": "stopped", + "os": "linux", + "cpu": 2, + "memory": "4GB", + "diskSize": "64GB" + } +] +``` +
+ +
+Get VM Details - GET /vms/:name + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + http://localhost:3000/lume/vms/lume_vm\ +``` +``` +{ + "name": "lume_vm", + "state": "running", + "os": "macOS", + "cpu": 2, + "memory": "4GB", + "diskSize": "64GB" +} +``` +
+ +
+Update VM Settings - PATCH /vms/:name + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X PATCH \ + -H "Content-Type: application/json" \ + -d '{ + "cpu": 4, + "memory": "8GB", + "diskSize": "128GB" + }' \ + http://localhost:3000/lume/vms/my-vm-name +``` +
+ +
+Stop VM - POST /vms/:name/stop + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + http://localhost:3000/lume/vms/my-vm-name/stop +``` +
+ +
+Delete VM - DELETE /vms/:name + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X DELETE \ + http://localhost:3000/lume/vms/my-vm-name +``` +
+ +
+Pull Image - POST /pull + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "image": "monterey:latest", + "name": "my-vm-name", # Optional, defaults to image name + "registry": "ghcr.io", # Optional, defaults to ghcr.io + "organization": "trycua" # Optional, defaults to trycua + }' \ + http://localhost:3000/lume/pull +``` + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "image": "macos-sequoia-vanilla:15.2", + "name": "macos-sequoia-vanilla" + }' \ + http://localhost:3000/lume/pull +``` +
+ +
+Clone VM - POST /vms/:name/clone + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "name": "source-vm", + "newName": "cloned-vm" + }' \ + http://localhost:3000/lume/vms/source-vm/clone +``` +
+ +
+Get Latest IPSW URL - GET /ipsw + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + http://localhost:3000/lume/ipsw +``` +
+ +
+List Images - GET /images + +```bash +# List images with default organization (trycua) +curl --connect-timeout 6000 \ + --max-time 5000 \ + http://localhost:3000/lume/images +``` + +```json +{ + "local": [ + "macos-sequoia-xcode:latest", + "macos-sequoia-vanilla:latest" + ] +} +``` +
+ +
+Prune Images - POST /lume/prune + +```bash +curl --connect-timeout 6000 \ + --max-time 5000 \ + -X POST \ + http://localhost:3000/lume/prune +``` +
diff --git a/docs/Development.md b/docs/Development.md new file mode 100644 index 00000000..cbaa4df5 --- /dev/null +++ b/docs/Development.md @@ -0,0 +1,45 @@ +# Development Guide + +This guide will help you set up your development environment and understand the process for contributing code to lume. + +## Environment Setup + +Lume development requires: +- Swift 6 or higher +- Xcode 15 or higher +- macOS Sequoia 15.2 or higher +- (Optional) VS Code with Swift extension + +## Setting Up the Repository Locally + +1. **Fork the Repository**: Create your own fork of lume +2. **Clone the Repository**: + ```bash + git clone https://github.com/trycua/lume.git + cd lume + ``` +3. **Install Dependencies**: + ```bash + swift package resolve + ``` +4. **Build the Project**: + ```bash + swift build + ``` + +## Development Workflow + +1. Create a new branch for your changes +2. Make your changes +3. Run the tests: `swift test` +4. Build and test your changes locally +5. Commit your changes with clear commit messages + +## Submitting Pull Requests + +1. Push your changes to your fork +2. Open a Pull Request with: + - A clear title and description + - Reference to any related issues + - Screenshots or logs if relevant +3. Respond to any feedback from maintainers diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 00000000..cad1cb54 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,37 @@ +# FAQs + +### Where are the VMs stored? + +VMs are stored in `~/.lume`. + +### How are images cached? + +Images are cached in `~/.lume/cache`. When doing `lume pull `, it will check if the image is already cached. If not, it will download the image and cache it, removing any older versions. + +### Are VM disks taking up all the disk space? + +No, macOS uses sparse files, which only allocate space as needed. For example, VM disks totaling 50 GB may only use 20 GB on disk. + +### How do I get the latest macOS restore image URL? + +```bash +lume ipsw +``` + +### How do I delete a VM? + +```bash +lume delete +``` + +### How do I install a custom linux image? + +The process for creating a custom linux image differs than macOS, with IPSW restore files not being used. You need to create a linux VM first, then mount a setup image file to the VM for the first boot. + +```bash +lume create --os linux + +lume run --mount --start-vnc + +lume run --start-vnc +``` diff --git a/img/cli.png b/img/cli.png new file mode 100644 index 0000000000000000000000000000000000000000..2d0b646541796641b389b5718ec0e0658ae92c9d GIT binary patch literal 382457 zcmce;c|4Tw+dphc3XzC}hzf-fmEDlFFi4AiDQos6yBVa2vP2|nQqy9~zEcz#ne2>x zOM@Zn3}blCi~8K(`?>G$bKkG$`Mn25PprWEW zbVW<;8Wk0z6crUs>Vf^>ok0DIP2j(M_A0t6R8${h8MmzI!0-Hbw65t=Q3af#q6!J8 zqS^#+h5VwTx+g(JHETshCHI_)iqk!<%0L19LEqNsik+@5)miZR02K}O5h_~niW>Z* zig2XbxBHrk3S0^N&}LEp$6vE({{EFwDvS2-YnoK(gJREZy}?C{9j_aC8|muE-Engf zx3+P+Z7c5Q;tqX)O2JPKymYbkw&wS9ad!2R^HUVq{e&EN4ZRH$;NSg-x09lPk*+?! zs+*@R|0Qt=aR~t>27Z2i1y370xoc_~f4&ZWQxtIU_I8(p!F+vv#eJp3-8}7K=VfJO zVG@!sNl7vA2{A8!S8r=SF;_3ay^H+4j+(949ZyGhZ$~#*e(1W^x83e}D+&leH~Qyi zZ=BwacK>lFSFbR8-1TSJYIl`%%xQM?akxjmfjG-{IEFjFBxG zqcy!``)c)xj;-eE`v-9$4=VbNtG7`*6x0qGwX*`>*+wJ7T|Har8+TG!^uBJeaylMU z3j2iO>bQON)fIZBS|0e1O~VuYFR30qicX%C7LfEw)lV{9RE)&soiHX7%#LpfNp|MD z7Tj{jZKx-HyA>>Cj$ZOz+DX{lncZs9OJY0!%t;&fc{mLjQWo6W*D|Zj1#5;$u=_ot z5!I|Jp6GfgF&(^JNKN$IdZ@;gJ3E2nH+b)QE^*aWh(yQyD#RR7Hrcz)jvl_4kM#aVy#3Wh-vS3#1I=y>OVd#7IT-=;HQLIJ`_EB<|}52F-@c)M%f_R zP)R_tOp8D9*#0ZhazRIh7W!T?IV^^BJ~7?bI~_`u#70lvUq42rHFJK>=Sgqf_bV9z zb?B!S7T92c_HLucNbJaR^z!Dh(hS1;ofNtAxAN9=DI$pK;K*x!T~96LC~xPEjm8MS49xe!Rv#^yE47RnIMD z?%3soc*2Vk|EMnUieIq~FB?Y&M~L%HTnzrBUu(Z%mgM>HT%*m#CF{rx4q6Yk)a)EG z#Q>rBW8q99eVrEdx_s5tG%57biRl7W{Ev|p%1L>TOL0P`Po&tglG$dwZHnfjO49;D zUpC0ITV3o(MP{_m&5llpEU%N#kKDg8rHk45_FyhE*K2DiO(sjNdxdLT=x6i#J3bRE zX31#`wIf*H)|N4@ZbyTHHM5i~3A5DbQdi5kH1^j0%=?le@UbOw@Mmyzip-+2age04 z_)N|{xKMkv{{A<<0=%Qn3HY(9oh^e*$~)phk7=fl@|@lVK^XTdqG@Sw@`fI@f;#73 zB4cpxfMJ=OtC;R)WTGpg$&n8;`|>DI?rMW7{l2vm%9p&W$N7Hh#2910)LSR}bW!$^ zkEbw;>UrP}>7e_xrzzyOqdH!^3riRO_PH`m%=W2uOPZ zxCtFzXiJaXp`O$YRw+E_MS17ndni6n%ox*p1uQFb%SAg z;zoJ>K=RW2sOclm2x1?6o=*~^g-sl9UR*%ioEUVeiuifQ(X)-OHFKhb5x1?sEq;C4 z-hA0}OGE9Un|*FFo6zqb`RTKO2AG+o-j z;tp%6shH`X?Lyx>e=^!tg|%+PvU~yEMezxU{jzN7MUz9m#Vqq7bAF(}%nv(LLVLH~ zo?tK%D|zgwvYpywsm%2L2eT0e*T5Q@Gi;F;cgWZaPc}Ea#mgwhXG$ifNlzjl2b-SE zy;uE8ZML{V_%^AQr`}Snq;?}R>JO?H2$y79Xj6rFr@ZdvE@f@#Kt*;{pev^TCnq&7BF+23Tbr#^O){wJLXW;upTlcx)uj3TVLfL8rX@Hb(XyVVvkql~O7U6||p zUo?wRDE;Cm-{?spC6j#^j($Q;3@O96Leax?@@K+!^dtSuIZJ=;f7ouawD8m?du3 z4$2FnZ`f~ws<7_52GreySt*=O5%oJ)%F zlf`M$p@*I3l(>G27(RzdgA*hHI->$_;F7snxvF*hEf8s;arkUZdX01QK>atbq1*5j zHqNPRY81OVH*bDU!g?cSiPlA-wfgrjvf^xd>`>es`NG2Z!MxhcX#VAs$lDa2`Ud^6 zLYj242>IqCRCi{;xkQ>^@ubLrH1M!sY8koG89VnH)!S}k>5|ycH!_#O$IsB&lsew* zG=uBS+*>1hjHHHcXb2AWY!Ip2@v6d{?M9qpA!=1O;M=(Y~?iv zbX~iGWor43aoPTsQ|eG@zC$|IzB-T=Vk2jhdE*IRv!T^ea!PqM_UtDdWTlT??p!SN zD`|B;@8T(9LkbV4@BO|%MWtjjkxi&jmHG%9=iVsllSA4#7>3$ks{}iq;niQ0f>9j% z@iVuEdThq{o~SlmQ#A3&4gTbBoIax>_tZkt&3!F(Kng3BOewrovW}J^gw^OITTfv} zHqjPV!V-}*&Z6FZ;CUXg{T+R%DB%_F;C*$-`e^Dks24=?sEx@RXVnUCz$(ci zr`Whkc2U!MZFsiDm=iny@UOV!N#Z?WTAX}{cjHm&ixlbE;**1iszJckFhd>8#E? zOBc*+qVaZ=dU1jlnos$sXS%tF3Jbh=6UAa0cw2)ObQo&J)K`9BkY{dT^xF z;cKaBrsu)C4~aDGC4GUftz>eH5EF7tOx?A3Lhg_$@^A~k+h(a;PBBJwU z?F?DfXTq5uJ6DS(*RDCDxl_!l1@>Z`uW*A;*X*IudTZiJ_q8>6%k<^)wb(0_B1_2$ zj|QijJgkwq$&@!+cWw-SW$J@)#&32>!xFIZ0P3TP4q+y5eEK31E7WzmLiB5!Bj-@D z;UZ5?DPx^;a(r(`CqNm}uZ>YWY1E$O*KCGF#7d~=%eKwae3S?ITJH_+ zt&B5l8a^gA;v$xwb{r^J0c`xybxXUh>=iLA@Hms;_!AYkiT{)M^&SY$%1AjUDjSTMkPMLzC+Un(~5xW zk``!Q9{~qKjL%sx)Qk6kqxAazd(_Mj9%u?)tK$J3NzB|y6`%@lJxDj6NL-C#c6O& zyZG{(%3o4_pK$WBDW=830M`6nfqJ%kc6R1Y9 zGm`(>6%4e#9Si`6*3zduxf#2Qjrl1L=zmU&d=^bwM4K&s=&DVch7a45{i=T0!6&}| zzySbLt-)2pz~4GcSCXK8k0Nm%0xiO`mUbGG9NCA~17$IhH_z!&LSr94{{B)TI{Gk2 z`~y$0ikz$jo~~j)*(*FhzP{Q*$A#bi0VWDUt;=P|hh@ADaV_Y&a6zee;is%iG)k!1 zO<@Y-R4E$WzFBlDe^PEs9FfL_NOolnx4av8?x{&BszxW>`+1^P=E!hXxe_6ych}Kg zpzAL@&_9*VeH*8;+=zBPnh}Y*ur!Zbsl^g+Hw2^ zgZm3CZyH(~*W(N~4AXp65DuhKErNGOv9rlc@E{_MnSH=JmDT^^0!UO~Y9k9tgqz`G z&r^Qjs{Ik>6A9nIQih$nEOKm#e&3tLy=f7`O%SyW*6jSe_}}1LjW4}PKd9Ae56zFn ztyG=mmY8b;zTC$*{+m!8Fl)Z2h!PSz+Wg0Q#BzhD`M~$9MN1|&8oxLf2hZxk2_9RK z$+Dam{X=@m%k*Y}5-XPOt>(f5?}s^F_W&1C&GlNA5+`gCV>!km(hy(^Su0psBFEu1 zi|`iRWGFVApOVq{w{N~59nH49hTueIt)Xl9WXtC^GDe(?5w)zW!+d@yA+|61otJ+e zAw!D*_9KeHSf6S0EMln3faXL1GMRcdynNOWNnLb#ekBMd)XtEiklIn3CIb=`VTP7~ zo;76@SPHN6l*u0@%sY5XyEmEeNUg!+Ri*GRj>MoT&u_2aeG%bf&8&U=)U;MJv)BIf z=@zsZxg$z*12&<_7QV6{cX0HrXD}<wboF=VE=ZNe<(@Qs@vi$jIJna1>WC(L|SWizKCG*A)Xc9!UfzruTwa88xAJAK8w zX%X{Tt98TmyounXbeUmc=7@~Ncf`w6SNtX^A+8mX=JS;eo)h}0FVQEF2j~z6A=6h2 zSN!E-owi&M)oT=^<&b)g>C2^SndikRHv;QW-vWAx!Ci_v51V1-eS}4pYMDawnnMF` zOejU`t{Djtq^WBl<$FAM^s3pTTxZ;GD9QNhD%I{eOUFU?-1QGmFinjCkBw1HpTRV3 z8Q`)!I&EUe-JMh~Sk|`x(EF>5WePHGsIMv@fCY=7wo9bOuU(;3SIYcScjmB~cyMXH zWTln!P3KNN@|)E?1rnFiqw~Q&u-t3*QH`2H`;L$Z3@w&++oO)9ZC}5gefdimJ!17X zI_6_LM*@ej_yZ;uP7+Meq|krYhr<39ES!1+g6s*KOTRjR34ZG26X~z|U{EmR-=ty> zK`BeM4vn=Vp-~oIyZ|z6c)q9}d*QW>?m1uV#N%R&n!wB}7)n|aktw>>6yf1MEHMwI z!9jY(P^iGQ9X0bLyPrGA>yD#~%&TXZu^XJ5E;6`6#l1neAEUd8xe}oCnm)67zJ#`S zy>I58T(a!xSSZC^Hp*BKyte^zf;RnqJFD9-#LHxvm+W?7C>^G{@$4_1W8WHHO`kfq z2CR)DVzzj|@%G{Ix9I_b0(U4EK1H(mrPAbZ9}18~!XucB_I0nid5DyA2f*Mp7Et*+^jAqYK7 zi4~sF%Nc_oEx$Y(e4>+Y{&CL_C^8hLmzWfMG@}+ion;cn@;kU%vsvmVSfuqbu5>IxC;Ayks+7UzUqDFr(kl&mo!d$ zi^m&Fn$L~X=m}!=_N`pH!5bMGPO@wJFLOnXygYT%JFtK3Zh!G)a<~C?eqHJSY1p@< z7uj#xe*5l*F$5gWN?Pv&V+Wg|eIIxX!0+D-nGWFx>wy=hCNhTrJY*K)Jnu~6$g542 zlBLYShc$l}%qX4K>Fa^O$%*OEjSNAW^Mh`hKc6BqFy9WWI(W36TmJE6Mo^fgs|xka z>p|J##KilB?7~gJB3V9^y? z|HrP6av>+WWjml7fBJoNAb@ev`0sqh@A{%441rwLq%KbBChJ6_Whw(I;Tc~Z(c*@q zmUk(OxRW6BIF}fP&KxDIY!2`cBIkZi$l#lWIQXybtGh|fp};#o8I<|;pkv2w1b?y` zFd3B0Xgs{#v@Mh@lSg<8Hr5rf?`IeC($m^(0_{v6a0bQ%9u*STf{)$FZg0z-1LKKo z`Av8XrNt+1hIoGhsV7-ot$H`#IHR&X)y>TcKXPD3^Khx@6Vu)JvB}6XMH%@1me_;0 zz$|Ah6l-VH?chGLq0QXA(zyMrA~9MOB;H*#!JW2Vo{-R%PdRG;NpZ)xhvbnLvSs^o zN2lv;rlJ_U6yWEHkB^1-o?jgVE6y@4IwOVvni-1Q!6wI+DS0#~%C8}hUy)G*k401M zs~sDZ>;8UAW25~+$yaPXd1)yA$f$d3Jqz#}^1#bTH{v_R!j?`i8McU(^e9z67{+#` zS8u{(r?ZC5g;|*BTn$4GJ=s4zD@u2%JVvzW>hZywx0*~({JHxc%{8M%Z0Hi~5d79-=;BGrPpXLE@ zfKMOUw=wT+z`QWU9?wub{w65fK?1ply?H=VMiYv`W%{Rbjl+#@0$f0Z=$N*bzcG3)V3Udjvpl%g+k z9R=g8udNQR73|;a-M$!fQfA*Lc~b6dATsUrW1sgB`Y=d63bDJ}j{#pvyY9K=s_CdN zeV2=hK0KPOIh1D~&oPd?;IA9DK8mbMP6MxpEHXBxi!TKCr?gEDA-Gwc{eP#~~n?f@byL5FM;^N^l7zSBS5wqi3?htv)DCrwZTcbDbQWNMwQdh&G$5DHl1yK0yBV z3hq&VU)yOwbgny)f_P{nGkE%Ek=s<*Z7dQ>)mLe^g`+s+f?`;9sjhqD)!P~|9AI>^a#Dc8pz z{$tGc+C+r@*aRjO9z)fdf#UEARoVs^;tx{NL%30xIGipd#Q;+HQQ{>#c*x2xAYC z=i(+`M#d`rsun#@B8XC?7UBEJ3w?}L=@(loYJ>mxLuJz;e zW{yaV_LMMwAPpck=JS)pi@|Kj>g={R;8$+Npj+)NeLhfF@;yv^TKj~hax;QboI7@X zH9q1@#&)JTwzftiD)VyaE#WN2;NFCz$zfmi&rjLjY2Q}$=+L~^aVU;(5??eT(dRQo z7#4R9o(Ku_nWY0BS@#1xz@9+#YIo|C^QhXYf)&`l^mOIZJW)LdM_iEud!tRC1}}es zRVR54WDnfW3vR8MXL{SzQA{A;S{-jwLbis1M?n$UIhz=6@QEvNFJqM-hr)2Vh;miO z82F-Pgk)ANZ4^Z%nU*GUj1j>z&pqTik6XyCD;;e}wzTV8QF?6%E0{sL$(bC@tuLj= z;x&&eMYbJ9KTvlPV@Y+a4C^~WU`2PNTNtmPa`&@@mQovxcW ztXVFLkHB%8?MO-mel~2;>DUI#6Ix(kRe7Or__l%7(#rklTS)}(gNVNV0fF}E5)`bH zdvg+G#|Q2uY`%i`E{y32`JRvNMec>gVY<(Gx2(4>OE2k|*zDCSUvG(HKhP_GIsL$@h|ovG+5Uc$-lho(-&$*cQ~+%gjJK0+fb0W+~I$J@4 zT_#_7hZ+-Bew21^WibSAJ(+BHeMF7^7CxY|WO}srdVwW^ z&3%`(0LIKObYV;C)N$AbK=Et_He|Bxw2HjPezi*G_gU{%uf9^sFf7Kq5e^~XMJc}T zf2gpk-~sgj6^5BDpxGUx)4C-G%;8W(=eQoru}d14_U@M+qq9x!h2zy&fI$N1Lu(&< zU($H7@BWESw-M}(cD*t^Y|tPxl4TlIUcdCfng-2<`o%4haT zV@7e(%^R&nU?{MkTP;(BJc(IQ z!udu9epwst zZ=o(S&Xi%E8jp?m&b5re2i(58p=pLov2;aKpKhNj^?#A1NM1IxFFz1;Gg+>BkW#oy za!(!FI{hj% z(;Vn6?)Z<3VpOxiEJeIYtmkVh8*q>b1qwB@*r3AdkE@^K6>kQNB;+*Xh%OtO;)=Co zjE#v8Me_I~{Y~94$rwSieO*}3;J$6=LA-QtgMOANbtju9sGxCwv0m{vm&?IGxh#v- ztiy?jlF{$Qq?%h+;-W(0>qlV^NhjH5a;OGy@0|%VE8d7sWbn?jt7DDXW=!SVQq8ss zcdmXO-f{&0nDM2E>;({?=>q}5mB8Pg*%pvHUyN{!oeo_*x1*1d3{%_f6@%t z#vf1g_1MQDw6^$jB8#)sI7Amw3u_Xn5SFPG{#jhr5M^y=!MhX4rZv=X@@QOr+g82d zI#v#Ddp}}Q`q*r_FcTb>B)MfF?r}`%2+31wPMyQ>&e2Cen3yb*J4U6ci zs`yi9IBt7j6K{?MISUA!$-)iGIr8Vk>ALo}X)aa+cNr&q|CYJU5N-z%}z2KCVKGV6ee7bNet&<2547V+|C2w2!2&IdnN0?qu^bq?LSl70{UV65N zR4~<-*DF87sjCO=c`$hz^}Q0OK&bxhW2c6HLG0unOt>oT&1#fPPh$kZgtYtPYEw#* z@*eo-*`;n?@7`wyWRU#`>deniaEG^DSJBP8jk)ht5X2&xoW{(tJG5Y}uj95d*tsvF z>)(mg0|niHEmI7Wlp*A|<(2^;ocZ_&0)8qfd6M%O1J6zVg05@zyD@W!k@J{m=a=0; zRpulv1JN9u6jrEiKS&03d)1SRgB5zfZ_EfPPLgG!dq=Qc69TTc4HLab;jQoJal_%u z4V)qkNKK#zfJ5~1Jz-v4U>)2>sD1ykLV<@J4d4*dVsw(sKPDMM5|#*>+%o}P*e-u_ zim8J72onA^@MBa>$JHHDaZ+`q*-$6SK=IX3|7uX#bgBZp41+Y>KL4Hx{Z-y!x8UJ^ zRFbU;Y$qu81KPeYy6ciN!D+-KP2`$+TA+e@{u+oPz5=FNeDh1!Vv}+eD_jKh! zGzO+p)NDFd8Pbk_@&U~pxSDIVPs zyjyNkwhpGKvPsL5JD|!j93~J{Tkkrohb9wNPef1G|2%GGH#!vnLl7sstzrzt8#28n z!nPzuhhimVAUy>cRMEKsra99MXr7_#%G;7Fs5g$CShNXw%lORd=e^}rRpH<~c?`K* z2FZ&X-K+IU(P_yC{Gd$SfUbpOwxEJtCs|QxsJ6{S~^rUnZ751|2beZOm=h#GKFkPL**r2_}W68*6(nxGBsdvLwE+D)RzRznTH zt?`J5UREhSL#h;nyL(wJt3*ueeNbaO%zC{!rSmeK+>@Cpm#>1ADMqIC*67ja^=v7J zHTotJQG${_3H!Z<$)mTU38fwjfd}@zfry;{PCuc6BYIU| z$ByS*C-jMmvz>ib2%9wpse-g>AW@h%OK>!dQRSrAr=3Eq&ZgjI|FWqp+tpXQ3|v1U z{BJqL%Sjp1;|#}iX1PvDD(eyf5}=tHE2NKdr)Udn60W&HV2l@@j`^AB zm8wm$%;NWkpjYvP*)1~F%<9QR#v7X$k4nDOw2{`}%LL}_&n~e9CzgR;leOI7FJ8-I zdgAxIJkpkvV}q-*c|b`T_8UeQvv3TeF6zHO|4Utb0J#e8+t6vC^Z8Mx>2HP9IU4$$P%h|o1=AuY3D`tIfyUwiMN z5B0SC#+=z+;4k1m+pBKFrTI)VWkPuw;+|Ld?b65P2YWD&q?jd5S4lqNhh}-z0Cs&mbj5z+WXrqPOpt z*Zgo%r=yp$ufYdJ_>S00$Sr&ayahi#Yf@^hBaHBA&s$jnMW?HeRWDM>GPAF%vKfsu z{w~>{Z#iepchFySCixfeWL&M`KwOd1Tx1?KR^?JMYq+3nvGB~-|NmEsMP=6VmuDdC z9%${q*Pk6(1~S=u+1LA64ubu?>*}(Ee^ibb&zwU6xX<+P0q|IW+P(r6@V>YBSag0Z zaU&$Vm)r@oe=&Z$&o#L9(aPB=Mbo-Ej8+%%Cb$Ceq~JI}A@?>nnhV~A2Q(8!4QD0e!8CR=y>5*K?&%{km@GU9EqYamB8UU5^mi`oY8C$v#mpYzsiQ6`^R z+J<$O=+|ZU!kVeY0sM@tmXk9Ln1Hfym(MTpGx(Q-jT*rV8bf)`3J{AUu7w&;K3Vy&6@UA?+0{MaX74S;;dDkG1L`E&U;t+jng<=EojHZtX{>6VYt zxrJ%mW4%}uBu3rTjulz4+lb>ApGp-!#$e_sX5qNL<>cg2_-A!_-b>9u z$_B>6(Ng(c1jJXApr`Xj#mC-yKp2qn*MTHl7C%eC(Mw#N@G)VQ}6r$q%#@)XwS zl_VuEMSK<&zigz&(F#lv)IYp*uoKg#JRm5VdHG0`3N!$LJLLyEAT}k5rm7av^@C(o zojOw@ww=yXzZ3^6m@X2Po5NUC#?1Y$mhrHYF|SfTjC@DP)4JQUy(D?B)B2h~aACRs z@&_Qu0Teh@z>0Cp_T?^up8xT@oQ0;ug@wYbde9=ST@W8sLq~K}< z*?k(1R*d0WlAOu@sz|2IS9I7gT)3RVL?~+u-vo=fOJN)V`awt(NLvr+`*W(maCg(d z5zpD$1M#Wi=76ER)8dh}d{r)}>GXmBM}39(|7U##9KO8^Fhp^s4g`2ZeA&h8yHxE) z%k)!iLr68R6v%bDeBM*ODaHL@_8NQ)7n=m1u8-u?J>1UiAmsO|bxmDISZtSwxl_&t z@ouDx3~}vDzW`+mB%?vUtrIQJ^s)I(j(6gAraWnuF?X6wyUW)g3WhpW2ui(Km9%5B1K*@A7G2}r?4pj0nNw#|v8UGZ4AS}bbCB{Z7NUc%`JaIt@e*Gg~+rlMP^~t>8K<*vn!m0OsIaltQ12A zR8P5vKp!gTlcMa)(ow9sKBa%nS#}NN(f1jiQxbze_+{D(A*7=kU(N*1)*kjR8uWu_ zaf{#P3=}U+lj*ixx*f@PIndlfcjt`!CYtGXzKC(+(7zavYzo`a!z5^-KR|3Q7e6^dGT>ivmE_*PJXc`@9 zI!F;Eke#R744gfkfbfI8e7iU7b)a(T&i6`A{T&6@8?Wh1Po$N@C;7c9+v^hHC|rvB zyPR!+Bvu73F-k05=JvsLa6Y|;a!C9%>vt?-JmFZEJ@NC|dv9cdyf5>IzB-qMO4(Rc zDLngj8j`$L9h6ufxZNp$c^x-CPP1dH#&~J3c0}hRtbBW756YxB0SWcU{kHvz0U#(C zZ3@zv`d*xUCjVUK*F1>U+tI?fw-y5W9Zvc8OJOpW2ro{vm7JhnOOv9VqT`H~cEC}U z^Qj-E1dVRm+l0d3hqy8oe=P7D5q|r5|7}@h|1E`Pk@ffrrkl-;`|rw_{EM{0XumfJ zkQY`2>}E~b4Dmc7jWxd66Zx$jvy}Ss0CJ5|O2*)8;Lm&mg*-N?aim0F!EbkoH!2`S z9{j-^@5$j<)_>#_FaNEaVsV6I9P2Wb+-oN!PHRxI?t)nEUV%R|vM~KbTPnIR;6fRb zrKnY`JKv%#c1Ks{SpK7^xC)6gE&h)yDn9&CR8am$Ma9Wr=49%-pFuR?ZecV27svof zmO*}Y0#Xo{mH!uI1?roKUCQOLN$H*{3s-A> z^YrFGyThabQmzOWF=Uw{v44Dz1w9Ci3T(h!Dh#y>z&=1KHmf1R>+JG7V!o*o$TSCj zh?xXR+k0;Ip{ioXEU^YxpXYy&C4@kFWXxK)sK26QGG!oTdC3hWNn;KcENad6qGBM` zw6zHlR(HtL!$2$IH09O~$Zs(E4_{^WuKvXmxwl?`djnUD2!re-Tt`|J)I#JsTxXnD zvUY0(xOI_HwGKqY_oY4bN4LD4m$8T+0_s05*5Yl?uF1NLP6>~AZTOl}*MtZn-(um3 zy~jj|PkFY&?`U=_xQ!NoRSd7k@H4*T8ssJW(`6IcV(Vu(8*}(FjY5Q3NhWkgzr71% zSAvfVvG~^!UhAzf(+Ny}2ho;FfBb$YyAiPh}%=vWiJh?W|0{$^%6sETT43;|Mb= zcsM{THV_Vxk_zvs20Gtk%^AtI3cfi_(rL?!sUWAM|Y@9tIJ(N;lMw%9!xT}t&_ zfs)vJCVyKSA?x7F389HWb}Q9aD$>&2XJSX20x!0Xm6D4DN;5$+Ep+uBrJrsT^N1EA zU4d08TRhWMB>fNSinaf(x?;Uy9t=GQs0jsmftnCP4X6OBj|NGHE*7MMjW!rR)o;4k zEzfd=Q$T62IshPVml}W3w4+VF383mv+iP&?MfVZCySs_j3gwvZ{;?lMbCI0Q*awog zd6R^5wxL8q_&Yx0Ah1OmlvZd}MpqWVYOL}26YkU}rBy3{iV+hxDL~lqCkq|2YYuHC zizM^6*rTI8p@qiAd!-rg$pm|;y1lX}=<;CS1f`3$lcUXIdOE%4+msB)Va%fqJx~Ot z^5vKBz3$fPnxoyZoA>)%Z0^(9QWDfHKON|>CkCs zg^6}|1JupBD(#Z}m#%JHc0WLK@3(OeajT`N;}r*}(%CqlN&{CWNioDNF!lfG@OW!W z#d8=aEg9oz8&3#--qltR=Z`f1pOjYY!HT{5=N)Pgl`Wx;Fv&E+5w8JhPnD1M^&kP zRuo@C9VV>6u^U;X1%4TM*Xlp9=-s`}MAr^9n?q4{fPriG-I^okUH42wx0G%_HQbg< zWV9R}Std8&w@)|rt!zU`;zZpcf{#P9=7K)daZ{Pq4R3iwJG(@4(EDthWUbsTDtcAU z1(JL=BOo3Y1{4*NwIjy61k|EeXH4NUG?^jje=VojR7zv?sLK4L3Dj2J^%g%4 zalX$SW_;KEV&b>;=NKRjRmyIdfJu=9vWzcy0hQH$)nT4v+ZyYp>(iJ;4$p|(=h zk^JuwGFeXm0TMxT>WuiV>K$Ynwz6wqRqQ_W+;M_~QgP6&F7ez6d->_&YLlwA6QzcW zoCb+ED5uP2GtOoP59C2StmA-)<*T;BfMwgFze4#Jew!#CMdI>t5hMeh#mf-!Rb#!I zW(*wMg$c<|WXUXF)cJbUrIpJfaxejnW`ptNzSV_mk7$7$i4(Zs3whhW(g7ZnEx0n* zgkPSkt4Vs$S{Zy|*~nsN>FNu5%sXk-cW)Pes7aXT19awURcF!T8{8ScvF-%b-3b~` zI~F2&14z(rgi(&^EJyYfRw+~gl0eN#Ys5E zL&-E}PW|tO3$bq9p?^gb0{=-wQK@9R!-~a8Z|Jasvivi3+^4#4UsyKk4%&PEA_4L; z&hb9IQWn*tt6h7@-5JVSwhl`UGi`u4li=Z1ZI7c{1Wi3HvMPUtIchPBPO#c3Jq%<&QnEb&yid!P2`a2=V+(ckRQ{Ke3N9m3w3fQ?KEVXN zv10vE(yMLTpLz+$twBE*#jJh4*l19p)RdqNwXlX}i=MeGu?02d0m+iO8Y2e~hH--W zeRwt?OF%wPwg@wT{T8RI2<*rJWQ8aoE8ub3Y+JQ0uP<7@c>M~0M0RpHG1pUTa}G1E z8``?yaWQ*+l>B(e$UC*27K6e1FPlh`_X&@ZSpsMWkqIrgRKN6BTrud%?^+g z=Xe1Mc(|+KWa1X&-n$<{^UO~0T&8z+#GY3O{c4+g@9$Xv#my=yxx_gY7QFQ0%-I>C z5!5}8j2ZQVYdiOyhLX~kwc{Q=T`O1uTO`%sMHkCp!rm0>0nn)H%G#|b;cdo2`1{*G z3V-NKJj;hauj;fDsV&$tC~r<$o7_~;n~_124CL1|)VxOnQRVhR2tLWa;*zue65oaR z=7Co{1lsV!UV*0rdNe<&Vu{_=R3JmDZ2%MdaA%Vy`T55bE$77eoI!AiPruMsB zfDN&;JN}PSO8^RvVSxr#%4)9+!=mWiN+qj&X$~kf6}FZNWmS=~=6X6JU`p~^*SLh- zCv;8#&3RXmz0h5&PA4YbS!eY2E`mcJnw}AmQojzaW7Azu6h8TDn&guG*t}X7Y|BMk z;o%Of&R6XbyDr3-GDD#SzG$!Z%Ap0AKE)OvBaNojX&NWD<@sClkV#SwZr^3k^RD3G z+m&|DT^=bPAbNn}oEz1KUBX!9leU(2)Dv`m#P1XZs(&wQ*u#+IubKodFYf9cwNKY-X97Vl8LMjyI zY|Ptf^Wcib0jaND_C|6XUw>~uQc1n+-Ax`K@=W6)C7i2Gs}KXQ8GC&!m-jwc^8oQh z;v5;Op@*tdcZSk{2?I67dMDOaQ~x*+GkpE*-7d9W8Uh*@XEKy)R!Bzgdp$Jkoy6nJ zIo_%Gb81?7T-$LKXAUq(rR$&SCSMi}iw^6GzOD3uG546AU7+Rgbkm`r*EE2p-dPy9 zK<<*91T5>Jhnr#ycd*f~TRJeOnHz;pZ6&jcoiP{UC4p=WoF(uFKkP+Vm&?Gp4n=^h zrUU0T(>eBYSX(Z4Hp5n^9hU7N(uk1Ln_Qd-dh~eov33@IT7sjFr_5oGY@!{};0i

%jjFM41%{e_Jzo_~%YIr0Y3Si|d--K(JX8dcPe^DZE21O~7flH6gV$!IF zO?Iw>oxi8N1X%Eo=gQ#t+{R6AX>qrxXo!0z4gztYWRZEoC1@M|7bOJ@Tr}I-_%#TC#tJ@gkmo`DedyaJ=!<*Z zoB{t{PVs*`rs7}Z6m3MFl*4vpg4ROGrXwLZ?XfZ#;-Xz*g!^zN$7p_ABIu14d9x;6 z!K^)Tdvt2TcjdR6quZH?GEg%Q)nQZkO--j|^a8|yUBbB2bCz;gGJO|EAQVb!rJjV{ z>Jbuesbkto4U~I7h&#&-HE$@zu&n~>EsMZ1eJ(Nj*6{X} z!cKzr^|JC@oHeA)ba-vtQ&Ioa-?@Am`D_mKeSniN%;S)so7K`&jQe(x*!Td~9F+0r zQsKiI_e!MIDeVUYo^@~2_sK|+!vN>JEpk}nlP79)fTSx=_wdffE5B1%(dvkr;tJD&rK8 zwQ%}fr!IQ135lz0V_AF)Tr+4nQH>(>m5QGuG+4DG&dKFBpG-E|uq-Y2tDR4RJwR)h z?GrVd9K!lDIj%M0`V?&0wx@bu2=RWX1?NlPh95$`{0el-8n+yI~q92Nwl=V+t(sDi1 z+w0bkm;Sk^b|$<79NRG(`FQwLi`yxx`#XqJ=3MC@PVrogfGb)V(K{FJ1|xail&#ve zT6>{(k5lHLIb>I=44y&F8W&3nhA?ky$CaGi7bp|IO5wz=c)Im2bBz|mW+#0QUhOLh z%#Qn;g=7*AHbu=NEfgownFd+;#0yiqD#LFhrdHj7tdhLYaSCL~sfsbu#}=Q0*JS>oBk6h@hslOvWpeXw1c%Kk@Id&_RiP>Gr0w67j>;84gD>X5r zdw60#3L=%5=rjyM4n=H(vUqR{(JifzV`d#Xi~_H@h?~pPlLuk!O&R-hzTIYJr>SMG zWMgFIW7`fa;hJJZ!nIP%;tHL&*FV?2b%Q#nmp=* zdoH;x1uR@s?am@H4gu0MJIEA!?w)miKV?rIOFlgP8+hj3PLMXcre1gxbTq}I#&Wj3 zPE$>dKRym*1EARy=-`2rboh)jXs1&b=+3Sb@-8-DxZIIIz^5=-<+Q8+vcOEe!Oew;Jpvp9y|LVLq~mxGQ=^n zgEN81(UE1f`1fn9kD-=3kXe)Ibcmc=eUZnF0F6cG5|~K;v)PC>asVkWnY`)+I(fc! zeg9%zrFC>?zmTgx>x-c`i}hXP(0GIW1(R&NWfq0+2X(x zKA>~jvEkqBUNp7^)=V6AbVwd;rAegIabCzdg@Ynx^3 z_5O-|a9!y+tmr;ZEDwDO=AO^%ph^rv-fEVGOWjduCajh77I_}Sm1hF%(+jp9NeM_Eo+&aM1q>cUN7h<>!tVLb~I_X!Ghd z5ux@EaYW}KNKXWW6p)I*_@zFOwjs+&AuKLW|q)}^?a zwUHUh$Yx0w)NC3Lb-6&NW5xBn{qVAfyUw?9VdmZISyUY6f$JP!!cXpG1y{F0M|A%? zA}2VUz)m+ds*Rlj<$4hYAvx$qB+}iZ^^V@AB+59`=Tni9S8!{Aib?et#)mb@cJ1o_ zMcsD>Rh4bsim0e01p!f#WEFu1m7F9BNHCL838G|>97O~q2RR@)i3*D3oFsDy3X+rL zoHK_5Z|;NLx9@kqQ1`1>uj>7>s%>HKwbz9kGonQVZgFajZnjJA}a`dWJ-=vo}c5Zb2hm@i?ACvtrQi@wPIBxOpHWluFvTsZO zu&K~&oo$O}J++6=)Vo~L`pr$l5ywyP54DnJl1w^|A=<~x7f}GiHB>A4>8L0(x}OwETWP9 z=;h0DkHcP`b`5%^A~B6f4x#-+C;>T58LDp9$&$dpkwE{4B*Cfm>t7uuTV_8QSETcL zINJ0MNVCQvwpGq@f_XHNe`7@JeK%%nY``Ln>#wIY`Q za#1bz(1d~TNEPs*1JpdRCdmk(2jP%^fneJ~AE~Y(9w6T|luD^CShlCYbGs4=klfXd zBKh_itIR18lmXo!_<-cG^@Uokn`_!=OQa!WuL2_!$=>$fvxx=QJptv5{WDqdXV(Bt zq$V8^200=A&l6@Hpo!G;?8a%0DR}I!Cmnd)s3JaYv+jEkrA&5HT_YoHHaaQS#Wmtq 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 oyn1+MJm3Jsw^chsl5|f%N9e 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

c0P8^#4V7>5JiBx?VD;vpOh zBzvL|zrI6^^_QqS2;R&8g$OX0Z_;1h2y46O?Z`2Q#Y~l&J@knGjkvT#_?ynL?pM68 zDH&L^{|Dkne6)K)A=r!wqz#=skS$t>P9B4jG3=~`V|VOqK)&5C7e)o_$LXTRu!Ns5 zANh>}*Dico)MSZNXhma~W*=Rk%U5>%{pJ>~=3)QOED-i`+3P8zx!e6{{*nvE@z1C> zd=@qRHA?J={8_ca@Xm9Lonl9B<7ZL?j3y!FfPb`o=(Ybzbj$^?$N%@}n53G&Q8-`^O|+@tYggIXqaiaUCIaE+r0*a)*1A;TSuc|eV0c=QC)`u_eKA5M$)_m{{+`>fezwc=8SQuc zKIm~3gf3N#=T|y4LsWBCTrSuGz%+WL(Y=MAVL5Z?r(5f^o4;$xxNqn*P=_#4z~Le% zdn_*7&Nr{YMEGqjD3-0LqaKima*emG$yuy2V-VmoAP;1ky?Gu8Xaak?D!dJY9)sAS zO>~$~t*PXv+NSYFwb!4D{(*-~md*>03N&RfI^IuYLxIK)KGB0eln(>Jy1kWIQQ((K zd(*4C&<`p-Qm@Rzfu}c<${B8e>&J4rcj|eLCRcOwl6nR)_c;sqKXb5auG{54%Vs~w zCg))=D*kt@2+ZGpYp#*n2-n{|-WL6cqn+ov2Z1$(;%M?|tbK}TM-(oZZ^bQPx$&H% z@#b7ws*D@oMg^mGM~wkZe?Au%XLk-ujmh{_1Cr*6&6cja+9^N>25#aIe+~s(oi98c zxOMA6MA$N-Esx}+WDrn9q_rA2pC&V%?Pe)%n3@C2M7lN{*(h124WOa`aZK}3qg{*vEL{pdMfRg#tl$Kjm&?~n^i9E1vhWNBDEu5IrKGqKPM%{ zv`y{dWg^jUAr2ZU`iP~**QF;P1mO}Skg08%dqzijGV)k+S09lDF;wwV?)5Lp4l)cf zZ`C4_MV^CK5a2$6lfWlL73<;id>Qn1mbH%`GTls>+C>5n=9d$@Bhz{{YvgFPLc4s) z|D8R$*-dK+1>|)og0Dy118i&s>$g*ud!QsWo}n;_Qqr_Z$(i+g{9sVyHcOO61b++9za1&BeWOl&x{hkotj&E2nntb-N7-bBi@* zulxOil>0~`*V{pwo|7GMxL1g6s*Uf9C6|>150=FH>Ka^tiL$9I@tb2O$cTOIj80?zSkq?MeA}LpS7UAO zAE;e8P3{9V!(YQ=6?9)qhCwiEmi&aOO?nE2}q$Yt}z8C8@l6o>3FgRtgC

w6%%`cYXWG*)vm`G%0OH-C=e8RnKoa|;;$EC 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

CT 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%0Ac?fY}eer8TuIs_X)|h|ME>HPw0oY*fz6A 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 Pga7uM+wLpb>y`LFQwJh% literal 0 HcmV?d00001 diff --git a/img/logo_black.png b/img/logo_black.png new file mode 100644 index 0000000000000000000000000000000000000000..8198ec61545085a9dadc305a49a28cf8e1e6ed6c GIT binary patch literal 4799 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 Js!sSN{tvb6Njm@l literal 0 HcmV?d00001 diff --git a/img/logo_white.png b/img/logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..cd83c45be604a9c8a3f6bb949aa8eae12409975b GIT binary patch literal 4260 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 + + + + com.apple.security.virtualization + + + diff --git a/scripts/build/build-debug.sh b/scripts/build/build-debug.sh new file mode 100755 index 00000000..452e20c3 --- /dev/null +++ b/scripts/build/build-debug.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +swift build --product lume +codesign --force --entitlement resources/lume.entitlements --sign - .build/debug/lume diff --git a/scripts/build/build-release-notarized.sh b/scripts/build/build-release-notarized.sh new file mode 100755 index 00000000..0665db22 --- /dev/null +++ b/scripts/build/build-release-notarized.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# Check required environment variables +required_vars=( + "CERT_APPLICATION_NAME" + "CERT_INSTALLER_NAME" + "APPLE_ID" + "TEAM_ID" + "APP_SPECIFIC_PASSWORD" +) + +for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + echo "Error: $var is not set" + exit 1 + fi +done + +# Move to the project root directory +pushd ../../ + +# Build the release version +swift build -c release --product lume + +# Sign the binary with hardened runtime entitlements +codesign --force --options runtime \ + --entitlement ./resources/lume.entitlements \ + --sign "$CERT_APPLICATION_NAME" \ + .build/release/lume + +# Create a temporary directory for packaging +TEMP_ROOT=$(mktemp -d) +mkdir -p "$TEMP_ROOT/usr/local/bin" +cp -f .build/release/lume "$TEMP_ROOT/usr/local/bin/" + +# Build the installer package +pkgbuild --root "$TEMP_ROOT" \ + --identifier "com.trycua.lume" \ + --version "1.0" \ + --install-location "/" \ + --sign "$CERT_INSTALLER_NAME" \ + ./.release/lume.pkg + +# Submit for notarization using stored credentials +xcrun notarytool submit ./.release/lume.pkg \ + --apple-id "${APPLE_ID}" \ + --team-id "${TEAM_ID}" \ + --password "${APP_SPECIFIC_PASSWORD}" \ + --wait + +# Staple the notarization ticket +xcrun stapler staple ./.release/lume.pkg + +# Create temporary directory for package extraction +EXTRACT_ROOT=$(mktemp -d) +PKG_PATH="$(pwd)/.release/lume.pkg" + +# Extract the pkg using xar +cd "$EXTRACT_ROOT" +xar -xf "$PKG_PATH" + +# Verify Payload exists before proceeding +if [ ! -f "Payload" ]; then + echo "Error: Payload file not found after xar extraction" + exit 1 +fi + +# Create a directory for the extracted contents +mkdir -p extracted +cd extracted + +# Extract the Payload +cat ../Payload | gunzip -dc | cpio -i + +# Verify the binary exists +if [ ! -f "usr/local/bin/lume" ]; then + echo "Error: lume binary not found in expected location" + exit 1 +fi + +# Copy extracted lume to ./.release/lume +cp -f usr/local/bin/lume "$(dirname "$PKG_PATH")/lume" + +# Create symbolic link in /usr/local/bin +cd "$(dirname "$PKG_PATH")" +sudo ln -sf "$(pwd)/lume" /usr/local/bin/lume + +# Create zip archive of the package +tar -czvf lume.tar.gz lume +tar -czvf lume.pkg.tar.gz lume.pkg + +# Create sha256 checksum for the lume tarball +shasum -a 256 lume.tar.gz + +popd + +# Clean up +rm -rf "$TEMP_ROOT" +rm -rf "$EXTRACT_ROOT" \ No newline at end of file diff --git a/scripts/build/build-release.sh b/scripts/build/build-release.sh new file mode 100755 index 00000000..9861ca56 --- /dev/null +++ b/scripts/build/build-release.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +pushd ../../ + +swift build -c release --product lume +codesign --force --entitlement ./resources/lume.entitlements --sign - .build/release/lume + +mkdir -p ./.release +cp -f .build/release/lume ./.release/lume + +# Create symbolic link in /usr/local/bin +sudo mkdir -p /usr/local/bin +sudo ln -sf "$(pwd)/.release/lume" /usr/local/bin/lume + +popd \ No newline at end of file diff --git a/scripts/ghcr/pull-ghcr.sh b/scripts/ghcr/pull-ghcr.sh new file mode 100755 index 00000000..8b10fae1 --- /dev/null +++ b/scripts/ghcr/pull-ghcr.sh @@ -0,0 +1,205 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Default parameters +organization="" +image_name="" +image_version="" +target_folder_path="" + +# Parse the command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --organization) + organization="$2" + shift 2 + ;; + --image-name) + image_name="$2" + shift 2 + ;; + --image-version) + image_version="$2" + shift 2 + ;; + --target-folder-path) + target_folder_path="$2" + shift 2 + ;; + --help) + echo "Usage: $0 [options]" + echo "Options:" + echo " --organization : GitHub organization (required)" + echo " --image-name : Name of the image to pull (required)" + echo " --image-version : Version of the image to pull (required)" + echo " --target-folder-path : Path where to extract the files (required)" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Ensure required arguments +if [[ -z "$organization" || -z "$image_name" || -z "$image_version" || -z "$target_folder_path" ]]; then + echo "Error: Missing required arguments. Use --help for usage." + exit 1 +fi + +# Check and install required tools +for tool in "jq" "pv" "parallel"; do + if ! command -v "$tool" &> /dev/null; then + echo "$tool is not installed. Installing using Homebrew..." + if ! command -v brew &> /dev/null; then + echo "Homebrew is not installed. Please install Homebrew first: https://brew.sh/" + exit 1 + fi + brew install "$tool" + fi +done + +# Create target folder if it doesn't exist +mkdir -p "$target_folder_path" + +# Create a temporary directory for processing files +work_dir=$(mktemp -d) +echo "Working directory: $work_dir" +trap 'rm -rf "$work_dir"' EXIT + +# Registry details +REGISTRY="ghcr.io" +REPOSITORY="$organization/$image_name" +TAG="$image_version" + +# Get anonymous token +echo "Getting authentication token..." +curl -s "https://$REGISTRY/token?service=ghcr.io&scope=repository:$REPOSITORY:pull" -o "$work_dir/token.json" +TOKEN=$(cat "$work_dir/token.json" | jq -r ".token") + +if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then + echo "Failed to obtain token" + exit 1 +fi + +echo "Token obtained successfully" + +# Fetch manifest +echo "Fetching manifest..." +MANIFEST_RESPONSE=$(curl -s \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.oci.image.manifest.v1+json" \ + "https://$REGISTRY/v2/$REPOSITORY/manifests/$TAG") + +echo "Processing manifest..." + +# Create a directory for all files +cd "$work_dir" + +# Create a download function for parallel execution +download_layer() { + local media_type="$1" + local digest="$2" + local output_file="$3" + + echo "Downloading $output_file..." + curl -s -L \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: $media_type" \ + "https://$REGISTRY/v2/$REPOSITORY/blobs/$digest" | \ + pv > "$output_file" +} +export -f download_layer +export TOKEN REGISTRY REPOSITORY + +# Process layers and create download jobs +echo "$MANIFEST_RESPONSE" | jq -c '.layers[]' | while read -r layer; do + media_type=$(echo "$layer" | jq -r '.mediaType') + digest=$(echo "$layer" | jq -r '.digest') + + # Skip empty layers + if [[ "$media_type" == "application/vnd.oci.empty.v1+json" ]]; then + continue + fi + + # Extract part information if present + if [[ $media_type =~ part\.number=([0-9]+)\;part\.total=([0-9]+) ]]; then + part_num="${BASH_REMATCH[1]}" + total_parts="${BASH_REMATCH[2]}" + echo "Found part $part_num of $total_parts" + output_file="disk.img.part.$part_num" + else + case "$media_type" in + "application/vnd.oci.image.layer.v1.tar") + output_file="disk.img" + ;; + "application/vnd.oci.image.config.v1+json") + output_file="config.json" + ;; + "application/octet-stream") + output_file="nvram.bin" + ;; + *) + echo "Unknown media type: $media_type" + continue + ;; + esac + fi + + # Add to download queue + echo "$media_type"$'\t'"$digest"$'\t'"$output_file" >> download_queue.txt +done + +# Download all files in parallel +echo "Downloading files in parallel..." +parallel --colsep $'\t' -a download_queue.txt download_layer {1} {2} {3} + +# Check if we have disk parts to reassemble +if ls disk.img.part.* 1> /dev/null 2>&1; then + echo "Found disk parts, reassembling..." + + # Get total parts from the first part's filename + first_part=$(ls disk.img.part.* | head -n 1) + total_parts=$(echo "$MANIFEST_RESPONSE" | jq -r '.layers[] | select(.mediaType | contains("part.total")) | .mediaType' | grep -o 'part\.total=[0-9]*' | cut -d= -f2 | head -n 1) + + echo "Total parts to reassemble: $total_parts" + + # Concatenate parts in order + echo "Reassembling disk image..." + { + for i in $(seq 1 "$total_parts"); do + part_file="disk.img.part.$i" + if [ -f "$part_file" ]; then + cat "$part_file" + else + echo "Error: Missing part $i" + exit 1 + fi + done + } | pv > "$target_folder_path/disk.img" + + echo "Disk image reassembled successfully" +else + # If no parts found, just copy disk.img if it exists + if [ -f disk.img ]; then + echo "Copying disk image..." + pv disk.img > "$target_folder_path/disk.img" + fi +fi + +# Copy config.json if it exists +if [ -f config.json ]; then + echo "Copying config.json..." + cp config.json "$target_folder_path/" +fi + +# Copy nvram.bin if it exists +if [ -f nvram.bin ]; then + echo "Copying nvram.bin..." + cp nvram.bin "$target_folder_path/" +fi + +echo "Download complete: Files extracted to $target_folder_path" \ No newline at end of file diff --git a/scripts/ghcr/push-ghcr.sh b/scripts/ghcr/push-ghcr.sh new file mode 100755 index 00000000..c204f97b --- /dev/null +++ b/scripts/ghcr/push-ghcr.sh @@ -0,0 +1,208 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Default parameters +organization="" +folder_path="" +image_name="" +image_versions="" +chunk_size="500M" # Default chunk size for splitting large files + +# Parse the command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --organization) + organization="$2" + shift 2 + ;; + --folder-path) + folder_path="$2" + shift 2 + ;; + --image-name) + image_name="$2" + shift 2 + ;; + --image-versions) + image_versions="$2" + shift 2 + ;; + --chunk-size) + chunk_size="$2" + shift 2 + ;; + --help) + echo "Usage: $0 [options]" + echo "Options:" + echo " --organization : GitHub organization (required if not using token)" + echo " --folder-path : Path to the folder to upload (required)" + echo " --image-name : Name of the image to publish (required)" + echo " --image-versions : Comma separated list of versions of the image to publish (required)" + echo " --chunk-size : Size of chunks for large files (e.g., 500M, default: 500M)" + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Ensure required arguments +if [[ -z "$organization" || -z "$folder_path" || -z "$image_name" || -z "$image_versions" ]]; then + echo "Error: Missing required arguments. Use --help for usage." + exit 1 +fi + +# Check if the GITHUB_TOKEN variable is set +if [[ -z "$GITHUB_TOKEN" ]]; then + echo "Error: GITHUB_TOKEN is not set." + exit 1 +fi + +# Ensure the folder exists +if [[ ! -d "$folder_path" ]]; then + echo "Error: Folder $folder_path does not exist." + exit 1 +fi + +# Check and install required tools +for tool in "oras" "split" "pv" "gzip"; do + if ! command -v "$tool" &> /dev/null; then + echo "$tool is not installed. Installing using Homebrew..." + if ! command -v brew &> /dev/null; then + echo "Homebrew is not installed. Please install Homebrew first: https://brew.sh/" + exit 1 + fi + brew install "$tool" + fi +done + +# Authenticate with GitHub Container Registry +echo "$GITHUB_TOKEN" | oras login ghcr.io -u "$organization" --password-stdin + +# Create a temporary directory for processing files +work_dir=$(mktemp -d) +echo "Working directory: $work_dir" +trap 'rm -rf "$work_dir"' EXIT + +# Create a directory for all files +mkdir -p "$work_dir/files" +cd "$work_dir/files" + +# Copy config.json if it exists +if [ -f "$folder_path/config.json" ]; then + echo "Copying config.json..." + cp "$folder_path/config.json" config.json +fi + +# Copy nvram.bin if it exists +nvram_bin="$folder_path/nvram.bin" +if [ -f "$nvram_bin" ]; then + echo "Copying nvram.bin..." + cp "$nvram_bin" nvram.bin +fi + +# Process disk.img if it exists and needs splitting +disk_img="$folder_path/disk.img" +if [ -f "$disk_img" ]; then + file_size=$(stat -f%z "$disk_img") + if [ $file_size -gt 524288000 ]; then # 500MB in bytes + echo "Splitting large file: disk.img" + echo "Original disk.img size: $(du -h "$disk_img" | cut -f1)" + + # Copy and split the file with progress monitoring + echo "Copying disk image..." + pv "$disk_img" > disk.img + + echo "Splitting file..." + split -b "$chunk_size" disk.img disk.img.part. + rm disk.img + + # Get original file size for verification + original_size=$(stat -f%z "$disk_img") + echo "Original disk.img size: $(awk -v size=$original_size 'BEGIN {printf "%.2f GB", size/1024/1024/1024}')" + + # Verify split parts total size + total_size=0 + total_parts=$(ls disk.img.part.* | wc -l | tr -d ' ') + part_num=0 + + # Create array for files and their annotations + files=() + for part in disk.img.part.*; do + part_size=$(stat -f%z "$part") + total_size=$((total_size + part_size)) + part_num=$((part_num + 1)) + echo "Part $part: $(awk -v size=$part_size 'BEGIN {printf "%.2f GB", size/1024/1024/1024}')" + files+=("$part:application/vnd.oci.image.layer.v1.tar;part.number=$part_num;part.total=$total_parts") + done + + echo "Total size of parts: $(awk -v size=$total_size 'BEGIN {printf "%.2f GB", size/1024/1024/1024}')" + + # Verify total size matches original + if [ $total_size -ne $original_size ]; then + echo "ERROR: Size mismatch!" + echo "Original file size: $(awk -v size=$original_size 'BEGIN {printf "%.2f GB", size/1024/1024/1024}')" + echo "Sum of parts size: $(awk -v size=$total_size 'BEGIN {printf "%.2f GB", size/1024/1024/1024}')" + echo "Difference: $(awk -v orig=$original_size -v total=$total_size 'BEGIN {printf "%.2f GB", (orig-total)/1024/1024/1024}')" + exit 1 + fi + + # Add remaining files + if [ -f "config.json" ]; then + files+=("config.json:application/vnd.oci.image.config.v1+json") + fi + + if [ -f "nvram.bin" ]; then + files+=("nvram.bin:application/octet-stream") + fi + + # Push versions in parallel + push_pids=() + for version in $image_versions; do + ( + echo "Pushing version $version..." + oras push --disable-path-validation \ + "ghcr.io/$organization/$image_name:$version" \ + "${files[@]}" + echo "Completed push for version $version" + ) & + push_pids+=($!) + done + + # Wait for all pushes to complete + for pid in "${push_pids[@]}"; do + wait "$pid" + done + else + # Push disk.img directly if it's small enough + echo "Copying disk image..." + pv "$disk_img" > disk.img + + # Push all files together + echo "Pushing all files..." + files=("disk.img:application/vnd.oci.image.layer.v1.tar") + + if [ -f "config.json" ]; then + files+=("config.json:application/vnd.oci.image.config.v1+json") + fi + + if [ -f "nvram.bin" ]; then + files+=("nvram.bin:application/octet-stream") + fi + + for version in $image_versions; do + # Push all files in one command + oras push --disable-path-validation \ + "ghcr.io/$organization/$image_name:$version" \ + "${files[@]}" + done + fi +fi + +for version in $image_versions; do + echo "Upload complete: ghcr.io/$organization/$image_name:$version" +done diff --git a/src/Commands/Clone.swift b/src/Commands/Clone.swift new file mode 100644 index 00000000..85bb4125 --- /dev/null +++ b/src/Commands/Clone.swift @@ -0,0 +1,22 @@ +import ArgumentParser +import Foundation + +struct Clone: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Clone an existing virtual machine" + ) + + @Argument(help: "Name of the source virtual machine", completion: .custom(completeVMName)) + var name: String + + @Argument(help: "Name for the cloned virtual machine") + var newName: String + + init() {} + + @MainActor + func run() async throws { + let vmController = LumeController() + try vmController.clone(name: name, newName: newName) + } +} \ No newline at end of file diff --git a/src/Commands/Create.swift b/src/Commands/Create.swift new file mode 100644 index 00000000..2e0f0150 --- /dev/null +++ b/src/Commands/Create.swift @@ -0,0 +1,52 @@ +import ArgumentParser +import Foundation +import Virtualization + +// MARK: - Create Command + +struct Create: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Create a new virtual machine" + ) + + @Argument(help: "Name for the virtual machine") + var name: String + + @Option(help: "Operating system to install. Defaults to macOS.", completion: .list(["macOS", "linux"])) + var os: String = "macOS" + + @Option(help: "Number of CPU cores", transform: { Int($0) ?? 4 }) + var cpu: Int = 4 + + @Option(help: "Memory size, e.g., 8192MB or 8GB. Defaults to 8GB.", transform: { try parseSize($0) }) + var memory: UInt64 = 8 * 1024 * 1024 * 1024 + + @Option(help: "Disk size, e.g., 20480MB or 20GB. Defaults to 50GB.", transform: { try parseSize($0) }) + var diskSize: UInt64 = 50 * 1024 * 1024 * 1024 + + @Option(help: "Display resolution in format WIDTHxHEIGHT. Defaults to 1024x768.") + var display: VMDisplayResolution = VMDisplayResolution(string: "1024x768")! + + @Option( + help: "Path to macOS restore image (IPSW), or 'latest' to download the latest supported version. Required for macOS VMs.", + completion: .file(extensions: ["ipsw"]) + ) + var ipsw: String? + + init() { + } + + @MainActor + func run() async throws { + let vmController = LumeController() + try await vmController.create( + name: name, + os: os, + diskSize: diskSize, + cpuCount: cpu, + memorySize: memory, + display: display.string, + ipsw: ipsw + ) + } +} \ No newline at end of file diff --git a/src/Commands/Delete.swift b/src/Commands/Delete.swift new file mode 100644 index 00000000..59c44a0b --- /dev/null +++ b/src/Commands/Delete.swift @@ -0,0 +1,31 @@ +import ArgumentParser +import Foundation + +struct Delete: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Delete a virtual machine" + ) + + @Argument(help: "Name of the virtual machine to delete", completion: .custom(completeVMName)) + var name: String + + @Flag(name: .long, help: "Force deletion without confirmation") + var force = false + + init() {} + + @MainActor + func run() async throws { + if !force { + print("Are you sure you want to delete the virtual machine '\(name)'? [y/N] ", terminator: "") + guard let response = readLine()?.lowercased(), + response == "y" || response == "yes" else { + print("Deletion cancelled") + return + } + } + + let vmController = LumeController() + try await vmController.delete(name: name) + } +} \ No newline at end of file diff --git a/src/Commands/Get.swift b/src/Commands/Get.swift new file mode 100644 index 00000000..32b2b423 --- /dev/null +++ b/src/Commands/Get.swift @@ -0,0 +1,21 @@ +import ArgumentParser +import Foundation + +struct Get: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Get detailed information about a virtual machine" + ) + + @Argument(help: "Name of the virtual machine", completion: .custom(completeVMName)) + var name: String + + init() { + } + + @MainActor + func run() async throws { + let vmController = LumeController() + let vm = try vmController.get(name: name) + VMDetailsPrinter.printStatus([vm.details]) + } +} \ No newline at end of file diff --git a/src/Commands/IPSW.swift b/src/Commands/IPSW.swift new file mode 100644 index 00000000..2f85c948 --- /dev/null +++ b/src/Commands/IPSW.swift @@ -0,0 +1,20 @@ +import ArgumentParser +import Foundation + +struct IPSW: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Get macOS restore image IPSW URL", + discussion: "Download IPSW file manually, then use in create command with --ipsw" + ) + + init() { + + } + + @MainActor + func run() async throws { + let vmController = LumeController() + let url = try await vmController.getLatestIPSWURL() + print(url.absoluteString) + } +} \ No newline at end of file diff --git a/src/Commands/Images.swift b/src/Commands/Images.swift new file mode 100644 index 00000000..1f5ab1ca --- /dev/null +++ b/src/Commands/Images.swift @@ -0,0 +1,19 @@ +import ArgumentParser +import Foundation + +struct Images: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "List available macOS images from local cache" + ) + + @Option(help: "Organization to list from. Defaults to trycua") + var organization: String = "trycua" + + init() {} + + @MainActor + func run() async throws { + let vmController = LumeController() + _ = try await vmController.getImages(organization: organization) + } +} diff --git a/src/Commands/List.swift b/src/Commands/List.swift new file mode 100644 index 00000000..a5bfdad0 --- /dev/null +++ b/src/Commands/List.swift @@ -0,0 +1,19 @@ +import ArgumentParser +import Foundation + +struct List: AsyncParsableCommand { + static let configuration: CommandConfiguration = CommandConfiguration( + commandName: "ls", + abstract: "List virtual machines" + ) + + init() { + } + + @MainActor + func run() async throws { + let manager = LumeController() + let vms = try manager.list() + VMDetailsPrinter.printStatus(vms) + } +} \ No newline at end of file diff --git a/src/Commands/Prune.swift b/src/Commands/Prune.swift new file mode 100644 index 00000000..d2aaa285 --- /dev/null +++ b/src/Commands/Prune.swift @@ -0,0 +1,19 @@ +import ArgumentParser +import Foundation + +struct Prune: AsyncParsableCommand { + static let configuration: CommandConfiguration = CommandConfiguration( + commandName: "prune", + abstract: "Remove cached images" + ) + + init() { + } + + @MainActor + func run() async throws { + let manager = LumeController() + try await manager.pruneImages() + print("Successfully removed cached images") + } +} \ No newline at end of file diff --git a/src/Commands/Pull.swift b/src/Commands/Pull.swift new file mode 100644 index 00000000..4a622cd0 --- /dev/null +++ b/src/Commands/Pull.swift @@ -0,0 +1,30 @@ +import ArgumentParser +import Foundation + +struct Pull: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Pull a macOS image from GitHub Container Registry" + ) + + @Argument(help: "Image to pull (format: name:tag)") + var image: String + + @Argument(help: "Name for the VM (defaults to image name without tag)", transform: { Optional($0) }) + var name: String? + + @Option(help: "Github Container Registry to pull from. Defaults to ghcr.io") + var registry: String = "ghcr.io" + + @Option(help: "Organization to pull from. Defaults to trycua") + var organization: String = "trycua" + + init() {} + + @MainActor + func run() async throws { + let vmController = LumeController() + let components = image.split(separator: ":") + let vmName = name ?? (components.count == 2 ? "\(components[0])_\(components[1])" : image) + try await vmController.pullImage(image: image, name: vmName, registry: registry, organization: organization) + } +} \ No newline at end of file diff --git a/src/Commands/Run.swift b/src/Commands/Run.swift new file mode 100644 index 00000000..ce29fb02 --- /dev/null +++ b/src/Commands/Run.swift @@ -0,0 +1,97 @@ +import ArgumentParser +import Foundation +import Virtualization + +struct Run: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Run a virtual machine" + ) + + @Argument(help: "Name of the virtual machine or image to pull and run (format: name or name:tag)", completion: .custom(completeVMName)) + var name: String + + @Flag(name: [.short, .long], help: "Do not start the VNC client") + var noDisplay: Bool = false + + @Option(name: [.customLong("shared-dir")], help: "Directory to share with the VM. Can be just a path for read-write access (e.g. ~/src) or path:tag where tag is 'ro' for read-only or 'rw' for read-write (e.g. ~/src:ro)") + var sharedDirectories: [String] = [] + + @Option(help: "For Linux VMs only, a read-only disk image to attach to the VM (e.g. --mount=\"ubuntu.iso\")", completion: .file()) + var mount: Path? + + @Option(help: "Github Container Registry to pull the images from. Defaults to ghcr.io") + var registry: String = "ghcr.io" + + @Option(help: "Organization to pull the images from. Defaults to trycua") + var organization: String = "trycua" + + private var parsedSharedDirectories: [SharedDirectory] { + get throws { + try sharedDirectories.map { dirString -> SharedDirectory in + let components = dirString.split(separator: ":", maxSplits: 1) + let hostPath = String(components[0]) + + // If no tag is provided, default to read-write + if components.count == 1 { + return SharedDirectory( + hostPath: hostPath, + tag: VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag, + readOnly: false + ) + } + + // Parse the tag if provided + let tag = String(components[1]) + let readOnly: Bool + switch tag.lowercased() { + case "ro": + readOnly = true + case "rw": + readOnly = false + default: + throw ValidationError("Invalid tag value. Must be either 'ro' for read-only or 'rw' for read-write") + } + + return SharedDirectory( + hostPath: hostPath, + tag: VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag, + readOnly: readOnly + ) + } + } + } + + init() { + } + + @MainActor + func run() async throws { + let vmController = LumeController() + let dirs = try parsedSharedDirectories + + var vmName = name + + // Shorthand for pulling an image directly during run + let components = name.split(separator: ":") + if components.count == 2 { + // This is an image reference, try to pull it first + let image = name + vmName = "\(components[0])_\(components[1])" + + do { + try vmController.validateVMExists(vmName) + } + catch { + // If the VM doesn't exist, try to pull the image + try await vmController.pullImage(image: image, name: vmName, registry: registry, organization: organization) + } + } + + try await vmController.runVM( + name: vmName, + noDisplay: noDisplay, + sharedDirectories: dirs, + mount: mount + ) + } +} \ No newline at end of file diff --git a/src/Commands/Serve.swift b/src/Commands/Serve.swift new file mode 100644 index 00000000..299449fc --- /dev/null +++ b/src/Commands/Serve.swift @@ -0,0 +1,17 @@ +import ArgumentParser +import Foundation + +struct Serve: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Start the VM management server" + ) + + @Option(help: "Port to listen on") + var port: UInt16 = 3000 + + func run() async throws { + let server = await Server(port: port) + Logger.info("Starting server", metadata: ["port": "\(port)"]) + try await server.start() + } +} \ No newline at end of file diff --git a/src/Commands/Set.swift b/src/Commands/Set.swift new file mode 100644 index 00000000..5cbbd938 --- /dev/null +++ b/src/Commands/Set.swift @@ -0,0 +1,34 @@ +import ArgumentParser +import Foundation + +struct Set: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Set new values for CPU, memory, and disk size of a virtual machine" + ) + + @Argument(help: "Name of the virtual machine", completion: .custom(completeVMName)) + var name: String + + @Option(help: "New number of CPU cores") + var cpu: Int? + + @Option(help: "New memory size, e.g., 8192MB or 8GB.", transform: { try parseSize($0) }) + var memory: UInt64? + + @Option(help: "New disk size, e.g., 20480MB or 20GB.", transform: { try parseSize($0) }) + var diskSize: UInt64? + + init() { + } + + @MainActor + func run() async throws { + let vmController = LumeController() + try vmController.updateSettings( + name: name, + cpu: cpu, + memory: memory, + diskSize: diskSize + ) + } +} diff --git a/src/Commands/Stop.swift b/src/Commands/Stop.swift new file mode 100644 index 00000000..8494d0ca --- /dev/null +++ b/src/Commands/Stop.swift @@ -0,0 +1,20 @@ +import ArgumentParser +import Foundation + +struct Stop: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Stop a virtual machine" + ) + + @Argument(help: "Name of the virtual machine", completion: .custom(completeVMName)) + var name: String + + init() { + } + + @MainActor + func run() async throws { + let vmController = LumeController() + try await vmController.stopVM(name: name) + } +} \ No newline at end of file diff --git a/src/ContainerRegistry/ImageContainerRegistry.swift b/src/ContainerRegistry/ImageContainerRegistry.swift new file mode 100644 index 00000000..77a7d7e0 --- /dev/null +++ b/src/ContainerRegistry/ImageContainerRegistry.swift @@ -0,0 +1,764 @@ +import ArgumentParser +import Foundation +import Swift + +struct Layer: Codable, Equatable { + let mediaType: String + let digest: String + let size: Int +} + +struct Manifest: Codable { + let layers: [Layer] + let config: Layer? + let mediaType: String + let schemaVersion: Int +} + +struct RepositoryTag: Codable { + let name: String + let tags: [String] +} + +struct RepositoryList: Codable { + let repositories: [String] +} + +struct RepositoryTags: Codable { + let name: String + let tags: [String] +} + +struct CachedImage { + let repository: String + let tag: String + let manifestId: String +} + +actor ProgressTracker { + private var totalBytes: Int64 = 0 + private var downloadedBytes: Int64 = 0 + private var progressLogger = ProgressLogger(threshold: 0.01) + private var totalFiles: Int = 0 + private var completedFiles: Int = 0 + + func setTotal(_ total: Int64, files: Int) { + totalBytes = total + totalFiles = files + } + + func addProgress(_ bytes: Int64) { + downloadedBytes += bytes + let progress = Double(downloadedBytes) / Double(totalBytes) + progressLogger.logProgress(current: progress, context: "Downloading Image") + } +} + +actor TaskCounter { + private var count: Int = 0 + + func increment() { count += 1 } + func decrement() { count -= 1 } + func current() -> Int { count } +} + +class ImageContainerRegistry: @unchecked Sendable { + private let registry: String + private let organization: String + private let progress = ProgressTracker() + private let cacheDirectory: URL + private let downloadLock = NSLock() + private var activeDownloads: [String] = [] + + init(registry: String, organization: String) { + self.registry = registry + self.organization = organization + + // Setup cache directory in user's home + let home = FileManager.default.homeDirectoryForCurrentUser + self.cacheDirectory = home.appendingPathComponent(".lume/cache/ghcr") + try? FileManager.default.createDirectory(at: cacheDirectory, withIntermediateDirectories: true) + + // Create organization directory + let orgDir = cacheDirectory.appendingPathComponent(organization) + try? FileManager.default.createDirectory(at: orgDir, withIntermediateDirectories: true) + } + + private func getManifestIdentifier(_ manifest: Manifest) -> String { + // Use config digest if available, otherwise create a hash from layers + if let config = manifest.config { + return config.digest.replacingOccurrences(of: ":", with: "_") + } + // If no config layer, create a hash from all layer digests + let layerHash = manifest.layers.map { $0.digest }.joined(separator: "+") + return layerHash.replacingOccurrences(of: ":", with: "_") + } + + private func getImageCacheDirectory(manifestId: String) -> URL { + return cacheDirectory + .appendingPathComponent(organization) + .appendingPathComponent(manifestId) + } + + private func getCachedManifestPath(manifestId: String) -> URL { + return getImageCacheDirectory(manifestId: manifestId).appendingPathComponent("manifest.json") + } + + private func getCachedLayerPath(manifestId: String, digest: String) -> URL { + return getImageCacheDirectory(manifestId: manifestId).appendingPathComponent(digest.replacingOccurrences(of: ":", with: "_")) + } + + private func setupImageCache(manifestId: String) throws { + let cacheDir = getImageCacheDirectory(manifestId: manifestId) + // Remove existing cache if it exists + if FileManager.default.fileExists(atPath: cacheDir.path) { + try FileManager.default.removeItem(at: cacheDir) + // Ensure it's completely removed + while FileManager.default.fileExists(atPath: cacheDir.path) { + try? FileManager.default.removeItem(at: cacheDir) + } + } + try FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true) + } + + private func loadCachedManifest(manifestId: String) -> Manifest? { + let manifestPath = getCachedManifestPath(manifestId: manifestId) + guard let data = try? Data(contentsOf: manifestPath) else { return nil } + return try? JSONDecoder().decode(Manifest.self, from: data) + } + + private func validateCache(manifest: Manifest, manifestId: String) -> Bool { + // First check if manifest exists and matches + guard let cachedManifest = loadCachedManifest(manifestId: manifestId), + cachedManifest.layers == manifest.layers else { + return false + } + + // Then verify all layer files exist + for layer in manifest.layers { + let cachedLayer = getCachedLayerPath(manifestId: manifestId, digest: layer.digest) + if !FileManager.default.fileExists(atPath: cachedLayer.path) { + return false + } + } + + return true + } + + private func saveManifest(_ manifest: Manifest, manifestId: String) throws { + let manifestPath = getCachedManifestPath(manifestId: manifestId) + try JSONEncoder().encode(manifest).write(to: manifestPath) + } + + private func isDownloading(_ digest: String) -> Bool { + downloadLock.lock() + defer { downloadLock.unlock() } + return activeDownloads.contains(digest) + } + + private func markDownloadStarted(_ digest: String) { + downloadLock.lock() + if !activeDownloads.contains(digest) { + activeDownloads.append(digest) + } + downloadLock.unlock() + } + + private func markDownloadComplete(_ digest: String) { + downloadLock.lock() + activeDownloads.removeAll { $0 == digest } + downloadLock.unlock() + } + + private func waitForExistingDownload(_ digest: String, cachedLayer: URL) async throws { + while isDownloading(digest) { + try await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second + if FileManager.default.fileExists(atPath: cachedLayer.path) { + return // File is now available + } + } + } + + func pull(image: String, name: String?) async throws { + // Validate home directory + let home = Home() + try home.validateHomeDirectory() + + // Use provided name or derive from image + let vmName = name ?? image.split(separator: ":").first.map(String.init) ?? "" + let vmDir = home.getVMDirectory(vmName) + + // Parse image name and tag + let components = image.split(separator: ":") + guard components.count == 2 else { + throw PullError.invalidImageFormat + } + let imageName = String(components[0]) + let tag = String(components[1]) + + // Get anonymous token + Logger.info("Getting registry authentication token") + let token = try await getToken(repository: "\(self.organization)/\(imageName)") + + // Fetch manifest + Logger.info("Fetching Image manifest") + let manifest: Manifest = try await fetchManifest( + repository: "\(self.organization)/\(imageName)", + tag: tag, + token: token + ) + + // Get manifest identifier + let manifestId = getManifestIdentifier(manifest) + + // Create VM directory + try FileManager.default.createDirectory(at: URL(fileURLWithPath: vmDir.dir.path), withIntermediateDirectories: true) + + // Check if we have a valid cached version + if validateCache(manifest: manifest, manifestId: manifestId) { + Logger.info("Using cached version of image") + try await copyFromCache(manifest: manifest, manifestId: manifestId, to: URL(fileURLWithPath: vmDir.dir.path)) + return + } + + // Setup new cache directory + try setupImageCache(manifestId: manifestId) + + // Save new manifest + try saveManifest(manifest, manifestId: manifestId) + + // Create temporary directory for new downloads + let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + defer { + try? FileManager.default.removeItem(at: tempDir) + } + + // Set total size and file count + let totalFiles = manifest.layers.filter { $0.mediaType != "application/vnd.oci.empty.v1+json" }.count + await progress.setTotal( + manifest.layers.reduce(0) { $0 + Int64($1.size) }, + files: totalFiles + ) + + // Process layers with limited concurrency + Logger.info("Processing Image layers") + var diskParts: [(Int, URL)] = [] + var totalParts = 0 + let maxConcurrentTasks = 5 + let counter = TaskCounter() + + try await withThrowingTaskGroup(of: Int64.self) { group in + for layer in manifest.layers { + if layer.mediaType == "application/vnd.oci.empty.v1+json" { + continue + } + + while await counter.current() >= maxConcurrentTasks { + _ = try await group.next() + await counter.decrement() + } + + let outputURL: URL + if let partInfo = extractPartInfo(from: layer.mediaType) { + let (partNum, total) = partInfo + totalParts = total + outputURL = tempDir.appendingPathComponent("disk.img.part.\(partNum)") + diskParts.append((partNum, outputURL)) + } else { + switch layer.mediaType { + case "application/vnd.oci.image.layer.v1.tar": + outputURL = tempDir.appendingPathComponent("disk.img") + case "application/vnd.oci.image.config.v1+json": + outputURL = tempDir.appendingPathComponent("config.json") + case "application/octet-stream": + outputURL = tempDir.appendingPathComponent("nvram.bin") + default: + continue + } + } + + group.addTask { @Sendable [self] in + await counter.increment() + + let cachedLayer = getCachedLayerPath(manifestId: manifestId, digest: layer.digest) + + if FileManager.default.fileExists(atPath: cachedLayer.path) { + try FileManager.default.copyItem(at: cachedLayer, to: outputURL) + await progress.addProgress(Int64(layer.size)) + } else { + // Check if this layer is already being downloaded + if isDownloading(layer.digest) { + try await waitForExistingDownload(layer.digest, cachedLayer: cachedLayer) + if FileManager.default.fileExists(atPath: cachedLayer.path) { + try FileManager.default.copyItem(at: cachedLayer, to: outputURL) + await progress.addProgress(Int64(layer.size)) + return Int64(layer.size) + } + } + + // Start new download + markDownloadStarted(layer.digest) + defer { markDownloadComplete(layer.digest) } + + try await self.downloadLayer( + repository: "\(self.organization)/\(imageName)", + digest: layer.digest, + mediaType: layer.mediaType, + token: token, + to: outputURL, + maxRetries: 5, + progress: progress + ) + + // Cache the downloaded layer + if FileManager.default.fileExists(atPath: cachedLayer.path) { + try FileManager.default.removeItem(at: cachedLayer) + } + try FileManager.default.copyItem(at: outputURL, to: cachedLayer) + } + + return Int64(layer.size) + } + } + + // Wait for remaining tasks + for try await _ in group { } + } + Logger.info("") // New line after progress + + // Handle disk parts if present + if !diskParts.isEmpty { + Logger.info("Reassembling disk image...") + let outputURL = URL(fileURLWithPath: vmDir.dir.path).appendingPathComponent("disk.img") + try FileManager.default.createDirectory(at: outputURL.deletingLastPathComponent(), withIntermediateDirectories: true) + + // Create empty output file + FileManager.default.createFile(atPath: outputURL.path, contents: nil) + let outputHandle = try FileHandle(forWritingTo: outputURL) + defer { try? outputHandle.close() } + + var totalWritten: UInt64 = 0 + let expectedTotalSize = UInt64(manifest.layers.filter { extractPartInfo(from: $0.mediaType) != nil }.reduce(0) { $0 + $1.size }) + + // Process parts in order + for partNum in 1...totalParts { + guard let (_, partURL) = diskParts.first(where: { $0.0 == partNum }) else { + throw PullError.missingPart(partNum) + } + + let inputHandle = try FileHandle(forReadingFrom: partURL) + defer { + try? inputHandle.close() + try? FileManager.default.removeItem(at: partURL) + } + + // Read and write in chunks to minimize memory usage + let chunkSize = 10 * 1024 * 1024 // 10MB chunks + while let chunk = try inputHandle.read(upToCount: chunkSize) { + try outputHandle.write(contentsOf: chunk) + totalWritten += UInt64(chunk.count) + let progress: Double = Double(totalWritten) / Double(expectedTotalSize) * 100 + Logger.info("Reassembling disk image: \(Int(progress))%") + } + } + + // Verify final size + let finalSize = try FileManager.default.attributesOfItem(atPath: outputURL.path)[.size] as? UInt64 ?? 0 + Logger.info("Final disk image size: \(ByteCountFormatter.string(fromByteCount: Int64(finalSize), countStyle: .file))") + Logger.info("Expected size: \(ByteCountFormatter.string(fromByteCount: Int64(expectedTotalSize), countStyle: .file))") + + if finalSize != expectedTotalSize { + Logger.info("Warning: Final size (\(finalSize) bytes) differs from expected size (\(expectedTotalSize) bytes)") + } + + Logger.info("Disk image reassembled successfully") + } else { + // Copy single disk image if it exists + let diskURL = tempDir.appendingPathComponent("disk.img") + if FileManager.default.fileExists(atPath: diskURL.path) { + try FileManager.default.copyItem( + at: diskURL, + to: URL(fileURLWithPath: vmDir.dir.path).appendingPathComponent("disk.img") + ) + } + } + + // Copy config and nvram files if they exist + for file in ["config.json", "nvram.bin"] { + let sourceURL = tempDir.appendingPathComponent(file) + if FileManager.default.fileExists(atPath: sourceURL.path) { + try FileManager.default.copyItem( + at: sourceURL, + to: URL(fileURLWithPath: vmDir.dir.path).appendingPathComponent(file) + ) + } + } + + Logger.info("Download complete: Files extracted to \(vmDir.dir.path)") + + // If this was a "latest" tag pull and we successfully downloaded and cached the new version, + // clean up any old versions + if tag.lowercased() == "latest" { + let orgDir = cacheDirectory.appendingPathComponent(organization) + if FileManager.default.fileExists(atPath: orgDir.path) { + let contents = try FileManager.default.contentsOfDirectory(atPath: orgDir.path) + for item in contents { + // Skip if it's the current manifest + if item == manifestId { continue } + + let itemPath = orgDir.appendingPathComponent(item) + var isDirectory: ObjCBool = false + guard FileManager.default.fileExists(atPath: itemPath.path, isDirectory: &isDirectory), + isDirectory.boolValue else { continue } + + // Check for manifest.json + let manifestPath = itemPath.appendingPathComponent("manifest.json") + guard let manifestData = try? Data(contentsOf: manifestPath), + let oldManifest = try? JSONDecoder().decode(Manifest.self, from: manifestData), + let config = oldManifest.config else { continue } + let configPath = getCachedLayerPath(manifestId: item, digest: config.digest) + guard let configData = try? Data(contentsOf: configPath), + let configJson = try? JSONSerialization.jsonObject(with: configData) as? [String: Any], + let labels = configJson["config"] as? [String: Any], + let imageConfig = labels["Labels"] as? [String: String], + let oldRepository = imageConfig["org.opencontainers.image.source"]?.components(separatedBy: "/").last else { continue } + + // Only delete if it's from the same repository + if oldRepository == imageName { + try FileManager.default.removeItem(at: itemPath) + Logger.info("Removed outdated cached version", metadata: [ + "old_manifest_id": item, + "repository": imageName + ]) + } + } + } + } + } + + private func copyFromCache(manifest: Manifest, manifestId: String, to destination: URL) async throws { + Logger.info("Copying from cache...") + var diskParts: [(Int, URL)] = [] + var totalParts = 0 + var expectedTotalSize: UInt64 = 0 + + for layer in manifest.layers { + let cachedLayer = getCachedLayerPath(manifestId: manifestId, digest: layer.digest) + + if let partInfo = extractPartInfo(from: layer.mediaType) { + let (partNum, total) = partInfo + totalParts = total + let partURL = destination.appendingPathComponent("disk.img.part.\(partNum)") + try FileManager.default.copyItem(at: cachedLayer, to: partURL) + diskParts.append((partNum, partURL)) + expectedTotalSize += UInt64(layer.size) + } else { + let fileName: String + switch layer.mediaType { + case "application/vnd.oci.image.layer.v1.tar": + fileName = "disk.img" + case "application/vnd.oci.image.config.v1+json": + fileName = "config.json" + case "application/octet-stream": + fileName = "nvram.bin" + default: + continue + } + try FileManager.default.copyItem( + at: cachedLayer, + to: destination.appendingPathComponent(fileName) + ) + } + } + + // Reassemble disk parts if needed + if !diskParts.isEmpty { + Logger.info("Reassembling disk image from cached parts...") + let outputURL = destination.appendingPathComponent("disk.img") + FileManager.default.createFile(atPath: outputURL.path, contents: nil) + let outputHandle = try FileHandle(forWritingTo: outputURL) + defer { try? outputHandle.close() } + + var totalWritten: UInt64 = 0 + + for partNum in 1...totalParts { + guard let (_, partURL) = diskParts.first(where: { $0.0 == partNum }) else { + throw PullError.missingPart(partNum) + } + + let inputHandle = try FileHandle(forReadingFrom: partURL) + while let data = try inputHandle.read(upToCount: 1024 * 1024 * 10) { + try outputHandle.write(contentsOf: data) + totalWritten += UInt64(data.count) + } + try inputHandle.close() + try FileManager.default.removeItem(at: partURL) + } + + // Verify final size + let finalSize = try FileManager.default.attributesOfItem(atPath: outputURL.path)[.size] as? UInt64 ?? 0 + Logger.info("Final disk image size: \(ByteCountFormatter.string(fromByteCount: Int64(finalSize), countStyle: .file))") + Logger.info("Expected size: \(ByteCountFormatter.string(fromByteCount: Int64(expectedTotalSize), countStyle: .file))") + + if finalSize != expectedTotalSize { + Logger.info("Warning: Final size (\(finalSize) bytes) differs from expected size (\(expectedTotalSize) bytes)") + } + } + + Logger.info("Cache copy complete") + } + + private func getToken(repository: String) async throws -> String { + let url = URL(string: "https://\(self.registry)/token")! + .appending(queryItems: [ + URLQueryItem(name: "service", value: self.registry), + URLQueryItem(name: "scope", value: "repository:\(repository):pull") + ]) + + let (data, _) = try await URLSession.shared.data(from: url) + let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] + guard let token = json?["token"] as? String else { + throw PullError.tokenFetchFailed + } + return token + } + + private func fetchManifest(repository: String, tag: String, token: String) async throws -> Manifest { + var request = URLRequest(url: URL(string: "https://\(self.registry)/v2/\(repository)/manifests/\(tag)")!) + request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + request.addValue("application/vnd.oci.image.manifest.v1+json", forHTTPHeaderField: "Accept") + + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw PullError.manifestFetchFailed + } + + return try JSONDecoder().decode(Manifest.self, from: data) + } + + private func downloadLayer( + repository: String, + digest: String, + mediaType: String, + token: String, + to url: URL, + maxRetries: Int = 5, + progress: isolated ProgressTracker + ) async throws { + var lastError: Error? + + for attempt in 1...maxRetries { + do { + var request = URLRequest(url: URL(string: "https://\(self.registry)/v2/\(repository)/blobs/\(digest)")!) + request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + request.addValue(mediaType, forHTTPHeaderField: "Accept") + request.timeoutInterval = 60 + + // Configure session for better reliability + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 60 + config.timeoutIntervalForResource = 3600 + config.waitsForConnectivity = true + config.httpMaximumConnectionsPerHost = 1 + + let session = URLSession(configuration: config) + + let (tempURL, response) = try await session.download(for: request) + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 else { + throw PullError.layerDownloadFailed(digest) + } + + try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + try FileManager.default.moveItem(at: tempURL, to: url) + progress.addProgress(Int64(httpResponse.expectedContentLength)) + return + + } catch { + lastError = error + if attempt < maxRetries { + let delay = Double(attempt) * 5 + try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000)) + } + } + } + + throw lastError ?? PullError.layerDownloadFailed(digest) + } + + private func decompressGzipFile(at source: URL, to destination: URL) throws { + Logger.info("Decompressing \(source.lastPathComponent)...") + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/gunzip") + process.arguments = ["-c"] + + let inputPipe = Pipe() + let outputPipe = Pipe() + process.standardInput = inputPipe + process.standardOutput = outputPipe + + try process.run() + + // Read and pipe the gzipped file in chunks to avoid memory issues + let inputHandle = try FileHandle(forReadingFrom: source) + let outputHandle = try FileHandle(forWritingTo: destination) + defer { + try? inputHandle.close() + try? outputHandle.close() + } + + // Create the output file + FileManager.default.createFile(atPath: destination.path, contents: nil) + + // Process in 10MB chunks + let chunkSize = 10 * 1024 * 1024 + while let chunk = try inputHandle.read(upToCount: chunkSize) { + try inputPipe.fileHandleForWriting.write(contentsOf: chunk) + + // Read and write output in chunks as well + while let decompressedChunk = try outputPipe.fileHandleForReading.read(upToCount: chunkSize) { + try outputHandle.write(contentsOf: decompressedChunk) + } + } + + try inputPipe.fileHandleForWriting.close() + + // Read any remaining output + while let decompressedChunk = try outputPipe.fileHandleForReading.read(upToCount: chunkSize) { + try outputHandle.write(contentsOf: decompressedChunk) + } + + process.waitUntilExit() + + if process.terminationStatus != 0 { + throw PullError.decompressionFailed(source.lastPathComponent) + } + + // Verify the decompressed size + let decompressedSize = try FileManager.default.attributesOfItem(atPath: destination.path)[.size] as? UInt64 ?? 0 + Logger.info("Decompressed size: \(ByteCountFormatter.string(fromByteCount: Int64(decompressedSize), countStyle: .file))") + } + + private func extractPartInfo(from mediaType: String) -> (partNum: Int, total: Int)? { + let pattern = #"part\.number=(\d+);part\.total=(\d+)"# + guard let regex = try? NSRegularExpression(pattern: pattern), + let match = regex.firstMatch( + in: mediaType, + range: NSRange(mediaType.startIndex..., in: mediaType) + ), + let partNumRange = Range(match.range(at: 1), in: mediaType), + let totalRange = Range(match.range(at: 2), in: mediaType), + let partNum = Int(mediaType[partNumRange]), + let total = Int(mediaType[totalRange]) else { + return nil + } + return (partNum, total) + } + + private func listRepositories() async throws -> [String] { + var request = URLRequest(url: URL(string: "https://\(registry)/v2/\(organization)/repositories/list")!) + request.setValue("application/json", forHTTPHeaderField: "Accept") + + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse else { + throw PullError.manifestFetchFailed + } + + if httpResponse.statusCode == 404 { + return [] + } + + guard httpResponse.statusCode == 200 else { + throw PullError.manifestFetchFailed + } + + let repoList = try JSONDecoder().decode(RepositoryList.self, from: data) + return repoList.repositories + } + + func getImages() async throws -> [CachedImage] { + var images: [CachedImage] = [] + let orgDir = cacheDirectory.appendingPathComponent(organization) + + if FileManager.default.fileExists(atPath: orgDir.path) { + let contents = try FileManager.default.contentsOfDirectory(atPath: orgDir.path) + for item in contents { + let itemPath = orgDir.appendingPathComponent(item) + var isDirectory: ObjCBool = false + + // Check if it's a directory + guard FileManager.default.fileExists(atPath: itemPath.path, isDirectory: &isDirectory), + isDirectory.boolValue else { continue } + + // Check for manifest.json + let manifestPath = itemPath.appendingPathComponent("manifest.json") + guard FileManager.default.fileExists(atPath: manifestPath.path), + let manifestData = try? Data(contentsOf: manifestPath), + let manifest = try? JSONDecoder().decode(Manifest.self, from: manifestData) else { continue } + + // The directory name is now just the manifest ID + let manifestId = item + + // Verify the manifest ID matches + let currentManifestId = getManifestIdentifier(manifest) + if currentManifestId == manifestId { + // Add the image with just the manifest ID for now + images.append(CachedImage( + repository: "unknown", + tag: "unknown", + manifestId: manifestId + )) + } + } + } + + // For each cached image, try to find its repository and tag by checking the config + for i in 0.. [String] { + var request = URLRequest(url: URL(string: "https://\(registry)/v2/\(organization)/\(repository)/tags/list")!) + request.setValue("application/json", forHTTPHeaderField: "Accept") + + let (data, response) = try await URLSession.shared.data(for: request) + guard let httpResponse = response as? HTTPURLResponse else { + throw PullError.manifestFetchFailed + } + + if httpResponse.statusCode == 404 { + return [] + } + + guard httpResponse.statusCode == 200 else { + throw PullError.manifestFetchFailed + } + + let repoTags = try JSONDecoder().decode(RepositoryTags.self, from: data) + return repoTags.tags + } +} \ No newline at end of file diff --git a/src/ContainerRegistry/ImageList.swift b/src/ContainerRegistry/ImageList.swift new file mode 100644 index 00000000..d5268946 --- /dev/null +++ b/src/ContainerRegistry/ImageList.swift @@ -0,0 +1,4 @@ + public struct ImageList: Codable { + public let local: [String] + public let remote: [String] + } \ No newline at end of file diff --git a/src/ContainerRegistry/ImagesPrinter.swift b/src/ContainerRegistry/ImagesPrinter.swift new file mode 100644 index 00000000..83ee8211 --- /dev/null +++ b/src/ContainerRegistry/ImagesPrinter.swift @@ -0,0 +1,42 @@ +import Foundation + +struct ImagesPrinter { + private struct Column: Sendable { + let header: String + let width: Int + let getValue: @Sendable (String) -> String + } + + private static let columns: [Column] = [ + Column(header: "name", width: 28) { $0.split(separator: ":").first.map(String.init) ?? $0 }, + Column(header: "tag", width: 16) { $0.split(separator: ":").last.map(String.init) ?? "-" } + ] + + static func print(images: [String]) { + if images.isEmpty { + Swift.print("No images found") + return + } + + printHeader() + images.sorted().forEach(printImage) + } + + private static func printHeader() { + let paddedHeaders = columns.map { $0.header.paddedToWidth($0.width) } + Swift.print(paddedHeaders.joined()) + } + + private static func printImage(_ image: String) { + let paddedColumns = columns.map { column in + column.getValue(image).paddedToWidth(column.width) + } + Swift.print(paddedColumns.joined()) + } +} + +private extension String { + func paddedToWidth(_ width: Int) -> String { + padding(toLength: width, withPad: " ", startingAt: 0) + } +} \ No newline at end of file diff --git a/src/Errors/Errors.swift b/src/Errors/Errors.swift new file mode 100644 index 00000000..f396c888 --- /dev/null +++ b/src/Errors/Errors.swift @@ -0,0 +1,157 @@ +import Foundation + +enum HomeError: Error, LocalizedError { + case directoryCreationFailed(path: String) + case directoryAccessDenied(path: String) + case invalidHomeDirectory + case directoryAlreadyExists(path: String) + + var errorDescription: String? { + switch self { + case .directoryCreationFailed(let path): + return "Failed to create directory at path: \(path)" + case .directoryAccessDenied(let path): + return "Access denied to directory at path: \(path)" + case .invalidHomeDirectory: + return "Invalid home directory configuration" + case .directoryAlreadyExists(let path): + return "Directory already exists at path: \(path)" + } + } +} + +enum PullError: Error, LocalizedError { + case invalidImageFormat + case tokenFetchFailed + case manifestFetchFailed + case layerDownloadFailed(String) + case missingPart(Int) + case decompressionFailed(String) + + var errorDescription: String? { + switch self { + case .invalidImageFormat: + return "Invalid image format. Expected format: name:tag" + case .tokenFetchFailed: + return "Failed to obtain authentication token" + case .manifestFetchFailed: + return "Failed to fetch manifest" + case .layerDownloadFailed(let digest): + return "Failed to download layer: \(digest)" + case .missingPart(let number): + return "Missing disk image part \(number)" + case .decompressionFailed(let filename): + return "Failed to decompress file: \(filename)" + } + } +} + +enum VMConfigError: CustomNSError, LocalizedError { + case invalidDisplayResolution(String) + case invalidMachineIdentifier + case emptyMachineIdentifier + case emptyHardwareModel + case invalidHardwareModel + case invalidDiskSize + case malformedSizeInput(String) + + var errorDescription: String? { + switch self { + case .invalidDisplayResolution(let resolution): + return "Invalid display resolution: \(resolution)" + case .emptyMachineIdentifier: + return "Empty machine identifier" + case .invalidMachineIdentifier: + return "Invalid machine identifier" + case .emptyHardwareModel: + return "Empty hardware model" + case .invalidHardwareModel: + return "Invalid hardware model: the host does not support the hardware model" + case .invalidDiskSize: + return "Invalid disk size" + case .malformedSizeInput(let input): + return "Malformed size input: \(input)" + } + } + + static var errorDomain: String { "VMConfigError" } + + var errorCode: Int { + switch self { + case .invalidDisplayResolution: return 1 + case .emptyMachineIdentifier: return 2 + case .invalidMachineIdentifier: return 3 + case .emptyHardwareModel: return 4 + case .invalidHardwareModel: return 5 + case .invalidDiskSize: return 6 + case .malformedSizeInput: return 7 + } + } +} + +enum VMDirectoryError: Error, LocalizedError { + case configNotFound + case invalidConfigData + case diskOperationFailed(String) + case fileCreationFailed(String) + case sessionNotFound + case invalidSessionData + + var errorDescription: String { + switch self { + case .configNotFound: + return "VM configuration file not found" + case .invalidConfigData: + return "Invalid VM configuration data" + case .diskOperationFailed(let reason): + return "Disk operation failed: \(reason)" + case .fileCreationFailed(let path): + return "Failed to create file at path: \(path)" + case .sessionNotFound: + return "VNC session file not found" + case .invalidSessionData: + return "Invalid VNC session data" + } + } +} + +enum VMError: Error, LocalizedError { + case alreadyExists(String) + case notFound(String) + case notInitialized(String) + case notRunning(String) + case alreadyRunning(String) + case installNotStarted(String) + case stopTimeout(String) + case resizeTooSmall(current: UInt64, requested: UInt64) + case vncNotConfigured + case internalError(String) + case unsupportedOS(String) + + var errorDescription: String? { + switch self { + case .alreadyExists(let name): + return "Virtual machine already exists with name: \(name)" + case .notFound(let name): + return "Virtual machine not found: \(name)" + case .notInitialized(let name): + return "Virtual machine not initialized: \(name)" + case .notRunning(let name): + return "Virtual machine not running: \(name)" + case .alreadyRunning(let name): + return "Virtual machine already running: \(name)" + case .installNotStarted(let name): + return "Virtual machine install not started: \(name)" + case .stopTimeout(let name): + return "Timeout while stopping virtual machine: \(name)" + case .resizeTooSmall(let current, let requested): + return "Cannot resize disk to \(requested) bytes, current size is \(current) bytes" + case .vncNotConfigured: + return "VNC is not configured for this virtual machine" + case .internalError(let message): + return "Internal error: \(message)" + case .unsupportedOS(let os): + return "Unsupported operating system: \(os)" + } + } +} \ No newline at end of file diff --git a/src/FileSystem/Home.swift b/src/FileSystem/Home.swift new file mode 100644 index 00000000..571a7f92 --- /dev/null +++ b/src/FileSystem/Home.swift @@ -0,0 +1,146 @@ +import Foundation + +/// Manages the application's home directory and virtual machine directories. +/// Responsible for creating, accessing, and validating the application's directory structure. +final class Home { + // MARK: - Constants + + private enum Constants { + static let defaultDirectoryName = ".lume" + static let homeDirPath = "~/\(defaultDirectoryName)" + } + + // MARK: - Properties + + let homeDir: Path + private let fileManager: FileManager + + // MARK: - Initialization + + init(fileManager: FileManager = .default) { + self.fileManager = fileManager + self.homeDir = Path(Constants.homeDirPath) + } + + // MARK: - VM Directory Management + + /// Creates a temporary VM directory with a unique identifier + /// - Returns: A VMDirectory instance representing the created directory + /// - Throws: HomeError if directory creation fails + func createTempVMDirectory() throws -> VMDirectory { + let uuid = UUID().uuidString + let tempDir = homeDir.directory(uuid) + + Logger.info("Creating temporary directory", metadata: ["path": tempDir.path]) + + do { + try createDirectory(at: tempDir.url) + return VMDirectory(tempDir) + } catch { + throw HomeError.directoryCreationFailed(path: tempDir.path) + } + } + + /// Returns a VMDirectory instance for the given name + /// - Parameter name: Name of the VM directory + /// - Returns: A VMDirectory instance + func getVMDirectory(_ name: String) -> VMDirectory { + VMDirectory(homeDir.directory(name)) + } + + /// Returns all initialized VM directories + /// - Returns: An array of VMDirectory instances + /// - Throws: HomeError if directory access is denied + func getAllVMDirectories() throws -> [VMDirectory] { + guard homeDir.exists() else { return [] } + + do { + let allFolders = try fileManager.contentsOfDirectory( + at: homeDir.url, + includingPropertiesForKeys: nil + ) + let folders = allFolders + .compactMap { url in + let sanitizedName = sanitizeFileName(url.lastPathComponent) + let dir = getVMDirectory(sanitizedName) + let dir1 = dir.initialized() ? dir : nil + return dir1 + } + return folders + } catch { + throw HomeError.directoryAccessDenied(path: homeDir.path) + } + } + + /// Copies a VM directory to a new location with a new name + /// - Parameters: + /// - sourceName: Name of the source VM + /// - destName: Name for the destination VM + /// - Throws: HomeError if the copy operation fails + func copyVMDirectory(from sourceName: String, to destName: String) throws { + let sourceDir = getVMDirectory(sourceName) + let destDir = getVMDirectory(destName) + + if destDir.initialized() { + throw HomeError.directoryAlreadyExists(path: destDir.dir.path) + } + + do { + try fileManager.copyItem(atPath: sourceDir.dir.path, toPath: destDir.dir.path) + } catch { + throw HomeError.directoryCreationFailed(path: destDir.dir.path) + } + } + + // MARK: - Directory Validation + + /// Validates and ensures the existence of the home directory + /// - Throws: HomeError if validation fails or directory creation fails + func validateHomeDirectory() throws { + if !homeDir.exists() { + try createHomeDirectory() + return + } + + guard isValidDirectory(at: homeDir.path) else { + throw HomeError.invalidHomeDirectory + } + } + + // MARK: - Private Helpers + + private func createHomeDirectory() throws { + do { + try createDirectory(at: homeDir.url) + } catch { + throw HomeError.directoryCreationFailed(path: homeDir.path) + } + } + + private func createDirectory(at url: URL) throws { + try fileManager.createDirectory( + at: url, + withIntermediateDirectories: true + ) + } + + private func isValidDirectory(at path: String) -> Bool { + var isDirectory: ObjCBool = false + return fileManager.fileExists(atPath: path, isDirectory: &isDirectory) + && isDirectory.boolValue + && Path(path).writable() + } + + private func sanitizeFileName(_ name: String) -> String { + // Only decode percent encoding (e.g., %20 for spaces) + return name.removingPercentEncoding ?? name + } +} + +// MARK: - Home + CustomStringConvertible + +extension Home: CustomStringConvertible { + var description: String { + "Home(path: \(homeDir.path))" + } +} \ No newline at end of file diff --git a/src/FileSystem/VMConfig.swift b/src/FileSystem/VMConfig.swift new file mode 100644 index 00000000..09bc3b80 --- /dev/null +++ b/src/FileSystem/VMConfig.swift @@ -0,0 +1,141 @@ +import ArgumentParser +import Foundation +import Virtualization + +/// Represents a shared directory configuration +struct SharedDirectory { + let hostPath: String + let tag: String + let readOnly: Bool + + var string: String { + return "\(hostPath):\(tag):\(readOnly ? "ro" : "rw")" + } +} + +// MARK: - VMConfig +struct VMConfig: Codable { + + // MARK: - Properties + let os: String + private var _cpuCount: Int? + private var _memorySize: UInt64? + private var _diskSize: UInt64? + private var _macAddress: String? + let display: VMDisplayResolution + private var _hardwareModel: Data? + private var _machineIdentifier: Data? + + // MARK: - Initialization + init( + os: String, + cpuCount: Int? = nil, + memorySize: UInt64? = nil, + diskSize: UInt64? = nil, + macAddress: String? = nil, + display: String, + hardwareModel: Data? = nil, + machineIdentifier: Data? = nil + ) throws { + self.os = os + self._cpuCount = cpuCount + self._memorySize = memorySize + self._diskSize = diskSize + self._macAddress = macAddress + self.display = VMDisplayResolution(string: display) ?? VMDisplayResolution(string: "1024x768")! + self._hardwareModel = hardwareModel + self._machineIdentifier = machineIdentifier + } + + var cpuCount: Int? { + get { _cpuCount } + set { _cpuCount = newValue } + } + + var memorySize: UInt64? { + get { _memorySize } + set { _memorySize = newValue } + } + + var diskSize: UInt64? { + get { _diskSize } + set { _diskSize = newValue } + } + + var hardwareModel: Data? { + get { _hardwareModel } + set { _hardwareModel = newValue } + } + + var machineIdentifier: Data? { + get { _machineIdentifier } + set { _machineIdentifier = newValue } + } + + var macAddress: String? { + get { _macAddress } + set { _macAddress = newValue } + } + + mutating func setCpuCount(_ count: Int) { + _cpuCount = count + } + + mutating func setMemorySize(_ size: UInt64) { + _memorySize = size + } + + mutating func setDiskSize(_ size: UInt64) { + _diskSize = size + } + + mutating func setHardwareModel(_ hardwareModel: Data) { + _hardwareModel = hardwareModel + } + + mutating func setMachineIdentifier(_ machineIdentifier: Data) { + _machineIdentifier = machineIdentifier + } + + mutating func setMacAddress(_ macAddress: String) { + _macAddress = macAddress + } + + // MARK: - Codable + enum CodingKeys: String, CodingKey { + case _cpuCount = "cpuCount" + case _memorySize = "memorySize" + case _diskSize = "diskSize" + case macAddress + case display + case _hardwareModel = "hardwareModel" + case _machineIdentifier = "machineIdentifier" + case os + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + os = try container.decode(String.self, forKey: .os) + _cpuCount = try container.decodeIfPresent(Int.self, forKey: ._cpuCount) + _memorySize = try container.decodeIfPresent(UInt64.self, forKey: ._memorySize) + _diskSize = try container.decodeIfPresent(UInt64.self, forKey: ._diskSize) + _macAddress = try container.decodeIfPresent(String.self, forKey: .macAddress) + display = VMDisplayResolution(string: try container.decode(String.self, forKey: .display))! + _hardwareModel = try container.decodeIfPresent(Data.self, forKey: ._hardwareModel) + _machineIdentifier = try container.decodeIfPresent(Data.self, forKey: ._machineIdentifier) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encodeIfPresent(os, forKey: .os) + try container.encodeIfPresent(_cpuCount, forKey: ._cpuCount) + try container.encodeIfPresent(_memorySize, forKey: ._memorySize) + try container.encodeIfPresent(_diskSize, forKey: ._diskSize) + try container.encodeIfPresent(_macAddress, forKey: .macAddress) + try container.encode(display.string, forKey: .display) + try container.encodeIfPresent(_hardwareModel, forKey: ._hardwareModel) + try container.encodeIfPresent(_machineIdentifier, forKey: ._machineIdentifier) + } +} diff --git a/src/FileSystem/VMDirectory.swift b/src/FileSystem/VMDirectory.swift new file mode 100644 index 00000000..a902e34b --- /dev/null +++ b/src/FileSystem/VMDirectory.swift @@ -0,0 +1,181 @@ +import Foundation + +// MARK: - VMDirectory + +/// Manages a virtual machine's directory structure and files +/// Responsible for: +/// - Managing VM configuration files +/// - Handling disk operations +/// - Managing VM state and locking +/// - Providing access to VM-related paths +struct VMDirectory { + // MARK: - Constants + + private enum FileNames { + static let nvram = "nvram.bin" + static let disk = "disk.img" + static let config = "config.json" + static let sessions = "sessions.json" + } + + // MARK: - Properties + + let dir: Path + let nvramPath: Path + let diskPath: Path + let configPath: Path + let sessionsPath: Path + + private let fileManager: FileManager + + /// The name of the VM directory + var name: String { dir.name } + + // MARK: - Initialization + + /// Creates a new VMDirectory instance + /// - Parameters: + /// - dir: The base directory path for the VM + /// - fileManager: FileManager instance to use for file operations + init(_ dir: Path, fileManager: FileManager = .default) { + self.dir = dir + self.fileManager = fileManager + self.nvramPath = dir.file(FileNames.nvram) + self.diskPath = dir.file(FileNames.disk) + self.configPath = dir.file(FileNames.config) + self.sessionsPath = dir.file(FileNames.sessions) + } +} + +// MARK: - VM State Management + +extension VMDirectory { + /// Checks if the VM directory is fully initialized with all required files + func initialized() -> Bool { + configPath.exists() && diskPath.exists() && nvramPath.exists() + } + + /// Checks if the VM directory exists + func exists() -> Bool { + dir.exists() + } +} + +// MARK: - Disk Management + +extension VMDirectory { + /// Resizes the VM's disk to the specified size + /// - Parameter size: The new size in bytes + /// - Throws: VMDirectoryError if the disk operation fails + func setDisk(_ size: UInt64) throws { + do { + if !diskPath.exists() { + guard fileManager.createFile(atPath: diskPath.path, contents: nil) else { + throw VMDirectoryError.fileCreationFailed(diskPath.path) + } + } + + let handle = try FileHandle(forWritingTo: diskPath.url) + defer { try? handle.close() } + + try handle.truncate(atOffset: size) + } catch { + } + } +} + +// MARK: - Configuration Management + +extension VMDirectory { + /// Saves the VM configuration to disk + /// - Parameter config: The configuration to save + /// - Throws: VMDirectoryError if the save operation fails + func saveConfig(_ config: VMConfig) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + do { + let data = try encoder.encode(config) + guard fileManager.createFile(atPath: configPath.path, contents: data) else { + throw VMDirectoryError.fileCreationFailed(configPath.path) + } + } catch { + throw VMDirectoryError.invalidConfigData + } + } + + /// Loads the VM configuration from disk + /// - Returns: The loaded configuration + /// - Throws: VMDirectoryError if the load operation fails + func loadConfig() throws -> VMConfig { + guard let data = fileManager.contents(atPath: configPath.path) else { + throw VMDirectoryError.configNotFound + } + + do { + let decoder = JSONDecoder() + return try decoder.decode(VMConfig.self, from: data) + } catch { + throw VMDirectoryError.invalidConfigData + } + } +} + +// MARK: - VNC Session Management + +struct VNCSession: Codable { + let url: String +} + +extension VMDirectory { + /// Saves VNC session information to disk + /// - Parameter session: The VNC session to save + /// - Throws: VMDirectoryError if the save operation fails + func saveSession(_ session: VNCSession) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + do { + let data = try encoder.encode(session) + guard fileManager.createFile(atPath: sessionsPath.path, contents: data) else { + throw VMDirectoryError.fileCreationFailed(sessionsPath.path) + } + } catch { + throw VMDirectoryError.invalidSessionData + } + } + + /// Loads the VNC session information from disk + /// - Returns: The loaded VNC session + /// - Throws: VMDirectoryError if the load operation fails + func loadSession() throws -> VNCSession { + guard let data = fileManager.contents(atPath: sessionsPath.path) else { + throw VMDirectoryError.sessionNotFound + } + + do { + let decoder = JSONDecoder() + return try decoder.decode(VNCSession.self, from: data) + } catch { + throw VMDirectoryError.invalidSessionData + } + } + + /// Removes the VNC session information from disk + func clearSession() { + try? fileManager.removeItem(atPath: sessionsPath.path) + } +} + +// MARK: - CustomStringConvertible +extension VMDirectory: CustomStringConvertible { + var description: String { + "VMDirectory(path: \(dir.path))" + } +} + +extension VMDirectory { + func delete() throws { + try fileManager.removeItem(atPath: dir.path) + } +} diff --git a/src/LumeController.swift b/src/LumeController.swift new file mode 100644 index 00000000..e4d3a661 --- /dev/null +++ b/src/LumeController.swift @@ -0,0 +1,514 @@ +import ArgumentParser +import Foundation +import Virtualization + +// MARK: - Shared VM Manager + +@MainActor +final class SharedVM { + static let shared: SharedVM = SharedVM() + private var runningVMs: [String: VM] = [:] + + private init() {} + + func getVM(name: String) -> VM? { + return runningVMs[name] + } + + func setVM(name: String, vm: VM) { + runningVMs[name] = vm + } + + func removeVM(name: String) { + runningVMs.removeValue(forKey: name) + } +} + +/// Entrypoint for Commands and API server +final class LumeController { + // MARK: - Properties + + let home: Home + private let imageLoaderFactory: ImageLoaderFactory + private let vmFactory: VMFactory + + // MARK: - Initialization + + init( + home: Home = Home(), + imageLoaderFactory: ImageLoaderFactory = DefaultImageLoaderFactory(), + vmFactory: VMFactory = DefaultVMFactory() + ) { + self.home = home + self.imageLoaderFactory = imageLoaderFactory + self.vmFactory = vmFactory + } + + // MARK: - Public VM Management Methods + + /// Lists all virtual machines in the system + @MainActor + public func list() throws -> [VMDetails] { + do { + let statuses = try home.getAllVMDirectories().map { directory in + let vm = try self.get(name: directory.name) + return vm.details + } + return statuses + } catch { + Logger.error("Failed to list VMs", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func clone(name: String, newName: String) throws { + Logger.info("Cloning VM", metadata: ["source": name, "destination": newName]) + + do { + try self.validateVMExists(name) + + // Copy the VM directory + try home.copyVMDirectory(from: name, to: newName) + + // Update MAC address in the cloned VM to ensure uniqueness + let clonedVM = try get(name: newName) + try clonedVM.setMacAddress(VZMACAddress.randomLocallyAdministered().string) + + Logger.info("VM cloned successfully", metadata: ["source": name, "destination": newName]) + } catch { + Logger.error("Failed to clone VM", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func get(name: String) throws -> VM { + do { + try self.validateVMExists(name) + + let vm = try self.loadVM(name: name) + return vm + } catch { + Logger.error("Failed to get VM", metadata: ["error": error.localizedDescription]) + throw error + } + } + + /// Factory for creating the appropriate VM type based on the OS + @MainActor + public func create( + name: String, + os: String, + diskSize: UInt64, + cpuCount: Int, + memorySize: UInt64, + display: String, + ipsw: String? + ) async throws { + Logger.info( + "Creating VM", + metadata: [ + "name": name, + "os": os, + "disk_size": "\(diskSize / 1024 / 1024)MB", + "cpu_count": "\(cpuCount)", + "memory_size": "\(memorySize / 1024 / 1024)MB", + "display": display, + "ipsw": ipsw ?? "none", + ]) + + do { + try validateCreateParameters(name: name, os: os, ipsw: ipsw) + + let vm = try await createTempVMConfig( + os: os, + cpuCount: cpuCount, + memorySize: memorySize, + diskSize: diskSize, + display: display + ) + + try await vm.setup( + ipswPath: ipsw ?? "none", + cpuCount: cpuCount, + memorySize: memorySize, + diskSize: diskSize, + display: display + ) + + try vm.finalize(to: name, home: home) + + Logger.info("VM created successfully", metadata: ["name": name]) + } catch { + Logger.error("Failed to create VM", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func delete(name: String) async throws { + Logger.info("Deleting VM", metadata: ["name": name]) + + do { + try self.validateVMExists(name) + + // Stop VM if it's running + if SharedVM.shared.getVM(name: name) != nil { + try await stopVM(name: name) + } + + let vmDir = home.getVMDirectory(name) + try vmDir.delete() + Logger.info("VM deleted successfully", metadata: ["name": name]) + + } catch { + Logger.error("Failed to delete VM", metadata: ["error": error.localizedDescription]) + throw error + } + } + + // MARK: - VM Operations + + @MainActor + public func updateSettings( + name: String, + cpu: Int? = nil, + memory: UInt64? = nil, + diskSize: UInt64? = nil + ) throws { + Logger.info( + "Updating VM settings", + metadata: [ + "name": name, + "cpu": cpu.map { "\($0)" } ?? "unchanged", + "memory": memory.map { "\($0 / 1024 / 1024)MB" } ?? "unchanged", + "disk_size": diskSize.map { "\($0 / 1024 / 1024)MB" } ?? "unchanged", + ]) + do { + try self.validateVMExists(name) + + let vm = try get(name: name) + + // Apply settings in order + if let cpu = cpu { + try vm.setCpuCount(cpu) + } + if let memory = memory { + try vm.setMemorySize(memory) + } + if let diskSize = diskSize { + try vm.setDiskSize(diskSize) + } + + Logger.info("VM settings updated successfully", metadata: ["name": name]) + } catch { + Logger.error( + "Failed to update VM settings", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func stopVM(name: String) async throws { + Logger.info("Stopping VM", metadata: ["name": name]) + + do { + try self.validateVMExists(name) + + // Try to get VM from cache first + let vm: VM + if let cachedVM = SharedVM.shared.getVM(name: name) { + vm = cachedVM + } else { + vm = try get(name: name) + } + + try await vm.stop() + // Remove VM from cache after stopping + SharedVM.shared.removeVM(name: name) + Logger.info("VM stopped successfully", metadata: ["name": name]) + } catch { + // Clean up cache even if stop fails + SharedVM.shared.removeVM(name: name) + Logger.error("Failed to stop VM", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func runVM( + name: String, + noDisplay: Bool = false, + sharedDirectories: [SharedDirectory] = [], + mount: Path? = nil + ) async throws { + Logger.info( + "Running VM", + metadata: [ + "name": name, + "no_display": "\(noDisplay)", + "shared_directories": "\(sharedDirectories.map( { $0.string } ).joined(separator: ", "))", + "mount": mount?.path ?? "none", + ]) + + do { + try validateRunParameters( + name: name, sharedDirectories: sharedDirectories, mount: mount) + + let vm = try get(name: name) + SharedVM.shared.setVM(name: name, vm: vm) + try await vm.run(noDisplay: noDisplay, sharedDirectories: sharedDirectories, mount: mount) + Logger.info("VM started successfully", metadata: ["name": name]) + } catch { + SharedVM.shared.removeVM(name: name) + Logger.error("Failed to run VM", metadata: ["error": error.localizedDescription]) + throw error + } + } + + // MARK: - Image Management + + @MainActor + public func getLatestIPSWURL() async throws -> URL { + Logger.info("Fetching latest supported IPSW URL") + + do { + let imageLoader = DarwinImageLoader() + let url = try await imageLoader.fetchLatestSupportedURL() + Logger.info("Found latest IPSW URL", metadata: ["url": url.absoluteString]) + return url + } catch { + Logger.error( + "Failed to fetch IPSW URL", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func pullImage(image: String, name: String?, registry: String, organization: String) + async throws + { + do { + let vmName: String = name ?? image.split(separator: ":").first.map(String.init) ?? image + + Logger.info( + "Pulling image", + metadata: [ + "image": image, + "name": name ?? "default", + "registry": registry, + "organization": organization, + ]) + + try self.validatePullParameters( + image: image, name: vmName, registry: registry, organization: organization) + + let imageContainerRegistry = ImageContainerRegistry( + registry: registry, organization: organization) + try await imageContainerRegistry.pull(image: image, name: vmName) + + Logger.info("Setting new VM mac address") + + // Update MAC address in the cloned VM to ensure uniqueness + let vm = try get(name: vmName) + try vm.setMacAddress(VZMACAddress.randomLocallyAdministered().string) + + Logger.info( + "Image pulled successfully", + metadata: [ + "image": image, + "name": vmName, + "registry": registry, + "organization": organization, + ]) + } catch { + Logger.error("Failed to pull image", metadata: ["error": error.localizedDescription]) + throw error + } + } + + @MainActor + public func pruneImages() async throws { + Logger.info("Pruning cached images") + + do { + let home = FileManager.default.homeDirectoryForCurrentUser + let cacheDir = home.appendingPathComponent(".lume/cache/ghcr") + + if FileManager.default.fileExists(atPath: cacheDir.path) { + try FileManager.default.removeItem(at: cacheDir) + try FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true) + Logger.info("Successfully removed cached images") + } else { + Logger.info("No cached images found") + } + } catch { + Logger.error("Failed to prune images", metadata: ["error": error.localizedDescription]) + throw error + } + } + + public struct ImageInfo: Codable { + public let repository: String + public let tag: String + public let manifestId: String + + public var fullName: String { + return "\(repository):\(tag)" + } + } + + public struct ImageList: Codable { + public let local: [ImageInfo] + public let remote: [String] + } + + @MainActor + public func getImages(organization: String = "trycua") async throws -> ImageList { + Logger.info("Listing local images", metadata: ["organization": organization]) + + let imageContainerRegistry = ImageContainerRegistry(registry: "ghcr.io", organization: organization) + let cachedImages = try await imageContainerRegistry.getImages() + + let imageInfos = cachedImages.map { image in + ImageInfo(repository: image.repository, tag: image.tag, manifestId: image.manifestId) + } + + ImagesPrinter.print(images: imageInfos.map { $0.fullName }) + return ImageList(local: imageInfos, remote: []) + } + + // MARK: - Private Helper Methods + + @MainActor + private func createTempVMConfig( + os: String, + cpuCount: Int, + memorySize: UInt64, + diskSize: UInt64, + display: String + ) async throws -> VM { + let config = try VMConfig( + os: os, + cpuCount: cpuCount, + memorySize: memorySize, + diskSize: diskSize, + macAddress: VZMACAddress.randomLocallyAdministered().string, + display: display + ) + + let vmDirContext = VMDirContext( + dir: try home.createTempVMDirectory(), + config: config, + home: home + ) + + let imageLoader = os.lowercased() == "macos" ? imageLoaderFactory.createImageLoader() : nil + return try vmFactory.createVM(vmDirContext: vmDirContext, imageLoader: imageLoader) + } + + @MainActor + private func loadVM(name: String) throws -> VM { + let vmDir = home.getVMDirectory(name) + guard vmDir.initialized() else { + throw VMError.notInitialized(name) + } + + let config: VMConfig = try vmDir.loadConfig() + let vmDirContext = VMDirContext(dir: vmDir, config: config, home: home) + + let imageLoader = + config.os.lowercased() == "macos" ? imageLoaderFactory.createImageLoader() : nil + return try vmFactory.createVM(vmDirContext: vmDirContext, imageLoader: imageLoader) + } + + // MARK: - Validation Methods + + private func validateCreateParameters(name: String, os: String, ipsw: String?) throws { + if os.lowercased() == "macos" { + guard let ipsw = ipsw else { + throw ValidationError("IPSW path required for macOS VM") + } + if ipsw != "latest" && !FileManager.default.fileExists(atPath: ipsw) { + throw ValidationError("IPSW file not found") + } + } else if os.lowercased() == "linux" { + if ipsw != nil { + throw ValidationError("IPSW path not supported for Linux VM") + } + } else { + throw ValidationError("Unsupported OS type: \(os)") + } + + let vmDir = home.getVMDirectory(name) + if vmDir.exists() { + throw VMError.alreadyExists(name) + } + } + + private func validateSharedDirectories(_ directories: [SharedDirectory]) throws { + for dir in directories { + var isDirectory: ObjCBool = false + guard FileManager.default.fileExists(atPath: dir.hostPath, isDirectory: &isDirectory), + isDirectory.boolValue + else { + throw ValidationError( + "Host path does not exist or is not a directory: \(dir.hostPath)") + } + } + } + + public func validateVMExists(_ name: String) throws { + let vmDir = home.getVMDirectory(name) + guard vmDir.initialized() else { + throw VMError.notFound(name) + } + } + + private func validatePullParameters( + image: String, name: String, registry: String, organization: String + ) throws { + guard !image.isEmpty else { + throw ValidationError("Image name cannot be empty") + } + guard !name.isEmpty else { + throw ValidationError("VM name cannot be empty") + } + guard !registry.isEmpty else { + throw ValidationError("Registry cannot be empty") + } + guard !organization.isEmpty else { + throw ValidationError("Organization cannot be empty") + } + + let vmDir = home.getVMDirectory(name) + if vmDir.exists() { + throw VMError.alreadyExists(name) + } + } + + private func validateRunParameters( + name: String, sharedDirectories: [SharedDirectory]?, mount: Path? + ) throws { + try self.validateVMExists(name) + if let dirs: [SharedDirectory] = sharedDirectories { + try self.validateSharedDirectories(dirs) + } + let vmConfig = try home.getVMDirectory(name).loadConfig() + switch vmConfig.os.lowercased() { + case "macos": + if mount != nil { + throw ValidationError( + "Mounting disk images is not supported for macOS VMs. If you are looking to mount a IPSW, please use the --ipsw option in the create command." + ) + } + case "linux": + if let mount = mount, !FileManager.default.fileExists(atPath: mount.path) { + throw ValidationError("Mount file not found: \(mount.path)") + } + default: + break + } + } +} diff --git a/src/Main.swift b/src/Main.swift new file mode 100644 index 00000000..207bbcb0 --- /dev/null +++ b/src/Main.swift @@ -0,0 +1,43 @@ +import ArgumentParser +import Foundation + +@main +struct Lume: AsyncParsableCommand { + static var configuration: CommandConfiguration { + CommandConfiguration( + commandName: "lume", + abstract: "A lightweight CLI and local API server to build, run and manage macOS VMs.", + version: Version.current, + subcommands: CommandRegistry.allCommands, + helpNames: .long + ) + } +} + +// MARK: - Version Management +extension Lume { + enum Version { + static let current = "0.1.0" + } +} + +// MARK: - Command Execution +extension Lume { + public static func main() async { + do { + try await executeCommand() + } catch { + exit(withError: error) + } + } + + private static func executeCommand() async throws { + var command = try parseAsRoot() + + if var asyncCommand = command as? AsyncParsableCommand { + try await asyncCommand.run() + } else { + try command.run() + } + } +} \ No newline at end of file diff --git a/src/Server/HTTP.swift b/src/Server/HTTP.swift new file mode 100644 index 00000000..d2dd86a0 --- /dev/null +++ b/src/Server/HTTP.swift @@ -0,0 +1,113 @@ +import Foundation +import Network + +enum HTTPError: Error { + case internalError +} + +struct HTTPRequest { + let method: String + let path: String + let headers: [String: String] + let body: Data? + + init?(data: Data) { + guard let requestString = String(data: data, encoding: .utf8) else { return nil } + let components = requestString.components(separatedBy: "\r\n\r\n") + guard components.count >= 1 else { return nil } + + let headerLines = components[0].components(separatedBy: "\r\n") + guard !headerLines.isEmpty else { return nil } + + // Parse request line + let requestLine = headerLines[0].components(separatedBy: " ") + guard requestLine.count >= 2 else { return nil } + + self.method = requestLine[0] + self.path = requestLine[1] + + // Parse headers + var headers: [String: String] = [:] + for line in headerLines.dropFirst() { + let headerComponents = line.split(separator: ":", maxSplits: 1).map(String.init) + if headerComponents.count == 2 { + headers[headerComponents[0].trimmingCharacters(in: .whitespaces)] = + headerComponents[1].trimmingCharacters(in: .whitespaces) + } + } + self.headers = headers + + // Parse body if present + if components.count > 1 { + self.body = components[1].data(using: .utf8) + } else { + self.body = nil + } + } +} + +struct HTTPResponse { + enum StatusCode: Int { + case ok = 200 + case accepted = 202 + case badRequest = 400 + case notFound = 404 + case internalServerError = 500 + + var description: String { + switch self { + case .ok: return "OK" + case .accepted: return "Accepted" + case .badRequest: return "Bad Request" + case .notFound: return "Not Found" + case .internalServerError: return "Internal Server Error" + } + } + } + + let statusCode: StatusCode + let headers: [String: String] + let body: Data? + + init(statusCode: StatusCode, headers: [String: String] = [:], body: Data? = nil) { + self.statusCode = statusCode + self.headers = headers + self.body = body + } + + init(statusCode: StatusCode, body: String) { + self.statusCode = statusCode + self.headers = ["Content-Type": "text/plain"] + self.body = body.data(using: .utf8) + } + + func serialize() -> Data { + var response = "HTTP/1.1 \(statusCode.rawValue) \(statusCode.description)\r\n" + + var headers = self.headers + if let body = body { + headers["Content-Length"] = "\(body.count)" + } + + for (key, value) in headers { + response += "\(key): \(value)\r\n" + } + + response += "\r\n" + + var responseData = response.data(using: .utf8) ?? Data() + if let body = body { + responseData.append(body) + } + + return responseData + } +} + +final class HTTPServer { + let port: UInt16 + + init(port: UInt16) { + self.port = port + } +} \ No newline at end of file diff --git a/src/Server/Handlers.swift b/src/Server/Handlers.swift new file mode 100644 index 00000000..580b6936 --- /dev/null +++ b/src/Server/Handlers.swift @@ -0,0 +1,316 @@ +import Foundation +import Virtualization +import ArgumentParser + +@MainActor +extension Server { + // MARK: - VM Management Handlers + + func handleListVMs() async throws -> HTTPResponse { + do { + let vmController = LumeController() + let vms = try vmController.list() + return try .json(vms) + } catch { + return .badRequest(message: error.localizedDescription) + } + } + + func handleGetVM(name: String) async throws -> HTTPResponse { + do { + let vmController = LumeController() + let vm = try vmController.get(name: name) + return try .json(vm.details) + } catch { + return .badRequest(message: error.localizedDescription) + } + } + + func handleCreateVM(_ body: Data?) async throws -> HTTPResponse { + guard let body = body, + let request = try? JSONDecoder().decode(CreateVMRequest.self, from: body) else { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: "Invalid request body")) + ) + } + + do { + let sizes = try request.parse() + let vmController = LumeController() + try await vmController.create( + name: request.name, + os: request.os, + diskSize: sizes.diskSize, + cpuCount: request.cpu, + memorySize: sizes.memory, + display: request.display, + ipsw: request.ipsw + ) + + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(["message": "VM created successfully", "name": request.name]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + func handleDeleteVM(name: String) async throws -> HTTPResponse { + do { + let vmController = LumeController() + try await vmController.delete(name: name) + return HTTPResponse(statusCode: .ok, headers: ["Content-Type": "application/json"], body: Data()) + } catch { + return HTTPResponse(statusCode: .badRequest, headers: ["Content-Type": "application/json"], body: try JSONEncoder().encode(APIError(message: error.localizedDescription))) + } + } + + func handleCloneVM(_ body: Data?) async throws -> HTTPResponse { + guard let body = body, + let request = try? JSONDecoder().decode(CloneRequest.self, from: body) else { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: "Invalid request body")) + ) + } + + do { + let vmController = LumeController() + try vmController.clone(name: request.name, newName: request.newName) + + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode([ + "message": "VM cloned successfully", + "source": request.name, + "destination": request.newName + ]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + // MARK: - VM Operation Handlers + + func handleSetVM(name: String, body: Data?) async throws -> HTTPResponse { + guard let body = body, + let request = try? JSONDecoder().decode(SetVMRequest.self, from: body) else { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: "Invalid request body")) + ) + } + + do { + let vmController = LumeController() + let sizes = try request.parse() + try vmController.updateSettings( + name: name, + cpu: request.cpu, + memory: sizes.memory, + diskSize: sizes.diskSize + ) + + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(["message": "VM settings updated successfully"]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + func handleStopVM(name: String) async throws -> HTTPResponse { + do { + let vmController = LumeController() + try await vmController.stopVM(name: name) + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(["message": "VM stopped successfully"]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + func handleRunVM(name: String, body: Data?) async throws -> HTTPResponse { + let request = body.flatMap { try? JSONDecoder().decode(RunVMRequest.self, from: $0) } ?? RunVMRequest(noDisplay: nil, sharedDirectories: nil) + + do { + let dirs = try request.parse() + + // Start VM in background + startVM( + name: name, + noDisplay: request.noDisplay ?? false, + sharedDirectories: dirs + ) + + // Return response immediately + return HTTPResponse( + statusCode: .accepted, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode([ + "message": "VM start initiated", + "name": name, + "status": "pending" + ]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + // MARK: - Image Management Handlers + + func handleIPSW() async throws -> HTTPResponse { + do { + let vmController = LumeController() + let url = try await vmController.getLatestIPSWURL() + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(["url": url.absoluteString]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + func handlePull(_ body: Data?) async throws -> HTTPResponse { + guard let body = body, + let request = try? JSONDecoder().decode(PullRequest.self, from: body) else { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: "Invalid request body")) + ) + } + + do { + let vmController = LumeController() + try await vmController.pullImage(image: request.image, name: request.name, registry: request.registry, organization: request.organization) + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(["message": "Image pulled successfully"]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + func handlePruneImages() async throws -> HTTPResponse { + do { + let vmController = LumeController() + try await vmController.pruneImages() + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(["message": "Successfully removed cached images"]) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + func handleGetImages(_ request: HTTPRequest) async throws -> HTTPResponse { + // Parse query parameters from URL path and query string + let pathAndQuery = request.path.split(separator: "?", maxSplits: 1) + let queryParams = pathAndQuery.count > 1 ? pathAndQuery[1] + .split(separator: "&") + .reduce(into: [String: String]()) { dict, param in + let parts = param.split(separator: "=", maxSplits: 1) + if parts.count == 2 { + dict[String(parts[0])] = String(parts[1]) + } + } : [:] + + let organization = queryParams["organization"] ?? "trycua" + + do { + let vmController = LumeController() + let images = try await vmController.getImages(organization: organization) + + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(images) + ) + } catch { + return HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: try JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } + } + + // MARK: - Private Helper Methods + + nonisolated private func startVM( + name: String, + noDisplay: Bool, + sharedDirectories: [SharedDirectory] = [] + ) { + Task.detached { @MainActor @Sendable in + Logger.info("Starting VM in background", metadata: ["name": name]) + do { + let vmController = LumeController() + try await vmController.runVM( + name: name, + noDisplay: noDisplay, + sharedDirectories: sharedDirectories + ) + Logger.info("VM started successfully in background", metadata: ["name": name]) + } catch { + Logger.error("Failed to start VM in background", metadata: [ + "name": name, + "error": error.localizedDescription + ]) + } + } + } +} \ No newline at end of file diff --git a/src/Server/Requests.swift b/src/Server/Requests.swift new file mode 100644 index 00000000..c8e09b5a --- /dev/null +++ b/src/Server/Requests.swift @@ -0,0 +1,86 @@ +import Foundation +import ArgumentParser +import Virtualization + +struct RunVMRequest: Codable { + let noDisplay: Bool? + let sharedDirectories: [SharedDirectoryRequest]? + + struct SharedDirectoryRequest: Codable { + let hostPath: String + let readOnly: Bool? + } + + func parse() throws -> [SharedDirectory] { + guard let sharedDirectories = sharedDirectories else { return [] } + + return try sharedDirectories.map { dir -> SharedDirectory in + // Validate that the host path exists and is a directory + var isDirectory: ObjCBool = false + guard FileManager.default.fileExists(atPath: dir.hostPath, isDirectory: &isDirectory), + isDirectory.boolValue else { + throw ValidationError("Host path does not exist or is not a directory: \(dir.hostPath)") + } + + return SharedDirectory( + hostPath: dir.hostPath, + tag: VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag, + readOnly: dir.readOnly ?? false + ) + } + } +} + +struct PullRequest: Codable { + let image: String + let name: String? + var registry: String + var organization: String + + enum CodingKeys: String, CodingKey { + case image, name, registry, organization + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + image = try container.decode(String.self, forKey: .image) + name = try container.decodeIfPresent(String.self, forKey: .name) + registry = try container.decodeIfPresent(String.self, forKey: .registry) ?? "ghcr.io" + organization = try container.decodeIfPresent(String.self, forKey: .organization) ?? "trycua" + } +} + +struct CreateVMRequest: Codable { + let name: String + let os: String + let cpu: Int + let memory: String + let diskSize: String + let display: String + let ipsw: String? + + func parse() throws -> (memory: UInt64, diskSize: UInt64) { + return ( + memory: try parseSize(memory), + diskSize: try parseSize(diskSize) + ) + } +} + +struct SetVMRequest: Codable { + let cpu: Int? + let memory: String? + let diskSize: String? + + func parse() throws -> (memory: UInt64?, diskSize: UInt64?) { + return ( + memory: try memory.map { try parseSize($0) }, + diskSize: try diskSize.map { try parseSize($0) } + ) + } +} + +struct CloneRequest: Codable { + let name: String + let newName: String +} \ No newline at end of file diff --git a/src/Server/Responses.swift b/src/Server/Responses.swift new file mode 100644 index 00000000..e6d3bfe9 --- /dev/null +++ b/src/Server/Responses.swift @@ -0,0 +1,25 @@ +import Foundation + +struct APIError: Codable { + let message: String +} + +extension HTTPResponse { + static func json(_ value: T) throws -> HTTPResponse { + let data = try JSONEncoder().encode(value) + return HTTPResponse( + statusCode: .ok, + headers: ["Content-Type": "application/json"], + body: data + ) + } + + static func badRequest(message: String) -> HTTPResponse { + let error = APIError(message: message) + return try! HTTPResponse( + statusCode: .badRequest, + headers: ["Content-Type": "application/json"], + body: JSONEncoder().encode(error) + ) + } +} \ No newline at end of file diff --git a/src/Server/Server.swift b/src/Server/Server.swift new file mode 100644 index 00000000..cb8872f0 --- /dev/null +++ b/src/Server/Server.swift @@ -0,0 +1,281 @@ +import Foundation +import Network + +// MARK: - Server Class +@MainActor +final class Server { + + // MARK: - Route Type + private struct Route { + let method: String + let path: String + let handler: (HTTPRequest) async throws -> HTTPResponse + + func matches(_ request: HTTPRequest) -> Bool { + if method != request.method { return false } + + // Handle path parameters + let routeParts = path.split(separator: "/") + let requestParts = request.path.split(separator: "/") + + if routeParts.count != requestParts.count { return false } + + for (routePart, requestPart) in zip(routeParts, requestParts) { + if routePart.hasPrefix(":") { continue } // Path parameter + if routePart != requestPart { return false } + } + + return true + } + + func extractParams(_ request: HTTPRequest) -> [String: String] { + var params: [String: String] = [:] + let routeParts = path.split(separator: "/") + let requestParts = request.path.split(separator: "/") + + for (routePart, requestPart) in zip(routeParts, requestParts) { + if routePart.hasPrefix(":") { + let paramName = String(routePart.dropFirst()) + params[paramName] = String(requestPart) + } + } + + return params + } + } + + // MARK: - Properties + private let port: NWEndpoint.Port + private let controller: LumeController + private var isRunning = false + private var listener: NWListener? + private var routes: [Route] + + // MARK: - Initialization + init(port: UInt16 = 3000) { + self.port = NWEndpoint.Port(rawValue: port)! + self.controller = LumeController() + self.routes = [] + + // Define API routes after self is fully initialized + self.setupRoutes() + } + + // MARK: - Route Setup + private func setupRoutes() { + routes = [ + Route(method: "GET", path: "/lume/vms", handler: { [weak self] _ in + guard let self else { throw HTTPError.internalError } + return try await self.handleListVMs() + }), + Route(method: "GET", path: "/lume/vms/:name", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + let params = Route(method: "GET", path: "/lume/vms/:name", handler: { _ in + HTTPResponse(statusCode: .ok, body: "") + }).extractParams(request) + guard let name = params["name"] else { + return HTTPResponse(statusCode: .badRequest, body: "Missing VM name") + } + return try await self.handleGetVM(name: name) + }), + Route(method: "DELETE", path: "/lume/vms/:name", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + let params = Route(method: "DELETE", path: "/lume/vms/:name", handler: { _ in + HTTPResponse(statusCode: .ok, body: "") + }).extractParams(request) + guard let name = params["name"] else { + return HTTPResponse(statusCode: .badRequest, body: "Missing VM name") + } + return try await self.handleDeleteVM(name: name) + }), + Route(method: "POST", path: "/lume/vms", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + return try await self.handleCreateVM(request.body) + }), + Route(method: "POST", path: "/lume/vms/clone", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + return try await self.handleCloneVM(request.body) + }), + Route(method: "PATCH", path: "/lume/vms/:name", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + let params = Route(method: "PATCH", path: "/lume/vms/:name", handler: { _ in + HTTPResponse(statusCode: .ok, body: "") + }).extractParams(request) + guard let name = params["name"] else { + return HTTPResponse(statusCode: .badRequest, body: "Missing VM name") + } + return try await self.handleSetVM(name: name, body: request.body) + }), + Route(method: "POST", path: "/lume/vms/:name/run", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + let params = Route(method: "POST", path: "/lume/vms/:name/run", handler: { _ in + HTTPResponse(statusCode: .ok, body: "") + }).extractParams(request) + guard let name = params["name"] else { + return HTTPResponse(statusCode: .badRequest, body: "Missing VM name") + } + return try await self.handleRunVM(name: name, body: request.body) + }), + Route(method: "POST", path: "/lume/vms/:name/stop", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + let params = Route(method: "POST", path: "/lume/vms/:name/stop", handler: { _ in + HTTPResponse(statusCode: .ok, body: "") + }).extractParams(request) + guard let name = params["name"] else { + return HTTPResponse(statusCode: .badRequest, body: "Missing VM name") + } + return try await self.handleStopVM(name: name) + }), + Route(method: "GET", path: "/lume/ipsw", handler: { [weak self] _ in + guard let self else { throw HTTPError.internalError } + return try await self.handleIPSW() + }), + Route(method: "POST", path: "/lume/pull", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + return try await self.handlePull(request.body) + }), + Route(method: "POST", path: "/lume/prune", handler: { [weak self] _ in + guard let self else { throw HTTPError.internalError } + return try await self.handlePruneImages() + }), + Route(method: "GET", path: "/lume/images", handler: { [weak self] request in + guard let self else { throw HTTPError.internalError } + return try await self.handleGetImages(request) + }) + ] + } + + // MARK: - Server Lifecycle + func start() async throws { + let parameters = NWParameters.tcp + listener = try NWListener(using: parameters, on: port) + + listener?.newConnectionHandler = { [weak self] connection in + Task { @MainActor [weak self] in + guard let self else { return } + self.handleConnection(connection) + } + } + + listener?.start(queue: .main) + isRunning = true + + Logger.info("Server started", metadata: ["port": "\(port.rawValue)"]) + + // Keep the server running + while isRunning { + try await Task.sleep(nanoseconds: 1_000_000_000) + } + } + + func stop() { + isRunning = false + listener?.cancel() + } + + // MARK: - Connection Handling + private func handleConnection(_ connection: NWConnection) { + connection.stateUpdateHandler = { [weak self] state in + switch state { + case .ready: + Task { @MainActor [weak self] in + guard let self else { return } + self.receiveData(connection) + } + case .failed(let error): + Logger.error("Connection failed", metadata: ["error": error.localizedDescription]) + connection.cancel() + case .cancelled: + // Connection is already cancelled, no need to cancel again + break + default: + break + } + } + connection.start(queue: .main) + } + + private func receiveData(_ connection: NWConnection) { + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] content, _, isComplete, error in + if let error = error { + Logger.error("Receive error", metadata: ["error": error.localizedDescription]) + connection.cancel() + return + } + + guard let data = content, !data.isEmpty else { + if isComplete { + connection.cancel() + } + return + } + + Task { @MainActor [weak self] in + guard let self else { return } + do { + let response = try await self.handleRequest(data) + self.send(response, on: connection) + } catch { + let errorResponse = self.errorResponse(error) + self.send(errorResponse, on: connection) + } + } + } + } + + private func send(_ response: HTTPResponse, on connection: NWConnection) { + let data = response.serialize() + Logger.info("Serialized response", metadata: ["data": String(data: data, encoding: .utf8) ?? ""]) + connection.send(content: data, completion: .contentProcessed { [weak connection] error in + if let error = error { + Logger.error("Failed to send response", metadata: ["error": error.localizedDescription]) + } else { + Logger.info("Response sent successfully") + } + if connection?.state != .cancelled { + connection?.cancel() + } + }) + } + + // MARK: - Request Handling + private func handleRequest(_ data: Data) async throws -> HTTPResponse { + Logger.info("Received request data", metadata: ["data": String(data: data, encoding: .utf8) ?? ""]) + + guard let request = HTTPRequest(data: data) else { + Logger.error("Failed to parse request") + return HTTPResponse(statusCode: .badRequest, body: "Invalid request") + } + + Logger.info("Parsed request", metadata: [ + "method": request.method, + "path": request.path, + "headers": "\(request.headers)", + "body": String(data: request.body ?? Data(), encoding: .utf8) ?? "" + ]) + + // Find matching route + guard let route = routes.first(where: { $0.matches(request) }) else { + return HTTPResponse(statusCode: .notFound, body: "Not found") + } + + // Handle the request + let response = try await route.handler(request) + + Logger.info("Sending response", metadata: [ + "statusCode": "\(response.statusCode.rawValue)", + "headers": "\(response.headers)", + "body": String(data: response.body ?? Data(), encoding: .utf8) ?? "" + ]) + + return response + } + + private func errorResponse(_ error: Error) -> HTTPResponse { + HTTPResponse( + statusCode: .internalServerError, + headers: ["Content-Type": "application/json"], + body: try! JSONEncoder().encode(APIError(message: error.localizedDescription)) + ) + } +} \ No newline at end of file diff --git a/src/Utils/CommandRegistry.swift b/src/Utils/CommandRegistry.swift new file mode 100644 index 00000000..f7ec317b --- /dev/null +++ b/src/Utils/CommandRegistry.swift @@ -0,0 +1,21 @@ +import ArgumentParser + +enum CommandRegistry { + static var allCommands: [ParsableCommand.Type] { + [ + Create.self, + Pull.self, + Images.self, + Clone.self, + Get.self, + Set.self, + List.self, + Run.self, + Stop.self, + IPSW.self, + Serve.self, + Delete.self, + Prune.self + ] + } +} diff --git a/src/Utils/CommandUtils.swift b/src/Utils/CommandUtils.swift new file mode 100644 index 00000000..f400f383 --- /dev/null +++ b/src/Utils/CommandUtils.swift @@ -0,0 +1,6 @@ +import ArgumentParser +import Foundation + +func completeVMName(_ arguments: [String]) -> [String] { + (try? Home().getAllVMDirectories().map(\.name)) ?? [] +} \ No newline at end of file diff --git a/src/Utils/Logger.swift b/src/Utils/Logger.swift new file mode 100644 index 00000000..208e6fec --- /dev/null +++ b/src/Utils/Logger.swift @@ -0,0 +1,29 @@ +import Foundation + +struct Logger { + typealias Metadata = [String: String] + + enum Level: String { + case info + case error + case debug + } + + static func info(_ message: String, metadata: Metadata = [:]) { + log(.info, message, metadata) + } + + static func error(_ message: String, metadata: Metadata = [:]) { + log(.error, message, metadata) + } + + static func debug(_ message: String, metadata: Metadata = [:]) { + log(.debug, message, metadata) + } + + private static func log(_ level: Level, _ message: String, _ metadata: Metadata) { + let timestamp = ISO8601DateFormatter().string(from: Date()) + let metadataString = metadata.isEmpty ? "" : " " + metadata.map { "\($0.key)=\($0.value)" }.joined(separator: " ") + print("[\(timestamp)] \(level.rawValue.uppercased()): \(message)\(metadataString)") + } +} \ No newline at end of file diff --git a/src/Utils/NetworkUtils.swift b/src/Utils/NetworkUtils.swift new file mode 100644 index 00000000..14653f96 --- /dev/null +++ b/src/Utils/NetworkUtils.swift @@ -0,0 +1,24 @@ +import Foundation + +enum NetworkUtils { + /// Checks if an IP address is reachable by sending a ping + /// - Parameter ipAddress: The IP address to check + /// - Returns: true if the IP is reachable, false otherwise + static func isReachable(ipAddress: String) -> Bool { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/sbin/ping") + process.arguments = ["-c", "1", "-t", "1", ipAddress] + + let pipe = Pipe() + process.standardOutput = pipe + process.standardError = pipe + + do { + try process.run() + process.waitUntilExit() + return process.terminationStatus == 0 + } catch { + return false + } + } +} \ No newline at end of file diff --git a/src/Utils/Path.swift b/src/Utils/Path.swift new file mode 100644 index 00000000..37f67d39 --- /dev/null +++ b/src/Utils/Path.swift @@ -0,0 +1,46 @@ +import ArgumentParser +import Foundation + +struct Path: CustomStringConvertible, ExpressibleByArgument { + let url: URL + + init(_ path: String) { + url = URL(filePath: NSString(string: path).expandingTildeInPath).standardizedFileURL + } + + init(_ url: URL) { + self.url = url + } + + init(argument: String) { + self.init(argument) + } + + func file(_ path: String) -> Path { + return Path(url.appendingPathComponent(path, isDirectory: false)) + } + + func directory(_ path: String) -> Path { + return Path(url.appendingPathComponent(path, isDirectory: true)) + } + + func exists() -> Bool { + return FileManager.default.fileExists(atPath: url.standardizedFileURL.path(percentEncoded: false)) + } + + func writable() -> Bool { + return FileManager.default.isWritableFile(atPath: url.standardizedFileURL.path(percentEncoded: false)) + } + + var name: String { + return url.lastPathComponent + } + + var path: String { + return url.standardizedFileURL.path(percentEncoded: false) + } + + var description: String { + return url.path() + } +} diff --git a/src/Utils/ProcessRunner.swift b/src/Utils/ProcessRunner.swift new file mode 100644 index 00000000..1ca9b8b1 --- /dev/null +++ b/src/Utils/ProcessRunner.swift @@ -0,0 +1,15 @@ +import Foundation + +/// Protocol for process execution +protocol ProcessRunner { + func run(executable: String, arguments: [String]) throws +} + +class DefaultProcessRunner: ProcessRunner { + func run(executable: String, arguments: [String]) throws { + let process = Process() + process.executableURL = URL(fileURLWithPath: executable) + process.arguments = arguments + try process.run() + } +} \ No newline at end of file diff --git a/src/Utils/ProgressLogger.swift b/src/Utils/ProgressLogger.swift new file mode 100644 index 00000000..29c22a33 --- /dev/null +++ b/src/Utils/ProgressLogger.swift @@ -0,0 +1,18 @@ +import Foundation + +struct ProgressLogger { + private var lastLoggedProgress: Double = 0.0 + private let threshold: Double + + init(threshold: Double = 0.05) { + self.threshold = threshold + } + + mutating func logProgress(current: Double, context: String) { + if current - lastLoggedProgress >= threshold { + lastLoggedProgress = current + let percentage = Int(current * 100) + Logger.info("\(context) Progress: \(percentage)%") + } + } +} \ No newline at end of file diff --git a/src/Utils/String.swift b/src/Utils/String.swift new file mode 100644 index 00000000..0055cfd2 --- /dev/null +++ b/src/Utils/String.swift @@ -0,0 +1,7 @@ +import Foundation + +extension String { + func padding(_ toLength: Int) -> String { + return self.padding(toLength: toLength, withPad: " ", startingAt: 0) + } +} diff --git a/src/Utils/Utils.swift b/src/Utils/Utils.swift new file mode 100644 index 00000000..a04e3b0c --- /dev/null +++ b/src/Utils/Utils.swift @@ -0,0 +1,57 @@ +import Foundation +import ArgumentParser + +extension Collection { + subscript (safe index: Index) -> Element? { + indices.contains(index) ? self[index] : nil + } +} + +func resolveBinaryPath(_ name: String) -> URL? { + guard let path = ProcessInfo.processInfo.environment["PATH"] else { + return nil + } + + for pathComponent in path.split(separator: ":") { + let url = URL(fileURLWithPath: String(pathComponent)) + .appendingPathComponent(name, isDirectory: false) + + if FileManager.default.fileExists(atPath: url.path) { + return url + } + } + + return nil +} + +// Helper function to parse size strings +func parseSize(_ input: String) throws -> UInt64 { + let lowercased = input.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + let multiplier: UInt64 + let valueString: String + + if lowercased.hasSuffix("tb") { + multiplier = 1024 * 1024 * 1024 * 1024 + valueString = String(lowercased.dropLast(2)) + } else if lowercased.hasSuffix("gb") { + multiplier = 1024 * 1024 * 1024 + valueString = String(lowercased.dropLast(2)) + } else if lowercased.hasSuffix("mb") { + multiplier = 1024 * 1024 + valueString = String(lowercased.dropLast(2)) + } else if lowercased.hasSuffix("kb") { + multiplier = 1024 + valueString = String(lowercased.dropLast(2)) + } else { + multiplier = 1024 * 1024 // Default to MB + valueString = lowercased + } + + guard let value = UInt64(valueString.trimmingCharacters(in: .whitespacesAndNewlines)) else { + throw ValidationError("Malformed size input: \(input)") // Throw ad-hoc error for invalid input + } + + let val = value * multiplier + + return val +} diff --git a/src/VM/DarwinVM.swift b/src/VM/DarwinVM.swift new file mode 100644 index 00000000..90a60fef --- /dev/null +++ b/src/VM/DarwinVM.swift @@ -0,0 +1,84 @@ +import Foundation + +/// macOS-specific virtual machine implementation +@MainActor +final class DarwinVM: VM { + private let imageLoader: ImageLoader + + init( + vmDirContext: VMDirContext, + virtualizationServiceFactory: @escaping (VMVirtualizationServiceContext) throws -> VMVirtualizationService = { try DarwinVirtualizationService(configuration: $0) }, + vncServiceFactory: @escaping (VMDirectory) -> VNCService = { DefaultVNCService(vmDirectory: $0) }, + imageLoader: ImageLoader + ) { + self.imageLoader = imageLoader + super.init( + vmDirContext: vmDirContext, + virtualizationServiceFactory: virtualizationServiceFactory, + vncServiceFactory: vncServiceFactory + ) + } + + override func getOSType() -> String { + return "macOS" + } + + // MARK: - Installation and Configuration + + override func setup(ipswPath: String, cpuCount: Int, memorySize: UInt64, diskSize: UInt64, display: String) async throws { + let imagePath: Path + if ipswPath == "latest" { + Logger.info("Downloading latest supported Image...") + let downloadedPath = try await self.imageLoader.downloadLatestImage() + imagePath = Path(downloadedPath.path) + } else { + imagePath = Path(ipswPath) + } + + let requirements = try await imageLoader.loadImageRequirements(from: imagePath.url) + try setDiskSize(diskSize) + + let finalCpuCount = max(cpuCount, requirements.minimumSupportedCPUCount) + try setCpuCount(finalCpuCount) + if finalCpuCount != cpuCount { + Logger.info("CPU count overridden due to minimum image requirements", metadata: ["original": "\(cpuCount)", "final": "\(finalCpuCount)"]) + } + + let finalMemorySize = max(memorySize, requirements.minimumSupportedMemorySize) + try setMemorySize(finalMemorySize) + if finalMemorySize != memorySize { + Logger.info("Memory size overridden due to minimum image requirements", metadata: ["original": "\(memorySize)", "final": "\(finalMemorySize)"]) + } + + try updateVMConfig( + vmConfig: try VMConfig( + os: getOSType(), + cpuCount: finalCpuCount, + memorySize: finalMemorySize, + diskSize: diskSize, + macAddress: DarwinVirtualizationService.generateMacAddress(), + display: display, + hardwareModel: requirements.hardwareModel, + machineIdentifier: DarwinVirtualizationService.generateMachineIdentifier() + ) + ) + + let service: any VMVirtualizationService = try virtualizationServiceFactory( + try createVMVirtualizationServiceContext( + cpuCount: finalCpuCount, + memorySize: finalMemorySize, + display: display + ) + ) + guard let darwinService = service as? DarwinVirtualizationService else { + throw VMError.internalError("Installation requires DarwinVirtualizationService") + } + + // Create auxiliary storage with hardware model + try darwinService.createAuxiliaryStorage(at: vmDirContext.nvramPath, hardwareModel: requirements.hardwareModel) + + try await darwinService.installMacOS(imagePath: imagePath) { progress in + Logger.info("Installing macOS", metadata: ["progress": "\(Int(progress * 100))%"]) + } + } +} diff --git a/src/VM/LinuxVM.swift b/src/VM/LinuxVM.swift new file mode 100644 index 00000000..bb6d9214 --- /dev/null +++ b/src/VM/LinuxVM.swift @@ -0,0 +1,55 @@ +import Foundation + +/// Linux-specific virtual machine implementation +@MainActor +final class LinuxVM: VM { + override init( + vmDirContext: VMDirContext, + virtualizationServiceFactory: @escaping (VMVirtualizationServiceContext) throws -> VMVirtualizationService = { try LinuxVirtualizationService(configuration: $0) }, + vncServiceFactory: @escaping (VMDirectory) -> VNCService = { DefaultVNCService(vmDirectory: $0) } + ) { + super.init( + vmDirContext: vmDirContext, + virtualizationServiceFactory: virtualizationServiceFactory, + vncServiceFactory: vncServiceFactory + ) + } + + override func getOSType() -> String { + return "linux" + } + + override func setup( + ipswPath: String, + cpuCount: Int, + memorySize: UInt64, + diskSize: UInt64, + display: String + ) async throws { + + try setDiskSize(diskSize) + + let service = try virtualizationServiceFactory( + try createVMVirtualizationServiceContext( + cpuCount: cpuCount, + memorySize: memorySize, + display: display + ) + ) + guard let linuxService = service as? LinuxVirtualizationService else { + throw VMError.internalError("Installation requires LinuxVirtualizationService") + } + + try updateVMConfig(vmConfig: try VMConfig( + os: getOSType(), + cpuCount: cpuCount, + memorySize: memorySize, + diskSize: diskSize, + macAddress: linuxService.generateMacAddress(), + display: display + )) + + // Create NVRAM store for EFI + try linuxService.createNVRAM(at: vmDirContext.nvramPath) + } +} \ No newline at end of file diff --git a/src/VM/VM.swift b/src/VM/VM.swift new file mode 100644 index 00000000..75faedfd --- /dev/null +++ b/src/VM/VM.swift @@ -0,0 +1,390 @@ +import Foundation + +// MARK: - Support Types + +/// Base context for virtual machine directory and configuration +struct VMDirContext { + let dir: VMDirectory + var config: VMConfig + let home: Home + + func saveConfig() throws { + try dir.saveConfig(config) + } + + var name: String { dir.name } + var initialized: Bool { dir.initialized() } + var diskPath: Path { dir.diskPath } + var nvramPath: Path { dir.nvramPath } + + func setDisk(_ size: UInt64) throws { + try dir.setDisk(size) + } + + func finalize(to name: String) throws { + let vmDir = home.getVMDirectory(name) + try FileManager.default.moveItem(at: dir.dir.url, to: vmDir.dir.url) + } +} + +// MARK: - Base VM Class + +/// Base class for virtual machine implementations +@MainActor +class VM { + // MARK: - Properties + + var vmDirContext: VMDirContext + + @MainActor + private var virtualizationService: VMVirtualizationService? + private let vncService: VNCService + internal let virtualizationServiceFactory: (VMVirtualizationServiceContext) throws -> VMVirtualizationService + private let vncServiceFactory: (VMDirectory) -> VNCService + + // MARK: - Initialization + + init( + vmDirContext: VMDirContext, + virtualizationServiceFactory: @escaping (VMVirtualizationServiceContext) throws -> VMVirtualizationService = { try DarwinVirtualizationService(configuration: $0) }, + vncServiceFactory: @escaping (VMDirectory) -> VNCService = { DefaultVNCService(vmDirectory: $0) } + ) { + self.vmDirContext = vmDirContext + self.virtualizationServiceFactory = virtualizationServiceFactory + self.vncServiceFactory = vncServiceFactory + + // Initialize VNC service + self.vncService = vncServiceFactory(vmDirContext.dir) + } + + // MARK: - VM State Management + + private var isRunning: Bool { + // First check if we have an IP address + guard let ipAddress = DHCPLeaseParser.getIPAddress(forMAC: vmDirContext.config.macAddress!) else { + return false + } + + // Then check if it's reachable + return NetworkUtils.isReachable(ipAddress: ipAddress) + } + + var details: VMDetails { + let isRunning: Bool = self.isRunning + let vncUrl = isRunning ? getVNCUrl() : nil + + return VMDetails( + name: vmDirContext.name, + os: getOSType(), + cpuCount: vmDirContext.config.cpuCount ?? 0, + memorySize: vmDirContext.config.memorySize ?? 0, + diskSize: try! getDiskSize(), + status: isRunning ? "running" : "stopped", + vncUrl: vncUrl, + ipAddress: isRunning ? DHCPLeaseParser.getIPAddress(forMAC: vmDirContext.config.macAddress!) : nil + ) + } + + // MARK: - VM Lifecycle Management + + func run(noDisplay: Bool, sharedDirectories: [SharedDirectory], mount: Path?) async throws { + guard vmDirContext.initialized else { + throw VMError.notInitialized(vmDirContext.name) + } + + guard let cpuCount = vmDirContext.config.cpuCount, + let memorySize = vmDirContext.config.memorySize else { + throw VMError.notInitialized(vmDirContext.name) + } + + // Try to acquire lock on config file + let fileHandle = try FileHandle(forWritingTo: vmDirContext.dir.configPath.url) + guard flock(fileHandle.fileDescriptor, LOCK_EX | LOCK_NB) == 0 else { + try? fileHandle.close() + throw VMError.alreadyRunning(vmDirContext.name) + } + + Logger.info("Running VM with configuration", metadata: [ + "cpuCount": "\(cpuCount)", + "memorySize": "\(memorySize)", + "diskSize": "\(vmDirContext.config.diskSize ?? 0)", + "sharedDirectories": sharedDirectories.map( + { $0.string } + ).joined(separator: ", ") + ]) + + // Create and configure the VM + do { + let config = try createVMVirtualizationServiceContext( + cpuCount: cpuCount, + memorySize: memorySize, + display: vmDirContext.config.display.string, + sharedDirectories: sharedDirectories, + mount: mount + ) + virtualizationService = try virtualizationServiceFactory(config) + + let vncInfo = try await setupVNC(noDisplay: noDisplay) + Logger.info("VNC info", metadata: ["vncInfo": vncInfo]) + + // Start the VM + guard let service = virtualizationService else { + throw VMError.internalError("Virtualization service not initialized") + } + try await service.start() + + while true { + try await Task.sleep(nanoseconds: UInt64(1e9)) + } + } catch { + virtualizationService = nil + vncService.stop() + // Release lock + flock(fileHandle.fileDescriptor, LOCK_UN) + try? fileHandle.close() + throw error + } + } + + @MainActor + func stop() async throws { + guard vmDirContext.initialized else { + throw VMError.notInitialized(vmDirContext.name) + } + + Logger.info("Attempting to stop VM", metadata: ["name": vmDirContext.name]) + + // If we have a virtualization service, try to stop it cleanly first + if let service = virtualizationService { + do { + try await service.stop() + virtualizationService = nil + vncService.stop() + Logger.info("VM stopped successfully via virtualization service", metadata: ["name": vmDirContext.name]) + return + } catch let error { + Logger.error("Failed to stop VM via virtualization service, falling back to process termination", metadata: [ + "name": vmDirContext.name, + "error": "\(error)" + ]) + // Fall through to process termination + } + } + + // Try to open config file to get file descriptor - note that this matches with the serve process - so this is only for the command line + let fileHandle = try? FileHandle(forReadingFrom: vmDirContext.dir.configPath.url) + guard let fileHandle = fileHandle else { + Logger.error("Failed to open config file - VM not running", metadata: ["name": vmDirContext.name]) + throw VMError.notRunning(vmDirContext.name) + } + + // Get the PID of the process holding the lock using lsof command + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/sbin/lsof") + task.arguments = ["-F", "p", vmDirContext.dir.configPath.path] + + let outputPipe = Pipe() + task.standardOutput = outputPipe + + try task.run() + task.waitUntilExit() + + let outputData = try outputPipe.fileHandleForReading.readToEnd() ?? Data() + guard let outputString = String(data: outputData, encoding: .utf8), + let pidString = outputString.split(separator: "\n").first?.dropFirst(), // Drop the 'p' prefix + let pid = pid_t(pidString) else { + try? fileHandle.close() + Logger.error("Failed to find VM process - VM not running", metadata: ["name": vmDirContext.name]) + throw VMError.notRunning(vmDirContext.name) + } + + // First try graceful shutdown with SIGINT + if kill(pid, SIGINT) == 0 { + Logger.info("Sent SIGINT to VM process", metadata: ["name": vmDirContext.name, "pid": "\(pid)"]) + } + + // Wait for process to stop with timeout + var attempts = 0 + while attempts < 10 { + try await Task.sleep(nanoseconds: 1_000_000_000) + + // Check if process still exists + if kill(pid, 0) != 0 { + // Process is gone, do final cleanup + virtualizationService = nil + vncService.stop() + try? fileHandle.close() + + Logger.info("VM stopped successfully via process termination", metadata: ["name": vmDirContext.name]) + return + } + attempts += 1 + } + + // If graceful shutdown failed, force kill the process + Logger.info("Graceful shutdown failed, forcing termination", metadata: ["name": vmDirContext.name]) + if kill(pid, SIGKILL) == 0 { + // Wait a moment for the process to be fully killed + try await Task.sleep(nanoseconds: 2_000_000_000) + + // Do final cleanup + virtualizationService = nil + vncService.stop() + try? fileHandle.close() + + Logger.info("VM forcefully stopped", metadata: ["name": vmDirContext.name]) + return + } + + // If we get here, something went very wrong + try? fileHandle.close() + Logger.error("Failed to stop VM", metadata: ["name": vmDirContext.name, "pid": "\(pid)"]) + throw VMError.internalError("Failed to stop VM process") + } + + // MARK: - Resource Management + + func updateVMConfig(vmConfig: VMConfig) throws { + vmDirContext.config = vmConfig + try vmDirContext.saveConfig() + } + + private func getDiskSize() throws -> DiskSize { + let resourceValues = try vmDirContext.diskPath.url.resourceValues(forKeys: [ + .totalFileAllocatedSizeKey, + .totalFileSizeKey + ]) + + guard let allocated = resourceValues.totalFileAllocatedSize, + let total = resourceValues.totalFileSize else { + throw VMConfigError.invalidDiskSize + } + + return DiskSize(allocated: UInt64(allocated), total: UInt64(total)) + } + + func resizeDisk(_ newSize: UInt64) throws { + let currentSize = try getDiskSize() + + guard newSize >= currentSize.total else { + throw VMError.resizeTooSmall(current: currentSize.total, requested: newSize) + } + + try setDiskSize(newSize) + } + + func setCpuCount(_ newCpuCount: Int) throws { + guard !isRunning else { + throw VMError.alreadyRunning(vmDirContext.name) + } + vmDirContext.config.setCpuCount(newCpuCount) + try vmDirContext.saveConfig() + } + + func setMemorySize(_ newMemorySize: UInt64) throws { + guard !isRunning else { + throw VMError.alreadyRunning(vmDirContext.name) + } + vmDirContext.config.setMemorySize(newMemorySize) + try vmDirContext.saveConfig() + } + + func setDiskSize(_ newDiskSize: UInt64) throws { + try vmDirContext.setDisk(newDiskSize) + vmDirContext.config.setDiskSize(newDiskSize) + try vmDirContext.saveConfig() + } + + func setHardwareModel(_ newHardwareModel: Data) throws { + guard !isRunning else { + throw VMError.alreadyRunning(vmDirContext.name) + } + vmDirContext.config.setHardwareModel(newHardwareModel) + try vmDirContext.saveConfig() + } + + func setMachineIdentifier(_ newMachineIdentifier: Data) throws { + guard !isRunning else { + throw VMError.alreadyRunning(vmDirContext.name) + } + vmDirContext.config.setMachineIdentifier(newMachineIdentifier) + try vmDirContext.saveConfig() + } + + func setMacAddress(_ newMacAddress: String) throws { + guard !isRunning else { + throw VMError.alreadyRunning(vmDirContext.name) + } + vmDirContext.config.setMacAddress(newMacAddress) + try vmDirContext.saveConfig() + } + + // MARK: - VNC Management + + func getVNCUrl() -> String? { + return vncService.url + } + + private func setupVNC(noDisplay: Bool) async throws -> String { + guard let service = virtualizationService else { + throw VMError.internalError("Virtualization service not initialized") + } + + try await vncService.start(port: 0, virtualMachine: service.getVirtualMachine()) + + guard let url = vncService.url else { + throw VMError.vncNotConfigured + } + + if !noDisplay { + Logger.info("Starting VNC session") + try await vncService.openClient(url: url) + } + + return url + } + + // MARK: - Platform-specific Methods + + func getOSType() -> String { + fatalError("Must be implemented by subclass") + } + + func createVMVirtualizationServiceContext( + cpuCount: Int, + memorySize: UInt64, + display: String, + sharedDirectories: [SharedDirectory] = [], + mount: Path? = nil + ) throws -> VMVirtualizationServiceContext { + return VMVirtualizationServiceContext( + cpuCount: cpuCount, + memorySize: memorySize, + display: display, + sharedDirectories: sharedDirectories, + mount: mount, + hardwareModel: vmDirContext.config.hardwareModel, + machineIdentifier: vmDirContext.config.machineIdentifier, + macAddress: vmDirContext.config.macAddress!, + diskPath: vmDirContext.diskPath, + nvramPath: vmDirContext.nvramPath + ) + } + + func setup( + ipswPath: String, + cpuCount: Int, + memorySize: UInt64, + diskSize: UInt64, + display: String + ) async throws { + fatalError("Must be implemented by subclass") + } + + // MARK: - Finalization + + /// Post-installation step to move the VM directory to the home directory + func finalize(to name: String, home: Home) throws { + try vmDirContext.finalize(to: name) + } +} \ No newline at end of file diff --git a/src/VM/VMDetails.swift b/src/VM/VMDetails.swift new file mode 100644 index 00000000..31b17d97 --- /dev/null +++ b/src/VM/VMDetails.swift @@ -0,0 +1,41 @@ +import Foundation +import Network + +struct DiskSize: Codable { + let allocated: UInt64 + let total: UInt64 +} + +extension DiskSize { + var formattedAllocated: String { + formatBytes(allocated) + } + + var formattedTotal: String { + formatBytes(total) + } + + private func formatBytes(_ bytes: UInt64) -> String { + let units = ["B", "KB", "MB", "GB", "TB"] + var size = Double(bytes) + var unitIndex = 0 + + while size >= 1024 && unitIndex < units.count - 1 { + size /= 1024 + unitIndex += 1 + } + + return String(format: "%.1f%@", size, units[unitIndex]) + } +} + +struct VMDetails: Codable { + let name: String + let os: String + let cpuCount: Int + let memorySize: UInt64 + let diskSize: DiskSize + let status: String + let vncUrl: String? + let ipAddress: String? +} \ No newline at end of file diff --git a/src/VM/VMDetailsPrinter.swift b/src/VM/VMDetailsPrinter.swift new file mode 100644 index 00000000..7ec711ac --- /dev/null +++ b/src/VM/VMDetailsPrinter.swift @@ -0,0 +1,61 @@ +import Foundation + +/// Prints VM status information in a formatted table +enum VMDetailsPrinter { + /// Represents a column in the VM status table + private struct Column: Sendable { + let header: String + let width: Int + let getValue: @Sendable (VMDetails) -> String + } + + /// Configuration for all columns in the status table + private static let columns: [Column] = [ + Column(header: "name", width: 34, getValue: { $0.name }), + Column(header: "os", width: 8, getValue: { $0.os }), + Column(header: "cpu", width: 8, getValue: { String($0.cpuCount) }), + Column(header: "memory", width: 8, getValue: { + String(format: "%.2fG", Float($0.memorySize) / (1024 * 1024 * 1024)) + }), + Column(header: "disk", width: 16, getValue: { + "\($0.diskSize.formattedAllocated)/\($0.diskSize.formattedTotal)" + }), + Column(header: "status", width: 16, getValue: { + $0.status + }), + Column(header: "ip", width: 16, getValue: { + $0.ipAddress ?? "-" + }), + Column(header: "vnc", width: 50, getValue: { + $0.vncUrl ?? "-" + }) + ] + + /// Prints the status of all VMs in a formatted table + /// - Parameter vms: Array of VM status objects to display + static func printStatus(_ vms: [VMDetails]) { + printHeader() + vms.forEach(printVM) + } + + private static func printHeader() { + let paddedHeaders = columns.map { $0.header.paddedToWidth($0.width) } + print(paddedHeaders.joined()) + } + + private static func printVM(_ vm: VMDetails) { + let paddedColumns = columns.map { column in + column.getValue(vm).paddedToWidth(column.width) + } + print(paddedColumns.joined()) + } +} + +private extension String { + /// Pads the string to the specified width with spaces + /// - Parameter width: Target width for padding + /// - Returns: Padded string + func paddedToWidth(_ width: Int) -> String { + padding(toLength: width, withPad: " ", startingAt: 0) + } +} \ No newline at end of file diff --git a/src/VM/VMDisplayResolution.swift b/src/VM/VMDisplayResolution.swift new file mode 100644 index 00000000..e0103eef --- /dev/null +++ b/src/VM/VMDisplayResolution.swift @@ -0,0 +1,28 @@ +import Foundation +import ArgumentParser + +struct VMDisplayResolution: Codable, ExpressibleByArgument { + let width: Int + let height: Int + + init?(string: String) { + let components = string.components(separatedBy: "x") + guard components.count == 2, + let width = Int(components[0]), + let height = Int(components[1]), + width > 0, height > 0 else { + return nil + } + self.width = width + self.height = height + } + + var string: String { + "\(width)x\(height)" + } + + init?(argument: String) { + guard let resolution = VMDisplayResolution(string: argument) else { return nil } + self = resolution + } + } \ No newline at end of file diff --git a/src/VM/VMFactory.swift b/src/VM/VMFactory.swift new file mode 100644 index 00000000..7173e72f --- /dev/null +++ b/src/VM/VMFactory.swift @@ -0,0 +1,37 @@ +import Foundation +import Virtualization + +enum VMType: String { + case darwin = "macOS" + case linux = "linux" +} + +protocol VMFactory { + @MainActor + func createVM( + vmDirContext: VMDirContext, + imageLoader: ImageLoader? + ) throws -> VM +} + +class DefaultVMFactory: VMFactory { + @MainActor + func createVM( + vmDirContext: VMDirContext, + imageLoader: ImageLoader? + ) throws -> VM { + let osType = vmDirContext.config.os.lowercased() + + switch osType { + case "macos", "darwin": + guard let imageLoader = imageLoader else { + throw VMError.internalError("ImageLoader required for macOS VM") + } + return DarwinVM(vmDirContext: vmDirContext, imageLoader: imageLoader) + case "linux": + return LinuxVM(vmDirContext: vmDirContext) + default: + throw VMError.unsupportedOS(osType) + } + } +} \ No newline at end of file diff --git a/src/VNC/PassphraseGenerator.swift b/src/VNC/PassphraseGenerator.swift new file mode 100644 index 00000000..9f54ad36 --- /dev/null +++ b/src/VNC/PassphraseGenerator.swift @@ -0,0 +1,19 @@ +import Foundation + +final class PassphraseGenerator { + private let words: [String] + + init(words: [String] = PassphraseGenerator.defaultWords) { + self.words = words + } + + func prefix(_ count: Int) -> [String] { + guard count > 0 else { return [] } + return (0.. DHCPLease? { + guard let hwAddress = dict["hw_address"], + let ipAddress = dict["ip_address"], + let lease = dict["lease"] else { + return nil + } + + // Parse MAC address from hw_address field (format can be "1,xx:xx:xx:xx:xx:xx" or "ff,...") + let hwParts = hwAddress.split(separator: ",") + guard hwParts.count >= 2 else { return nil } + + // Get the MAC part after the prefix and normalize it + let rawMacAddress = String(hwParts[1]).trimmingCharacters(in: .whitespaces) + + // Normalize the MAC address by ensuring each component is two digits + let normalizedMacAddress = rawMacAddress.split(separator: ":") + .map { component in + let hex = String(component) + return hex.count == 1 ? "0\(hex)" : hex + } + .joined(separator: ":") + + // Convert hex timestamp to Date + let timestampHex = lease.trimmingCharacters(in: CharacterSet(charactersIn: "0x")) + guard let timestamp = UInt64(timestampHex, radix: 16) else { return nil } + let expirationDate = Date(timeIntervalSince1970: TimeInterval(timestamp)) + + return DHCPLease( + macAddress: normalizedMacAddress, + ipAddress: ipAddress, + expirationDate: expirationDate + ) + } + + /// Checks if the lease is currently valid + var isValid: Bool { + expirationDate > Date() + } +} + +/// Parses DHCP lease files to retrieve IP addresses for VMs based on their MAC addresses +enum DHCPLeaseParser { + private static let leasePath = "/var/db/dhcpd_leases" + + /// Retrieves the IP address for a given MAC address from the DHCP lease file + /// - Parameter macAddress: The MAC address to look up + /// - Returns: The IP address if found, nil otherwise + static func getIPAddress(forMAC macAddress: String) -> String? { + guard let leaseContents = try? String(contentsOfFile: leasePath, encoding: .utf8) else { + return nil + } + + // Normalize the input MAC address to ensure consistent format + let normalizedMacAddress = macAddress.split(separator: ":").map { component in + let hex = String(component) + return hex.count == 1 ? "0\(hex)" : hex + }.joined(separator: ":") + + let leases = try? parseDHCPLeases(leaseContents) + return leases?.first { lease in + lease.macAddress == normalizedMacAddress + }?.ipAddress + } + + /// Parses the contents of a DHCP lease file into lease entries + /// - Parameter contents: The raw contents of the lease file + /// - Returns: Array of parsed lease entries + private static func parseDHCPLeases(_ contents: String) throws -> [DHCPLease] { + var leases: [DHCPLease] = [] + var currentLease: [String: String] = [:] + var inLeaseBlock = false + + let lines = contents.components(separatedBy: .newlines) + + for line in lines { + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + + if trimmedLine == "{" { + inLeaseBlock = true + currentLease = [:] + } else if trimmedLine == "}" { + if let lease = DHCPLease.from(currentLease) { + leases.append(lease) + } + inLeaseBlock = false + } else if inLeaseBlock { + let parts = trimmedLine.split(separator: "=", maxSplits: 1) + if parts.count == 2 { + let key = String(parts[0]).trimmingCharacters(in: .whitespaces) + let value = String(parts[1]).trimmingCharacters(in: .whitespaces) + currentLease[key] = value + } + } + } + + return leases + } +} \ No newline at end of file diff --git a/src/Virtualization/DarwinImageLoader.swift b/src/Virtualization/DarwinImageLoader.swift new file mode 100644 index 00000000..498cf14d --- /dev/null +++ b/src/Virtualization/DarwinImageLoader.swift @@ -0,0 +1,113 @@ +import Foundation +import Virtualization + +/// Handles loading and validation of macOS restore images (IPSW files). +/// Provides functionality to: +/// - Fetch the latest supported macOS restore image URL +/// - Load and validate image requirements for VM creation +/// - Extract hardware model and auxiliary storage configuration +protocol ImageLoader: Sendable { + typealias ImageRequirements = DarwinImageLoader.ImageRequirements + func fetchLatestSupportedURL() async throws -> URL + func loadImageRequirements(from url: URL) async throws -> ImageRequirements + func downloadLatestImage() async throws -> Path +} + +final class DarwinImageLoader: NSObject, ImageLoader, @unchecked Sendable, URLSessionDownloadDelegate { + struct ImageRequirements: Sendable { + let hardwareModel: Data + let minimumSupportedCPUCount: Int + let minimumSupportedMemorySize: UInt64 + } + + enum ImageError: Error { + case invalidImage + case unsupportedConfiguration + case downloadFailed + } + + private var lastLoggedProgress: Double = 0.0 + private var progressLogger = ProgressLogger() + private var completionHandler: ((URL?, Error?) -> Void)? + + func fetchLatestSupportedURL() async throws -> URL { + try await withCheckedThrowingContinuation { continuation in + VZMacOSRestoreImage.fetchLatestSupported { result in + switch result { + case .success(let image): + continuation.resume(returning: image.url) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + func loadImageRequirements(from url: URL) async throws -> ImageRequirements { + let image = try await VZMacOSRestoreImage.image(from: url) + guard let requirements = image.mostFeaturefulSupportedConfiguration else { + throw ImageError.unsupportedConfiguration + } + + return ImageRequirements( + hardwareModel: requirements.hardwareModel.dataRepresentation, + minimumSupportedCPUCount: requirements.minimumSupportedCPUCount, + minimumSupportedMemorySize: requirements.minimumSupportedMemorySize + ) + } + + func downloadLatestImage() async throws -> Path { + let url = try await fetchLatestSupportedURL() + let tempDir = FileManager.default.temporaryDirectory + let downloadPath = tempDir.appendingPathComponent("latest.ipsw") + + // Reset progress logger state + progressLogger = ProgressLogger(threshold: 0.01) + + // Create a continuation to wait for download completion + return try await withCheckedThrowingContinuation { continuation in + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + let task = session.downloadTask(with: url) + + // Use the delegate method to handle completion + self.completionHandler = { location, error in + if let error = error { + continuation.resume(throwing: error) + return + } + + do { + // Remove existing file if it exists + if FileManager.default.fileExists(atPath: downloadPath.path) { + try FileManager.default.removeItem(at: downloadPath) + } + + try FileManager.default.moveItem(at: location!, to: downloadPath) + Logger.info("Download completed and moved to: \(downloadPath.path)") + continuation.resume(returning: Path(downloadPath.path)) + } catch { + continuation.resume(throwing: error) + } + } + + task.resume() + } + } + + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite) + progressLogger.logProgress(current: progress, context: "Downloading IPSW") + } + + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + // Call the stored completion handler + completionHandler?(location, nil) + } + + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + // Call the stored completion handler with an error if it occurred + if let error = error { + completionHandler?(nil, error) + } + } +} \ No newline at end of file diff --git a/src/Virtualization/ImageLoaderFactory.swift b/src/Virtualization/ImageLoaderFactory.swift new file mode 100644 index 00000000..b8a6d59d --- /dev/null +++ b/src/Virtualization/ImageLoaderFactory.swift @@ -0,0 +1,17 @@ +import Foundation + +/// Protocol defining a factory for creating image loaders based on the image type +protocol ImageLoaderFactory { + /// Creates an appropriate ImageLoader based on the image path or type + func createImageLoader() -> ImageLoader +} + +/// Default implementation of ImageLoaderFactory that creates appropriate loaders based on image type +final class DefaultImageLoaderFactory: ImageLoaderFactory { + func createImageLoader() -> ImageLoader { + // For now, we only support Darwin images + // In the future, this can be extended to support other OS types + // by analyzing the image path or having explicit OS type parameter + return DarwinImageLoader() + } +} \ No newline at end of file diff --git a/src/Virtualization/VMVirtualizationService.swift b/src/Virtualization/VMVirtualizationService.swift new file mode 100644 index 00000000..c10e2fd1 --- /dev/null +++ b/src/Virtualization/VMVirtualizationService.swift @@ -0,0 +1,329 @@ +import Foundation +import Virtualization + +/// Framework-agnostic VM configuration +struct VMVirtualizationServiceContext { + let cpuCount: Int + let memorySize: UInt64 + let display: String + let sharedDirectories: [SharedDirectory]? + let mount: Path? + let hardwareModel: Data? + let machineIdentifier: Data? + let macAddress: String + let diskPath: Path + let nvramPath: Path +} + +/// Protocol defining the interface for virtualization operations +@MainActor +protocol VMVirtualizationService { + var state: VZVirtualMachine.State { get } + func start() async throws + func stop() async throws + func pause() async throws + func resume() async throws + func getVirtualMachine() -> Any +} + +/// Base implementation of VMVirtualizationService using VZVirtualMachine +@MainActor +class BaseVirtualizationService: VMVirtualizationService { + let virtualMachine: VZVirtualMachine + + var state: VZVirtualMachine.State { + virtualMachine.state + } + + init(virtualMachine: VZVirtualMachine) { + self.virtualMachine = virtualMachine + } + + func start() async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + Task { @MainActor in + virtualMachine.start { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + } + + func stop() async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + virtualMachine.stop { error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + } + } + + func pause() async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + virtualMachine.start { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + func resume() async throws { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + virtualMachine.start { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + func getVirtualMachine() -> Any { + return virtualMachine + } + + // Helper methods for creating common configurations + static func createStorageDeviceConfiguration(diskPath: Path, readOnly: Bool = false) throws -> VZStorageDeviceConfiguration { + return VZVirtioBlockDeviceConfiguration( + attachment: try VZDiskImageStorageDeviceAttachment( + url: diskPath.url, + readOnly: readOnly, + cachingMode: VZDiskImageCachingMode.automatic, + synchronizationMode: VZDiskImageSynchronizationMode.fsync + ) + ) + } + + static func createNetworkDeviceConfiguration(macAddress: String) throws -> VZNetworkDeviceConfiguration { + let network = VZVirtioNetworkDeviceConfiguration() + guard let vzMacAddress = VZMACAddress(string: macAddress) else { + throw VMConfigError.invalidMachineIdentifier + } + network.attachment = VZNATNetworkDeviceAttachment() + network.macAddress = vzMacAddress + return network + } + + static func createDirectorySharingDevices(sharedDirectories: [SharedDirectory]?) -> [VZDirectorySharingDeviceConfiguration] { + return sharedDirectories?.map { sharedDir in + let device = VZVirtioFileSystemDeviceConfiguration(tag: sharedDir.tag) + let url = URL(fileURLWithPath: sharedDir.hostPath) + device.share = VZSingleDirectoryShare(directory: VZSharedDirectory(url: url, readOnly: sharedDir.readOnly)) + return device + } ?? [] + } +} + +/// macOS-specific virtualization service +@MainActor +final class DarwinVirtualizationService: BaseVirtualizationService { + static func createConfiguration(_ config: VMVirtualizationServiceContext) throws -> VZVirtualMachineConfiguration { + let vzConfig = VZVirtualMachineConfiguration() + vzConfig.cpuCount = config.cpuCount + vzConfig.memorySize = config.memorySize + + // Platform configuration + guard let machineIdentifier = config.machineIdentifier else { + throw VMConfigError.emptyMachineIdentifier + } + + guard let hardwareModel = config.hardwareModel else { + throw VMConfigError.emptyHardwareModel + } + + let platform = VZMacPlatformConfiguration() + platform.auxiliaryStorage = VZMacAuxiliaryStorage(url: config.nvramPath.url) + Logger.info("Pre-VZMacHardwareModel: hardwareModel=\(hardwareModel)") + guard let vzHardwareModel = VZMacHardwareModel(dataRepresentation: hardwareModel) else { + throw VMConfigError.invalidHardwareModel + } + platform.hardwareModel = vzHardwareModel + guard let vzMachineIdentifier = VZMacMachineIdentifier(dataRepresentation: machineIdentifier) else { + throw VMConfigError.invalidMachineIdentifier + } + platform.machineIdentifier = vzMachineIdentifier + vzConfig.platform = platform + vzConfig.bootLoader = VZMacOSBootLoader() + + // Graphics configuration + let display = VMDisplayResolution(string: config.display)! + let graphics = VZMacGraphicsDeviceConfiguration() + graphics.displays = [ + VZMacGraphicsDisplayConfiguration( + widthInPixels: display.width, + heightInPixels: display.height, + pixelsPerInch: 220 // Retina display density + ) + ] + vzConfig.graphicsDevices = [graphics] + + // Common configurations + vzConfig.keyboards = [VZUSBKeyboardConfiguration()] + vzConfig.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()] + var storageDevices = [try createStorageDeviceConfiguration(diskPath: config.diskPath)] + if let mount = config.mount { + storageDevices.append(try createStorageDeviceConfiguration(diskPath: mount, readOnly: true)) + } + vzConfig.storageDevices = storageDevices + vzConfig.networkDevices = [try createNetworkDeviceConfiguration(macAddress: config.macAddress)] + vzConfig.memoryBalloonDevices = [VZVirtioTraditionalMemoryBalloonDeviceConfiguration()] + vzConfig.entropyDevices = [VZVirtioEntropyDeviceConfiguration()] + + // Directory sharing + let directorySharingDevices = createDirectorySharingDevices(sharedDirectories: config.sharedDirectories) + if !directorySharingDevices.isEmpty { + vzConfig.directorySharingDevices = directorySharingDevices + } + + try vzConfig.validate() + return vzConfig + } + + static func generateMacAddress() -> String { + VZMACAddress.randomLocallyAdministered().string + } + + static func generateMachineIdentifier() -> Data { + VZMacMachineIdentifier().dataRepresentation + } + + func createAuxiliaryStorage(at path: Path, hardwareModel: Data) throws { + guard let vzHardwareModel = VZMacHardwareModel(dataRepresentation: hardwareModel) else { + throw VMConfigError.invalidHardwareModel + } + _ = try VZMacAuxiliaryStorage(creatingStorageAt: path.url, hardwareModel: vzHardwareModel) + } + + init(configuration: VMVirtualizationServiceContext) throws { + let vzConfig = try Self.createConfiguration(configuration) + super.init(virtualMachine: VZVirtualMachine(configuration: vzConfig)) + } + + func installMacOS(imagePath: Path, progressHandler: (@Sendable (Double) -> Void)?) async throws { + var observers: [NSKeyValueObservation] = [] // must hold observer references during installation to print process + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + Task { + let installer = VZMacOSInstaller(virtualMachine: virtualMachine, restoringFromImageAt: imagePath.url) + Logger.info("Starting macOS installation") + + if let progressHandler = progressHandler { + let observer = installer.progress.observe(\.fractionCompleted, options: [.initial, .new]) { (progress, change) in + if let newValue = change.newValue { + progressHandler(newValue) + } + } + observers.append(observer) + } + + installer.install { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + Logger.error("Failed to install, error=\(error))") + continuation.resume(throwing: error) + } + } + } + } + Logger.info("macOS installation finished") + } +} + +/// Linux-specific virtualization service +@MainActor +final class LinuxVirtualizationService: BaseVirtualizationService { + static func createConfiguration(_ config: VMVirtualizationServiceContext) throws -> VZVirtualMachineConfiguration { + let vzConfig = VZVirtualMachineConfiguration() + vzConfig.cpuCount = config.cpuCount + vzConfig.memorySize = config.memorySize + + // Platform configuration + let platform = VZGenericPlatformConfiguration() + if #available(macOS 15, *) { + platform.isNestedVirtualizationEnabled = VZGenericPlatformConfiguration.isNestedVirtualizationSupported + } + vzConfig.platform = platform + + let bootLoader = VZEFIBootLoader() + bootLoader.variableStore = VZEFIVariableStore(url: config.nvramPath.url) + vzConfig.bootLoader = bootLoader + + // Graphics configuration + let display = VMDisplayResolution(string: config.display)! + let graphics = VZVirtioGraphicsDeviceConfiguration() + graphics.scanouts = [ + VZVirtioGraphicsScanoutConfiguration( + widthInPixels: display.width, + heightInPixels: display.height + ) + ] + vzConfig.graphicsDevices = [graphics] + + // Common configurations + vzConfig.keyboards = [VZUSBKeyboardConfiguration()] + vzConfig.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()] + var storageDevices = [try createStorageDeviceConfiguration(diskPath: config.diskPath)] + if let mount = config.mount { + storageDevices.append(try createStorageDeviceConfiguration(diskPath: mount, readOnly: true)) + } + vzConfig.storageDevices = storageDevices + vzConfig.networkDevices = [try createNetworkDeviceConfiguration(macAddress: config.macAddress)] + vzConfig.memoryBalloonDevices = [VZVirtioTraditionalMemoryBalloonDeviceConfiguration()] + vzConfig.entropyDevices = [VZVirtioEntropyDeviceConfiguration()] + + // Directory sharing + var directorySharingDevices = createDirectorySharingDevices(sharedDirectories: config.sharedDirectories) + + // Add Rosetta support if available + if #available(macOS 13.0, *) { + if VZLinuxRosettaDirectoryShare.availability == .installed { + do { + let rosettaShare = try VZLinuxRosettaDirectoryShare() + let rosettaDevice = VZVirtioFileSystemDeviceConfiguration(tag: "rosetta") + rosettaDevice.share = rosettaShare + directorySharingDevices.append(rosettaDevice) + Logger.info("Added Rosetta support to Linux VM") + } catch { + Logger.info("Failed to add Rosetta support: \(error.localizedDescription)") + } + } else { + Logger.info("Rosetta not installed, skipping Rosetta support") + } + } + + if !directorySharingDevices.isEmpty { + vzConfig.directorySharingDevices = directorySharingDevices + } + + try vzConfig.validate() + return vzConfig + } + + func generateMacAddress() -> String { + VZMACAddress.randomLocallyAdministered().string + } + + func createNVRAM(at path: Path) throws { + _ = try VZEFIVariableStore(creatingVariableStoreAt: path.url) + } + + init(configuration: VMVirtualizationServiceContext) throws { + let vzConfig = try Self.createConfiguration(configuration) + super.init(virtualMachine: VZVirtualMachine(configuration: vzConfig)) + } +} \ No newline at end of file diff --git a/tests/Mocks/MockVM.swift b/tests/Mocks/MockVM.swift new file mode 100644 index 00000000..cad394d2 --- /dev/null +++ b/tests/Mocks/MockVM.swift @@ -0,0 +1,30 @@ +import Foundation +@testable import lume + +@MainActor +class MockVM: VM { + private var mockIsRunning = false + + override func getOSType() -> String { + return "mock-os" + } + + override func setup(ipswPath: String, cpuCount: Int, memorySize: UInt64, diskSize: UInt64, display: String) async throws { + // Mock setup implementation + vmDirContext.config.setCpuCount(cpuCount) + vmDirContext.config.setMemorySize(memorySize) + vmDirContext.config.setDiskSize(diskSize) + vmDirContext.config.setMacAddress("00:11:22:33:44:55") + try vmDirContext.saveConfig() + } + + override func run(noDisplay: Bool, sharedDirectories: [SharedDirectory], mount: Path?) async throws { + mockIsRunning = true + try await super.run(noDisplay: noDisplay, sharedDirectories: sharedDirectories, mount: mount) + } + + override func stop() async throws { + mockIsRunning = false + try await super.stop() + } +} \ No newline at end of file diff --git a/tests/Mocks/MockVMVirtualizationService.swift b/tests/Mocks/MockVMVirtualizationService.swift new file mode 100644 index 00000000..7e24a6e3 --- /dev/null +++ b/tests/Mocks/MockVMVirtualizationService.swift @@ -0,0 +1,65 @@ +import Foundation +import Virtualization +@testable import lume + +@MainActor +final class MockVMVirtualizationService: VMVirtualizationService { + private(set) var currentState: VZVirtualMachine.State = .stopped + private(set) var startCallCount = 0 + private(set) var stopCallCount = 0 + private(set) var pauseCallCount = 0 + private(set) var resumeCallCount = 0 + + var state: VZVirtualMachine.State { + currentState + } + + private var _shouldFailNextOperation = false + private var _operationError: Error = VMError.internalError("Mock operation failed") + + nonisolated func configure(shouldFail: Bool, error: Error = VMError.internalError("Mock operation failed")) async { + await setConfiguration(shouldFail: shouldFail, error: error) + } + + @MainActor + private func setConfiguration(shouldFail: Bool, error: Error) { + _shouldFailNextOperation = shouldFail + _operationError = error + } + + func start() async throws { + startCallCount += 1 + if _shouldFailNextOperation { + throw _operationError + } + currentState = .running + } + + func stop() async throws { + stopCallCount += 1 + if _shouldFailNextOperation { + throw _operationError + } + currentState = .stopped + } + + func pause() async throws { + pauseCallCount += 1 + if _shouldFailNextOperation { + throw _operationError + } + currentState = .paused + } + + func resume() async throws { + resumeCallCount += 1 + if _shouldFailNextOperation { + throw _operationError + } + currentState = .running + } + + func getVirtualMachine() -> Any { + return "mock_vm" + } +} \ No newline at end of file diff --git a/tests/Mocks/MockVNCService.swift b/tests/Mocks/MockVNCService.swift new file mode 100644 index 00000000..baafb8dd --- /dev/null +++ b/tests/Mocks/MockVNCService.swift @@ -0,0 +1,42 @@ +import Foundation +@testable import lume + +@MainActor +final class MockVNCService: VNCService { + private(set) var url: String? + private(set) var isRunning = false + private(set) var clientOpenCount = 0 + private var _attachedVM: Any? + private let vmDirectory: VMDirectory + + init(vmDirectory: VMDirectory) { + self.vmDirectory = vmDirectory + } + + nonisolated var attachedVM: String? { + get async { + await Task { @MainActor in + _attachedVM as? String + }.value + } + } + + func start(port: Int, virtualMachine: Any?) async throws { + isRunning = true + url = "vnc://localhost:\(port)" + _attachedVM = virtualMachine + } + + func stop() { + isRunning = false + url = nil + _attachedVM = nil + } + + func openClient(url: String) async throws { + guard isRunning else { + throw VMError.vncNotConfigured + } + clientOpenCount += 1 + } +} \ No newline at end of file diff --git a/tests/VMTests.swift b/tests/VMTests.swift new file mode 100644 index 00000000..d197806e --- /dev/null +++ b/tests/VMTests.swift @@ -0,0 +1,187 @@ +import Foundation +import Testing +@testable import lume + +class MockProcessRunner: ProcessRunner { + var runCalls: [(executable: String, arguments: [String])] = [] + + func run(executable: String, arguments: [String]) throws { + runCalls.append((executable, arguments)) + } +} + +private func setupVMDirectory(_ tempDir: URL) throws -> VMDirectory { + let vmDir = VMDirectory(Path(tempDir.path)) + + // Create disk image file + let diskPath = vmDir.diskPath + let diskData = Data(repeating: 0, count: 1024 * 1024) // 1MB mock disk + try diskData.write(to: diskPath.url) + + // Create nvram file + let nvramPath = vmDir.nvramPath + let nvramData = Data(repeating: 0, count: 1024) // 1KB mock nvram + try nvramData.write(to: nvramPath.url) + + // Create initial config file + var config = try VMConfig( + os: "mock-os", + cpuCount: 1, + memorySize: 1024, + diskSize: 1024, + display: "1024x768" + ) + config.setMacAddress("00:11:22:33:44:55") + try vmDir.saveConfig(config) + + // Create .initialized file to mark VM as initialized + let initializedPath = vmDir.dir.file(".initialized") + try Data().write(to: initializedPath.url) + + return vmDir +} + +@MainActor +@Test("VM initialization and configuration") +func testVMInitialization() async throws { + let tempDir = try createTempDirectory() + let vmDir = try setupVMDirectory(tempDir) + var config = try VMConfig( + os: "mock-os", + cpuCount: 1, + memorySize: 1024, + diskSize: 1024, + display: "1024x768" + ) + config.setMacAddress("00:11:22:33:44:55") // Set MAC address to avoid nil + let home = Home(fileManager: FileManager.default) + let context = VMDirContext(dir: vmDir, config: config, home: home) + + let vm = MockVM( + vmDirContext: context, + virtualizationServiceFactory: { _ in MockVMVirtualizationService() }, + vncServiceFactory: { MockVNCService(vmDirectory: $0) } + ) + + // Test initial state + let details = vm.details + #expect(details.name == vmDir.name) + #expect(details.os == "mock-os") + #expect(details.status == "stopped") + #expect(details.vncUrl == nil) +} + +@MainActor +@Test("VM run and stop operations") +func testVMRunAndStop() async throws { + let tempDir = try createTempDirectory() + let vmDir = try setupVMDirectory(tempDir) + var config = try VMConfig( + os: "mock-os", + cpuCount: 2, + memorySize: 2048, + diskSize: 1024, + display: "1024x768" + ) + config.setMacAddress("00:11:22:33:44:55") + let home = Home(fileManager: FileManager.default) + let context = VMDirContext(dir: vmDir, config: config, home: home) + + let vm = MockVM( + vmDirContext: context, + virtualizationServiceFactory: { _ in MockVMVirtualizationService() }, + vncServiceFactory: { MockVNCService(vmDirectory: $0) } + ) + + // Test running VM + let runTask = Task { + try await vm.run(noDisplay: false, sharedDirectories: [], mount: nil) + } + + // Give the VM time to start + try await Task.sleep(nanoseconds: UInt64(1e9)) + + // Test stopping VM + try await vm.stop() + runTask.cancel() +} + +@MainActor +@Test("VM configuration updates") +func testVMConfigurationUpdates() async throws { + let tempDir = try createTempDirectory() + let vmDir = try setupVMDirectory(tempDir) + var config = try VMConfig( + os: "mock-os", + cpuCount: 1, + memorySize: 1024, + diskSize: 1024, + display: "1024x768" + ) + config.setMacAddress("00:11:22:33:44:55") + let home = Home(fileManager: FileManager.default) + let context = VMDirContext(dir: vmDir, config: config, home: home) + + let vm = MockVM( + vmDirContext: context, + virtualizationServiceFactory: { _ in MockVMVirtualizationService() }, + vncServiceFactory: { MockVNCService(vmDirectory: $0) } + ) + + // Test CPU count update + try vm.setCpuCount(4) + #expect(vm.vmDirContext.config.cpuCount == 4) + + // Test memory size update + try vm.setMemorySize(4096) + #expect(vm.vmDirContext.config.memorySize == 4096) + + // Test MAC address update + try vm.setMacAddress("00:11:22:33:44:66") + #expect(vm.vmDirContext.config.macAddress == "00:11:22:33:44:66") +} + +@MainActor +@Test("VM setup process") +func testVMSetup() async throws { + let tempDir = try createTempDirectory() + let vmDir = try setupVMDirectory(tempDir) + var config = try VMConfig( + os: "mock-os", + cpuCount: 1, + memorySize: 1024, + diskSize: 1024, + display: "1024x768" + ) + config.setMacAddress("00:11:22:33:44:55") + let home = Home(fileManager: FileManager.default) + let context = VMDirContext(dir: vmDir, config: config, home: home) + + let vm = MockVM( + vmDirContext: context, + virtualizationServiceFactory: { _ in MockVMVirtualizationService() }, + vncServiceFactory: { MockVNCService(vmDirectory: $0) } + ) + + let expectedDiskSize: UInt64 = 64 * 1024 * 1024 * 1024 // 64 GB + + try await vm.setup( + ipswPath: "/path/to/mock.ipsw", + cpuCount: 2, + memorySize: 2048, + diskSize: expectedDiskSize, + display: "1024x768" + ) + + #expect(vm.vmDirContext.config.cpuCount == 2) + #expect(vm.vmDirContext.config.memorySize == 2048) + let actualDiskSize = vm.vmDirContext.config.diskSize ?? 0 + #expect(actualDiskSize == expectedDiskSize, "Expected disk size \(expectedDiskSize), but got \(actualDiskSize)") + #expect(vm.vmDirContext.config.macAddress == "00:11:22:33:44:55") +} + +private func createTempDirectory() throws -> URL { + let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + return tempDir +} \ No newline at end of file diff --git a/tests/VMVirtualizationServiceTests.swift b/tests/VMVirtualizationServiceTests.swift new file mode 100644 index 00000000..63463358 --- /dev/null +++ b/tests/VMVirtualizationServiceTests.swift @@ -0,0 +1,68 @@ +import Foundation +import Testing +import Virtualization +@testable import lume + +@Test("VMVirtualizationService starts correctly") +func testVMVirtualizationServiceStart() async throws { + let service = MockVMVirtualizationService() + + // Initial state + #expect(await service.state == .stopped) + #expect(await service.startCallCount == 0) + + // Start service + try await service.start() + #expect(await service.state == .running) + #expect(await service.startCallCount == 1) +} + +@Test("VMVirtualizationService stops correctly") +func testVMVirtualizationServiceStop() async throws { + let service = MockVMVirtualizationService() + + // Start then stop + try await service.start() + try await service.stop() + + #expect(await service.state == .stopped) + #expect(await service.stopCallCount == 1) +} + +@Test("VMVirtualizationService handles pause and resume") +func testVMVirtualizationServicePauseResume() async throws { + let service = MockVMVirtualizationService() + + // Start and pause + try await service.start() + try await service.pause() + #expect(await service.state == .paused) + #expect(await service.pauseCallCount == 1) + + // Resume + try await service.resume() + #expect(await service.state == .running) + #expect(await service.resumeCallCount == 1) +} + +@Test("VMVirtualizationService handles operation failures") +func testVMVirtualizationServiceFailures() async throws { + let service = MockVMVirtualizationService() + await service.configure(shouldFail: true) + + // Test start failure + do { + try await service.start() + #expect(Bool(false), "Expected start to throw") + } catch let error as VMError { + switch error { + case .internalError(let message): + #expect(message == "Mock operation failed") + default: + #expect(Bool(false), "Unexpected error type: \(error)") + } + } + + #expect(await service.state == .stopped) + #expect(await service.startCallCount == 1) +} \ No newline at end of file diff --git a/tests/VNCServiceTests.swift b/tests/VNCServiceTests.swift new file mode 100644 index 00000000..9ea6f2c8 --- /dev/null +++ b/tests/VNCServiceTests.swift @@ -0,0 +1,86 @@ +import Foundation +import Testing +@testable import lume + +@Test("VNCService starts correctly") +func testVNCServiceStart() async throws { + let tempDir = try createTempDirectory() + let vmDir = VMDirectory(Path(tempDir.path)) + let service = await MockVNCService(vmDirectory: vmDir) + + // Initial state + let isRunning = await service.isRunning + let url = await service.url + #expect(!isRunning) + #expect(url == nil) + + // Start service + try await service.start(port: 5900, virtualMachine: nil) + #expect(await service.isRunning) + #expect(await service.url?.contains("5900") ?? false) +} + +@Test("VNCService stops correctly") +func testVNCServiceStop() async throws { + let tempDir = try createTempDirectory() + let vmDir = VMDirectory(Path(tempDir.path)) + let service = await MockVNCService(vmDirectory: vmDir) + try await service.start(port: 5900, virtualMachine: nil) + + await service.stop() + let isRunning = await service.isRunning + let url = await service.url + #expect(!isRunning) + #expect(url == nil) +} + +@Test("VNCService handles client operations") +func testVNCServiceClient() async throws { + let tempDir = try createTempDirectory() + let vmDir = VMDirectory(Path(tempDir.path)) + let service = await MockVNCService(vmDirectory: vmDir) + + // Should fail when not started + do { + try await service.openClient(url: "vnc://localhost:5900") + #expect(Bool(false), "Expected openClient to throw when not started") + } catch VMError.vncNotConfigured { + // Expected error + } catch { + #expect(Bool(false), "Expected vncNotConfigured error but got \(error)") + } + + // Start and try client operations + try await service.start(port: 5900, virtualMachine: nil) + try await service.openClient(url: "vnc://localhost:5900") + #expect(await service.clientOpenCount == 1) + + // Stop and verify client operations fail + await service.stop() + do { + try await service.openClient(url: "vnc://localhost:5900") + #expect(Bool(false), "Expected openClient to throw after stopping") + } catch VMError.vncNotConfigured { + // Expected error + } catch { + #expect(Bool(false), "Expected vncNotConfigured error but got \(error)") + } +} + +@Test("VNCService handles virtual machine attachment") +func testVNCServiceVMAttachment() async throws { + let tempDir = try createTempDirectory() + let vmDir = VMDirectory(Path(tempDir.path)) + let service = await MockVNCService(vmDirectory: vmDir) + let mockVM = "mock_vm" + + try await service.start(port: 5900, virtualMachine: mockVM) + let attachedVM = await service.attachedVM + #expect(attachedVM == mockVM) +} + +private func createTempDirectory() throws -> URL { + let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) + return tempDir +} \ No newline at end of file