Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions authbridge/cmd/abctl/cmd_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func serviceInstall(p servicePaths, yes, forceRestart bool, stdout, stderr io.Wr
// of the installer's output for one fact.
if !yes {
fmt.Fprintf(stdout, "This will install a %s that runs:\n %s --config %s\n\n",
supervisorName(), p.binary, p.configFile)
supervisorName(runtime.GOOS), p.binary, p.configFile)
fmt.Fprintf(stdout, "It restarts on failure and starts at login, so Claude Code keeps working\n"+
"after a crash or a reboot. Unit file: %s\n\n", p.unitFile)
}
Expand Down Expand Up @@ -308,7 +308,7 @@ func serviceInstall(p servicePaths, yes, forceRestart bool, stdout, stderr io.Wr
" Cortex still runs, just not supervised — start it yourself:\n"+
" %s --local\n\n"+
" It will not restart after a crash or come back at login while running that\n"+
" way. To stop it: kill that process.\n", supervisorName(), why, p.binary)
" way. To stop it: kill that process.\n", supervisorName(runtime.GOOS), why, p.binary)
return exitNoSupervisor
}

Expand Down Expand Up @@ -381,7 +381,7 @@ func serviceInstall(p servicePaths, yes, forceRestart bool, stdout, stderr io.Wr
if installCanSkip(configChanged, forceRestart, func() bool { return serviceIsCurrent(p) }) {
fmt.Fprintf(stdout, "Already current: %s is running under %s and healthy.\n"+
" Nothing to change. Use `abctl service restart` to restart it anyway.\n",
filepath.Base(p.binary), supervisorName())
filepath.Base(p.binary), supervisorName(runtime.GOOS))
return 0
}

Expand Down Expand Up @@ -427,7 +427,7 @@ func serviceInstall(p servicePaths, yes, forceRestart bool, stdout, stderr io.Wr
fmt.Fprintf(stdout, "Wrote %s\n", p.unitFile)
}

if err := loadService(p, stdout); errors.Is(err, errLingerUnavailable) {
if err := loadService(runtime.GOOS, p, stdout); errors.Is(err, errLingerUnavailable) {
// The unit IS loaded, so this is a caveat rather than a failure: keep going,
// but never claim it survives a logout.
fmt.Fprintf(stderr, "abctl: %v\n", err)
Expand All @@ -449,7 +449,7 @@ func serviceInstall(p servicePaths, yes, forceRestart bool, stdout, stderr io.Wr
// keeps the ports, the supervised copy loses the bind race and crash-loops, and
// the probe cheerfully succeeds against the survivor. Ask the supervisor whether
// OUR job is actually up before believing the probe.
if running, why := supervisorRunning(p); !running {
if running, why := supervisorRunning(runtime.GOOS, p); !running {
fmt.Fprintf(stderr, "abctl: the unit loaded but the supervisor does not report it running (%s).\n"+
" Something else may hold the ports — check for a Cortex you started by hand:\n"+
" pgrep -fl authbridge-prox\n"+
Expand Down Expand Up @@ -491,9 +491,9 @@ const crashRecoveryNote = "A supervisor process handles crashes (launchd will no
// better. There is now no "which path prints what" to get wrong.
func reportInstallSuccess(healthy bool, stdout io.Writer) {
if healthy {
fmt.Fprintf(stdout, "Running as a %s, healthy.\n", supervisorName())
fmt.Fprintf(stdout, "Running as a %s, healthy.\n", supervisorName(runtime.GOOS))
} else {
fmt.Fprintf(stdout, "Running as a %s.\n", supervisorName())
fmt.Fprintf(stdout, "Running as a %s.\n", supervisorName(runtime.GOOS))
}
if runtime.GOOS == "darwin" {
fmt.Fprintln(stdout, crashRecoveryNote)
Expand All @@ -505,14 +505,14 @@ func serviceUninstall(p servicePaths, yes bool, stdout, stderr io.Writer) int {
fmt.Fprintf(stdout, "Nothing to do: no unit at %s\n", p.unitFile)
return 0
}
fmt.Fprintf(stdout, "This will stop and remove the %s at:\n %s\n\n", supervisorName(), p.unitFile)
fmt.Fprintf(stdout, "This will stop and remove the %s at:\n %s\n\n", supervisorName(runtime.GOOS), p.unitFile)
fmt.Fprintf(stdout, "Cortex will no longer start at login. Claude Code stops working whenever\n"+
"the proxy is not running — `abctl claude-code disable` removes that dependency.\n\n")
if !yes && !confirm(stdout) {
fmt.Fprintln(stdout, "Not changed.")
return exitDeclined
}
if err := unloadService(p); err != nil {
if err := unloadService(runtime.GOOS, p); err != nil {
// Report but keep going: leaving the unit file behind would make a
// reinstall look installed-but-dead.
fmt.Fprintf(stderr, "abctl: %v\n", err)
Expand Down Expand Up @@ -617,7 +617,7 @@ func serviceControl(action string, p servicePaths, stdout, stderr io.Writer) int
// umask — measured at 0644, which silently undid the 0600 this sets.
tightenLog(p.logFile, stderr)
}
if err := controlService(action, p, stdout); err != nil {
if err := controlService(runtime.GOOS, action, p, stdout); err != nil {
fmt.Fprintf(stderr, "abctl: %v\n", err)
return 1
}
Expand All @@ -644,7 +644,7 @@ func serviceControl(action string, p servicePaths, stdout, stderr io.Writer) int
// Same gate install uses: an unadopted proxy holding the ports answers the
// probe while OUR job crash-loops on the bind, so health alone would report a
// restart that did not happen.
if running, why := supervisorRunning(p); !running {
if running, why := supervisorRunning(runtime.GOOS, p); !running {
fmt.Fprintf(stderr, "abctl: %sed, but the supervisor does not report it running (%s).\n"+
" Check for a Cortex started by hand holding the ports: pgrep -fl authbridge-prox\n", action, why)
for _, line := range lastLines(p.logFile, 5) {
Expand Down Expand Up @@ -794,7 +794,7 @@ func serviceIsCurrent(p servicePaths) bool {
if runtime.GOOS == "darwin" && !strings.Contains(body, "--supervise") {
return false
}
if running, _ := supervisorRunning(p); !running {
if running, _ := supervisorRunning(runtime.GOOS, p); !running {
Comment thread
Alan-Cha marked this conversation as resolved.
return false
}
if p.healthURL == "" {
Expand Down
28 changes: 14 additions & 14 deletions authbridge/cmd/abctl/cmd_service_platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func newFlagSet(name string, stderr io.Writer) *flag.FlagSet {
return fs
}

func supervisorName() string {
if runtime.GOOS == "darwin" {
func supervisorName(goos string) string {
if goos == "darwin" {
return "launchd user agent"
}
return "systemd user unit"
Expand Down Expand Up @@ -148,8 +148,8 @@ WantedBy=default.target
`
}

func loadService(p servicePaths, progress io.Writer) error {
if runtime.GOOS == "darwin" {
func loadService(goos string, p servicePaths, progress io.Writer) error {
if goos == "darwin" {
uid := strconv.Itoa(os.Getuid())
target := "gui/" + uid + "/" + launchdLabel
// Clear any disable left by `service stop`: a disabled label cannot be
Expand Down Expand Up @@ -177,10 +177,10 @@ func loadService(p servicePaths, progress io.Writer) error {
if !waitBootedOutf(target, serviceBootoutTimeout, progress) {
if bootoutErr != nil && !strings.Contains(string(bootoutOut), "No such process") {
return fmt.Errorf("could not remove the previous %s: %v: %s",
supervisorName(), bootoutErr, strings.TrimSpace(string(bootoutOut)))
supervisorName(goos), bootoutErr, strings.TrimSpace(string(bootoutOut)))
}
return fmt.Errorf("the previous %s is still shutting down after %s; "+
"run `abctl service status`, then try again", supervisorName(), serviceBootoutTimeout)
"run `abctl service status`, then try again", supervisorName(goos), serviceBootoutTimeout)
}

// Retried on EIO, re-checking the domain each time. Without the re-check the
Expand Down Expand Up @@ -266,8 +266,8 @@ func lingerEnabled(uid string) bool {
return !strings.Contains(strings.ToLower(string(out)), "linger=no")
}

func unloadService(p servicePaths) error {
if runtime.GOOS == "darwin" {
func unloadService(goos string, p servicePaths) error {
if goos == "darwin" {
uid := strconv.Itoa(os.Getuid())
if out, err := exec.Command("launchctl", "bootout", "gui/"+uid+"/"+launchdLabel).CombinedOutput(); err != nil {
return fmt.Errorf("launchctl bootout: %v: %s", err, strings.TrimSpace(string(out)))
Expand Down Expand Up @@ -380,8 +380,8 @@ func dialableAddr(addr string) string {
}

// controlService maps stop/start/restart onto the platform's supervisor.
func controlService(action string, p servicePaths, progress io.Writer) error {
if runtime.GOOS == "darwin" {
func controlService(goos, action string, p servicePaths, progress io.Writer) error {
if goos == "darwin" {
target := "gui/" + strconv.Itoa(os.Getuid()) + "/" + launchdLabel
switch action {
case "stop":
Expand All @@ -403,10 +403,10 @@ func controlService(action string, p servicePaths, progress io.Writer) error {
}
return nil
case "start":
return loadService(p, progress) // loadService clears the disable
return loadService(goos, p, progress) // loadService clears the disable
default: // restart
_ = exec.Command("launchctl", "bootout", target).Run() //nolint:errcheck
return loadService(p, progress)
return loadService(goos, p, progress)
}
}
if _, err := exec.LookPath("systemctl"); err != nil {
Expand All @@ -431,8 +431,8 @@ func controlService(action string, p servicePaths, progress io.Writer) error {
// supervisorRunning asks the supervisor whether OUR job is up, which health alone
// cannot establish: an unadopted proxy keeps the ports, the supervised copy
// crash-loops on the bind, and the probe succeeds against the survivor.
func supervisorRunning(p servicePaths) (bool, string) {
if runtime.GOOS == "darwin" {
func supervisorRunning(goos string, p servicePaths) (bool, string) {
if goos == "darwin" {
target := "gui/" + strconv.Itoa(os.Getuid()) + "/" + launchdLabel
// Poll rather than sample once. Immediately after a kickstart the job passes
// through transient states — "xpcproxy" while launchd's exec helper is still
Expand Down
13 changes: 7 additions & 6 deletions authbridge/cmd/abctl/cmd_service_restricted_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import (
)

// fakeLaunchctl puts a launchctl on PATH that behaves like a restricted sandbox: it
// cannot answer, exactly as reported from a real one.
// cannot answer, exactly as reported from a real one. Delegates to installStub
// (cmd_service_systemd_test.go) so callers get the same exec.LookPath reachability
// check fakeSystemctl/fakeLoginctl have: without it, a stub that's silently
// unreachable (PATH not applied yet, or written non-executable) makes a
// zero-calls assertion pass for the wrong reason, indistinguishable from a real
// zero-calls outcome.
func fakeLaunchctl(t *testing.T, body string) {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "launchctl"), []byte(body), 0o700); err != nil { //nolint:gosec
t.Fatal(err)
}
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
installStub(t, "launchctl", body)
}

// TestLabelGone_UnknownIsNotGone is the defect this fixes. launchctl print exits 113
Expand Down
Loading
Loading