import Foundation /// Wrapper um das `rebreak-supervise-magic` Go-binary aus /// `ops/mdm/supervise-magic/bin/`. Spawnt es als child-process + streamt /// stdout zeilenweise in die UI. enum SuperviseRunner { enum RunnerError: Error, LocalizedError { case binaryMissing case nonZeroExit(Int32) var errorDescription: String? { switch self { case .binaryMissing: return "supervise-magic Binary nicht gefunden. Bitte aus `ops/mdm/supervise-magic/` via `make build` bauen oder REBREAK_SUPERVISE_MAGIC_BIN setzen." case .nonZeroExit(let code): return "supervise-magic ist mit Exit-Code \(code) abgebrochen." } } } /// Pre-Flight Check: liest FMI/SDP-Status + IsSupervised. /// Returnt das geparste output. Mit -v für detaillierte logs. @MainActor static func check(onLine: @escaping (String) -> Void) async throws -> ProcessRunner.Result { guard let bin = Paths.firstExecutable(in: Paths.superviseMagicCandidates) else { throw RunnerError.binaryMissing } return try await ProcessRunner.stream(bin, arguments: ["-v", "check"], onLine: onLine) } /// Schreibt CloudConfigurationDetails.plist auf das iPhone + reboot. /// supervise-magic macht die ganze MobileBackup2-Sandwich-Logik. @MainActor static func supervise( organizationName: String = "ReBreak", force: Bool = true, verbose: Bool = false, onLine: @escaping (String) -> Void ) async throws -> ProcessRunner.Result { guard let bin = Paths.firstExecutable(in: Paths.superviseMagicCandidates) else { throw RunnerError.binaryMissing } // -yes ist Pflicht: ohne TTY-Pipe hängt der Bestätigungs-Prompt sonst endlos. var args: [String] = verbose ? ["-v", "-yes"] : ["-yes"] if force { args.append("-force") } args.append(contentsOf: ["-org", organizationName, "supervise"]) let result = try await ProcessRunner.stream(bin, arguments: args, onLine: onLine) if result.exitCode != 0 { throw RunnerError.nonZeroExit(result.exitCode) } return result } /// Reverse-Operation für Tests / Recovery. @MainActor static func unsupervise(onLine: @escaping (String) -> Void) async throws -> ProcessRunner.Result { guard let bin = Paths.firstExecutable(in: Paths.superviseMagicCandidates) else { throw RunnerError.binaryMissing } return try await ProcessRunner.stream(bin, arguments: ["-v", "-yes", "unsupervise"], onLine: onLine) } }