Zwei Bugs:
1) 'profiles install -path' wurde mit macOS 15+ entfernt
('profiles tool no longer supports installs. Use System Settings
Profiles to add configuration profiles.'). Auf macOS 26 (Tahoe)
ist das Hard-Removal.
-> Switch zu NSWorkspace.shared.open(profileURL): \u00f6ffnet die
.mobileconfig in System Settings -> Profile-Pane. User best\u00e4tigt
manuell + gibt Admin-PW. Einziger Weg ohne MDM-Enrollment.
-> success-Text passt: 'Bitte in System Settings Installieren
klicken'.
2) Doppelte 'Mac registriert'-Karte: successMessage-Card UND
strukturierte Registration-Status-Card beide sichtbar nach
register. Auto-Profile-Install nach Register war eh totes
Verhalten (DNS jetzt optional).
-> successMessage wird nicht mehr in handleRegistration gesetzt,
nur noch in handleProfileInstall. Eine Karte.
107 lines
4.1 KiB
Swift
107 lines
4.1 KiB
Swift
import Foundation
|
|
import AppKit
|
|
|
|
/// Service für Mac-DNS-Profile-Download + Installation.
|
|
enum MacProfileInstaller {
|
|
|
|
enum InstallerError: Error, LocalizedError {
|
|
case noRegistration
|
|
case downloadFailed(String)
|
|
case installFailed(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .noRegistration:
|
|
return "Mac ist nicht registriert. Bitte zuerst registrieren."
|
|
case .downloadFailed(let msg):
|
|
return "Profile-Download fehlgeschlagen: \(msg)"
|
|
case .installFailed(let msg):
|
|
return "Profile-Installation fehlgeschlagen: \(msg)"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Lädt Mac-DNS-Profile von Backend und öffnet es in System Settings → Profiles.
|
|
/// Ab macOS 15+ ist `profiles install` für Configuration Profiles entfernt
|
|
/// ("profiles tool no longer supports installs. Use System Settings
|
|
/// Profiles to add configuration profiles."). Einzig zulässiger Weg ohne
|
|
/// MDM-Enrollment: NSWorkspace öffnet die .mobileconfig → Profiles-Pane
|
|
/// erscheint → User muss manuell „Installieren" klicken + Admin-PW geben.
|
|
static func downloadAndInstall(registration: MagicRegistration) async throws {
|
|
// 1. Download profile
|
|
let profileURL: URL
|
|
do {
|
|
profileURL = try await MagicAPIClient.shared.downloadProfile(token: registration.dnsToken)
|
|
} catch {
|
|
throw InstallerError.downloadFailed(error.localizedDescription)
|
|
}
|
|
|
|
// 2. Open in System Settings → Profiles (user must confirm in UI)
|
|
let opened = await MainActor.run {
|
|
NSWorkspace.shared.open(profileURL)
|
|
}
|
|
|
|
if !opened {
|
|
try? FileManager.default.removeItem(at: profileURL)
|
|
throw InstallerError.installFailed(
|
|
"System Settings konnte das Profil nicht öffnen. Datei liegt unter: \(profileURL.path)"
|
|
)
|
|
}
|
|
|
|
// NICHT löschen — System Settings braucht die Datei evtl. noch.
|
|
// OS räumt /tmp selbst auf.
|
|
}
|
|
|
|
/// Prüft ob ReBreak-DNS-Profile bereits installiert ist.
|
|
/// Verwendet `profiles show -type configuration`.
|
|
static func isInstalled() async -> Bool {
|
|
guard let result = try? await ProcessRunner.run(
|
|
"/usr/bin/profiles",
|
|
arguments: ["show", "-type", "configuration"]
|
|
), result.exitCode == 0 else {
|
|
return false
|
|
}
|
|
|
|
// Suche nach PayloadIdentifier pattern org.rebreak.protection.dns.filter*
|
|
return result.stdout.localizedCaseInsensitiveContains("org.rebreak.protection.dns.filter")
|
|
|| result.stdout.localizedCaseInsensitiveContains("org.rebreak.protection.profile")
|
|
}
|
|
|
|
/// Entfernt ReBreak-DNS-Profile (für Testing/Reset).
|
|
/// Benötigt PayloadIdentifier — wir suchen nach "org.rebreak.protection.profile.*".
|
|
static func remove() async throws {
|
|
// 1. Find identifier
|
|
guard let result = try? await ProcessRunner.run(
|
|
"/usr/bin/profiles",
|
|
arguments: ["show", "-type", "configuration"]
|
|
), result.exitCode == 0 else {
|
|
return
|
|
}
|
|
|
|
// Parse identifier aus Output (format: " <identifier>: <displayName>")
|
|
let lines = result.stdout.split(separator: "\n")
|
|
var identifier: String?
|
|
|
|
for line in lines {
|
|
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
|
if trimmed.hasPrefix("org.rebreak.protection.profile") {
|
|
// Format: "org.rebreak.protection.profile.abc123: ReBreak Protection"
|
|
identifier = trimmed.split(separator: ":").first.map(String.init)
|
|
break
|
|
}
|
|
}
|
|
|
|
guard let id = identifier else { return }
|
|
|
|
// 2. Remove profile
|
|
let removeResult = try await ProcessRunner.run(
|
|
"/usr/bin/profiles",
|
|
arguments: ["remove", "-identifier", id]
|
|
)
|
|
|
|
if removeResult.exitCode != 0 {
|
|
throw InstallerError.installFailed(removeResult.stderr)
|
|
}
|
|
}
|
|
}
|