diff --git a/Onyx/FanControl.swift b/Onyx/FanControl.swift index ae7ab42..18aea1b 100644 --- a/Onyx/FanControl.swift +++ b/Onyx/FanControl.swift @@ -29,6 +29,9 @@ public final class FanControl: ChargeLimitReading { private var connection: NSXPCConnection? private var heartbeatTimer: Timer? private var statusTimer: Timer? + /// Ob die Registrierung in diesem Programmlauf schon einmal erneuert wurde. + /// Einmal ist Selbstheilung, immer wieder wäre eine Schleife. + private var hasRepairedRegistration = false private var service: SMAppService { SMAppService.daemon(plistName: "com.scarriffleservices.onyx.helper.plist") @@ -117,10 +120,41 @@ public final class FanControl: ChargeLimitReading { Task { @MainActor in self?.lastError = error.localizedDescription log.error("Helfer nicht erreichbar: \(error.localizedDescription, privacy: .public)") + await self?.repairRegistration() } } as? OnyxHelperProtocol } + /// Meldet den Helfer ab und wieder an. + /// + /// **Der Fall, für den das da ist: die App ist umgezogen.** Aus dem + /// Ordner „Downloads" nach „Programme", oder vom Bauverzeichnis in ein + /// installiertes Bundle. macOS bindet den privilegierten Dienst beim + /// Anmelden an das Programm, das ihn angemeldet hat — nach dem Umzug ist + /// das ein anderes Bundle. Der alte Dienst läuft weiter, redet aber nicht + /// mit dem neuen Programm. + /// + /// Von außen ist das die verwirrendste Kombination, die es gibt: der + /// Zustand meldet „eingerichtet", und trotzdem kommt bei jedem Zugriff + /// „Kommunikation nicht möglich". Ohne diese Reparatur bleibt nur, den + /// Helfer von Hand zu entfernen und neu einzurichten — und darauf muss + /// man erst einmal kommen. + /// + /// Danach steht der Dienst auf „wartet auf Freigabe": das Verschieben + /// eines Programms mit Root-Dienst darf niemand still hinnehmen, auch + /// diese App nicht. + private func repairRegistration() async { + guard !hasRepairedRegistration, installState == .installed else { return } + hasRepairedRegistration = true + log.notice("Helfer nicht erreichbar — Registrierung wird erneuert") + + disconnect() + try? await service.unregister() + // launchd räumt den alten Dienst nicht im selben Atemzug ab. + try? await Task.sleep(for: .seconds(1)) + install() + } + private func disconnect() { heartbeatTimer?.invalidate(); heartbeatTimer = nil statusTimer?.invalidate(); statusTimer = nil diff --git a/Scripts/lib-dmg.sh b/Scripts/lib-dmg.sh index b70ded5..f17619b 100644 --- a/Scripts/lib-dmg.sh +++ b/Scripts/lib-dmg.sh @@ -33,15 +33,26 @@ make_pretty_dmg() { hdiutil create -volname "$volname" -srcfolder "$stage" -size 100m \ -fs HFS+ -format UDRW -ov "$temp" >/dev/null - local mount="/Volumes/$volname" - hdiutil attach "$temp" -nobrowse -quiet - # Ohne kurze Pause findet das nächste Skript das Volume noch nicht. + # Ein gleichnamiges Volume vorher abhängen. Sonst hängt macOS das neue als + # „Onyx 1" ein, das AppleScript richtet weiter „Onyx" ein — und das fertige + # Abbild sieht aus wie vorher, ohne dass irgendetwas fehlschlägt. + while [ -d "/Volumes/$volname" ]; do + hdiutil detach "/Volumes/$volname" -force -quiet || break + sleep 1 + done + + # Den tatsächlichen Einhängepunkt aus der Ausgabe lesen statt ihn zu raten. + local mount + mount=$(hdiutil attach "$temp" -nobrowse -noverify \ + | grep -o '/Volumes/.*' | tail -1) + [ -n "$mount" ] || { echo " (Abbild ließ sich nicht einhängen)"; return 1; } + local disk; disk="$(basename "$mount")" sleep 2 echo "→ Fenster einrichten" osascript </dev/null || echo " (Finder nicht erreichbar — das Fenster bleibt schlicht)" tell application "Finder" - tell disk "$volname" + tell disk "$disk" open set current view of container window to icon view set toolbar visible of container window to false