Merge nucleic/mellow-dewy-falcon-rjhr into main

This commit is contained in:
2026-08-07 00:44:36 -07:00
parent 749f0be4fb
commit bc2b6cd33b
47 changed files with 13159 additions and 0 deletions
@@ -0,0 +1,61 @@
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)
}
}
}