62 lines
2.2 KiB
Swift
62 lines
2.2 KiB
Swift
import ArgumentParser
|
|
import Foundation
|
|
import RunnerCore
|
|
import RunnerHost
|
|
|
|
/// `gitea-macos-runner doctor` — verify the host before anything else.
|
|
///
|
|
/// Every check corresponds to a failure that would otherwise show up as an
|
|
/// opaque error deep inside a VM boot: wrong architecture, unsigned binary,
|
|
/// locked keychain, non-admin Gitea token, dead download URL. Run this first,
|
|
/// and again after `service install`.
|
|
struct DoctorCommand: AsyncParsableCommand {
|
|
static let configuration = CommandConfiguration(
|
|
commandName: "doctor",
|
|
abstract: "Check that this host can build and run macOS guests."
|
|
)
|
|
|
|
@OptionGroup var options: GlobalOptions
|
|
|
|
/// Emit machine-readable JSON instead of aligned text.
|
|
@Flag(name: .long, help: "Emit results as JSON.")
|
|
var json: Bool = false
|
|
|
|
/// By default `doctor` exits non-zero when any check fails, so it can gate a
|
|
/// setup script. This makes it always exit zero.
|
|
@Flag(name: .customLong("no-fail"), help: "Exit zero even when checks fail.")
|
|
var noFail: Bool = false
|
|
|
|
func run() async throws {
|
|
// Deliberately does not use options.loadConfig(): a broken or missing
|
|
// config is exactly the state doctor exists to diagnose, so it is
|
|
// reported as a check rather than thrown as an error.
|
|
let checks = await Doctor.runChecks(configPath: options.configPath)
|
|
|
|
if json {
|
|
let payload: [[String: Any]] = checks.map { check in
|
|
var entry: [String: Any] = [
|
|
"name": check.name,
|
|
"result": check.result.label,
|
|
"detail": check.detail,
|
|
"blocking": check.isBlocking,
|
|
]
|
|
if let remediation = check.remediation {
|
|
entry["remediation"] = remediation
|
|
}
|
|
return entry
|
|
}
|
|
let data = try JSONSerialization.data(
|
|
withJSONObject: payload,
|
|
options: [.prettyPrinted, .sortedKeys]
|
|
)
|
|
print(String(data: data, encoding: .utf8) ?? "[]")
|
|
} else {
|
|
print(Doctor.format(checks))
|
|
}
|
|
|
|
if !noFail, checks.contains(where: \.isBlocking) {
|
|
throw ExitCode(1)
|
|
}
|
|
}
|
|
}
|