From e579ac1d560c57b9ba9403c6bcdf502ac5dd240c Mon Sep 17 00:00:00 2001 From: maxsoch Date: Mon, 6 Jul 2026 21:27:45 +0200 Subject: [PATCH] fix: show a GUI password dialog when brew needs sudo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brew runs inside a Process with no terminal attached, so any sudo prompt (cask upgrades, some installers) would hang or fail silently — there was nowhere to type the password. Set SUDO_ASKPASS to a small helper script written to Application Support at launch. When sudo detects no tty, it runs the helper, which shows a native macOS password dialog via osascript and prints the answer for sudo to consume. Cancelling the dialog makes sudo fail cleanly instead of hanging. Co-Authored-By: Claude Fable 5 --- BrewBar/BrewBarApp.swift | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/BrewBar/BrewBarApp.swift b/BrewBar/BrewBarApp.swift index 44b7ecf..9df1548 100644 --- a/BrewBar/BrewBarApp.swift +++ b/BrewBar/BrewBarApp.swift @@ -174,6 +174,23 @@ class AppDelegate: NSObject, NSApplicationDelegate { return "brew" } + /// Written once per launch; sudo runs it to show a GUI password dialog + /// whenever brew needs admin rights (no terminal is attached to Process). + static let askpassURL: URL = { + let dir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + .appendingPathComponent("BrewBar", isDirectory: true) + try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + + let url = dir.appendingPathComponent("askpass.sh") + let script = """ + #!/bin/zsh + osascript -e 'display dialog "BrewBar needs your administrator password to continue:" default answer "" with hidden answer with title "BrewBar" with icon caution buttons {"Cancel", "OK"} default button "OK"' -e 'text returned of result' + """ + try? script.write(to: url, atomically: true, encoding: .utf8) + try? FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: url.path) + return url + }() + func runBrew(_ command: String, completion: @escaping (String) -> Void = { _ in }) { DispatchQueue.global().async { let brew = self.resolveBrewPath() @@ -186,6 +203,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { process.executableURL = URL(fileURLWithPath: "/bin/zsh") process.arguments = ["-c", fullCommand] + var environment = ProcessInfo.processInfo.environment + environment["SUDO_ASKPASS"] = Self.askpassURL.path + process.environment = environment + let pipe = Pipe() process.standardOutput = pipe process.standardError = pipe