fix: show a GUI password dialog when brew needs sudo

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 <noreply@anthropic.com>
This commit is contained in:
maxsoch 2026-07-06 21:27:45 +02:00
parent 7390d68a33
commit e579ac1d56

View file

@ -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