chahinebrini 2cb1f8ad6e feat(binder-mac): SwiftUI Wizard für Self-Bind End-to-End-Flow
apps/rebreak-binder-mac/ — neue macOS-App die User durch den kompletten
Self-Bind-Prozess führt: Welcome → Preflight → Supervise → Enroll →
Configure (MDM-Push + Pre/Post-Check) → Sideload Lock-Profile (AirDrop).

3-Layer Smart-Resume: supervised? + Enrollment-Profil installed (cfgutil
Ground-Truth)? + MDM-Ack fresh (NanoMDM-DB via ssh+psql)?

Services: DeviceDetector (ideviceinfo + cfgutil), SuperviseRunner
(spawnt supervise-magic CLI), MDMClient (PUT /v1/enqueue?push=1, Apple
XML-Plist, identisch zum server-watcher-Format), MDMStatus (DB-Real-
Check + ManagedApplicationList-Result-Read).

Plus:
- fix(supervise-magic): EOF nach ProcessMessage Response (ErrorCode=0)
  ist Success, nicht Error — vermeidet false-fail bei iPhone-Restore-
  Reboot
- feat(mdm-profiles): rebreak-content-filter-mdm.mobileconfig als
  MDM-Push-Variante (ohne ConsentText, ohne globales allowAppRemoval=
  false — per-app via managed-state)

End-to-End validiert: App-Push via Ad-Hoc-Manifest (silent), Managed-
State via ManagedApplicationList-Query, NEFilter-Mode nach App-Force-
Quit, Lock-Profile non-removable nach Sideload.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 08:37:14 +02:00

62 lines
2.5 KiB
Swift

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,
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] = ["-v", "-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)
}
}