Nucleic: Gitea Runner macOS VM Support
This commit is contained in:
@@ -35,7 +35,13 @@ struct ImageCommand: AsyncParsableCommand {
|
||||
var name: String = "default"
|
||||
|
||||
/// A local `.ipsw`; omit to download the latest supported image.
|
||||
@Option(name: .long, help: "Path to a local .ipsw (default: download the latest supported).")
|
||||
///
|
||||
/// Resolved through ``PathResolution`` so it works whether or not the
|
||||
/// shell got to the glob first: `--ipsw '~/Downloads/UniversalMac_27*.ipsw'`
|
||||
/// and the unquoted form both land on the same file.
|
||||
@Option(
|
||||
name: .long,
|
||||
help: "Path to a local .ipsw; may be a glob (default: download the latest supported).")
|
||||
var ipsw: String?
|
||||
|
||||
/// Nominal guest disk size, overriding `guest.diskGB`.
|
||||
@@ -44,6 +50,12 @@ struct ImageCommand: AsyncParsableCommand {
|
||||
|
||||
func run() async throws {
|
||||
CLI.bootstrapLogging(verbose: options.verbose)
|
||||
|
||||
// Before anything else touches the disk: an unresolvable --ipsw is
|
||||
// an argument error, and an argument error should not first make the
|
||||
// operator wait on a config load and a free-space check.
|
||||
let resolvedIPSW = try ipsw.map { try PathResolution.resolve($0, label: "--ipsw") }
|
||||
|
||||
var config = try options.loadConfig()
|
||||
if let diskGB {
|
||||
config.guest.diskGB = diskGB
|
||||
@@ -64,7 +76,7 @@ struct ImageCommand: AsyncParsableCommand {
|
||||
let printer = ProgressPrinter()
|
||||
let builder = ImageBuilder(store: store)
|
||||
let imageName = name
|
||||
let ipswPath = ipsw
|
||||
let ipswPath = resolvedIPSW
|
||||
let frozenConfig = config
|
||||
|
||||
// `image build` runs `VZMacOSInstaller` and then boots the guest, so
|
||||
@@ -201,20 +213,28 @@ struct ImageCommand: AsyncParsableCommand {
|
||||
|
||||
/// Optional Xcode `.xip` to install into the guest. Adds tens of
|
||||
/// gigabytes; omitted by default.
|
||||
@Option(name: .customLong("xcode-xip"), help: "Path to an Xcode .xip to install into the guest.")
|
||||
@Option(
|
||||
name: .customLong("xcode-xip"),
|
||||
help: "Path to an Xcode .xip to install into the guest; may be a glob.")
|
||||
var xcodeXIP: String?
|
||||
|
||||
func run() async throws {
|
||||
CLI.bootstrapLogging(verbose: options.verbose)
|
||||
|
||||
// Same treatment as `image build --ipsw`, and for the same reason:
|
||||
// this is a long path to a big file that people reach for with a
|
||||
// glob. Resolved first so a bad one costs nothing.
|
||||
let resolvedXIP = try xcodeXIP.map { try PathResolution.resolve($0, label: "--xcode-xip") }
|
||||
if let resolvedXIP, !FileManager.default.fileExists(atPath: resolvedXIP) {
|
||||
throw ValidationError("no file at \(resolvedXIP)")
|
||||
}
|
||||
|
||||
let config = try options.loadConfig()
|
||||
let store = VMStore(config: config)
|
||||
|
||||
guard try store.image(named: name) != nil else {
|
||||
throw ValidationError("no image named '\(name)'")
|
||||
}
|
||||
if let xcodeXIP, !FileManager.default.fileExists(atPath: RunnerConfig.expandTilde(xcodeXIP)) {
|
||||
throw ValidationError("no file at \(RunnerConfig.expandTilde(xcodeXIP))")
|
||||
}
|
||||
|
||||
CLI.note("provisioning base image '\(name)' in place — stop the daemon before doing this")
|
||||
|
||||
@@ -222,7 +242,7 @@ struct ImageCommand: AsyncParsableCommand {
|
||||
let builder = ImageBuilder(store: store)
|
||||
let imageName = name
|
||||
let frozenConfig = config
|
||||
let xipPath = xcodeXIP.map(RunnerConfig.expandTilde)
|
||||
let xipPath = resolvedXIP
|
||||
|
||||
// Boots the image to run provision.sh in it, so it needs the run
|
||||
// loop for exactly the reason `image build` does.
|
||||
|
||||
Reference in New Issue
Block a user