diff --git a/orbit/changes/25924-change-how-macos-desktop-launches b/orbit/changes/25924-change-how-macos-desktop-launches new file mode 100644 index 0000000000..7f789e52ba --- /dev/null +++ b/orbit/changes/25924-change-how-macos-desktop-launches @@ -0,0 +1 @@ +- changed the way macos opens the fleet desktop app to better ensure successful launch \ No newline at end of file diff --git a/orbit/cmd/orbit/orbit.go b/orbit/cmd/orbit/orbit.go index 802cf145a7..530a62a16b 100644 --- a/orbit/cmd/orbit/orbit.go +++ b/orbit/cmd/orbit/orbit.go @@ -1622,16 +1622,20 @@ func (d *desktopRunner) Execute() error { // On MacOS, if we attempt to run Fleet Desktop while the user is not logged in through // the GUI, MacOS returns an error. See https://github.com/fleetdm/fleet/issues/14698 // for more details. - loggedInGui, err := user.IsUserLoggedInViaGui() + loggedInUser, err := user.UserLoggedInViaGui() if err != nil { log.Debug().Err(err).Msg("desktop.IsUserLoggedInGui") return true } - if !loggedInGui { + if loggedInUser == nil { return true } + if *loggedInUser != "" { + opts = append(opts, execuser.WithUser(*loggedInUser)) + } + log.Info().Msg("killing any pre-existing fleet-desktop instances") if err := platform.SignalProcessBeforeTerminate(constant.DesktopAppExecName); err != nil && !errors.Is(err, platform.ErrProcessNotFound) && diff --git a/orbit/pkg/execuser/execuser.go b/orbit/pkg/execuser/execuser.go index 3687163218..01053f824d 100644 --- a/orbit/pkg/execuser/execuser.go +++ b/orbit/pkg/execuser/execuser.go @@ -12,6 +12,7 @@ type eopts struct { args [][2]string stderrPath string //nolint:structcheck,unused timeout time.Duration + user string } // Option allows configuring the application. @@ -38,6 +39,13 @@ func WithTimeout(duration time.Duration) Option { } } +// WithUser sets the user to run the application as. Currently only supported on MacOS. +func WithUser(user string) Option { + return func(a *eopts) { + a.user = user + } +} + // Run runs an application as the current login user. // It assumes the caller is running with high privileges (root on Unix, SYSTEM on Windows). // diff --git a/orbit/pkg/execuser/execuser_darwin.go b/orbit/pkg/execuser/execuser_darwin.go index 78286ee5db..14c4c1e5d1 100644 --- a/orbit/pkg/execuser/execuser_darwin.go +++ b/orbit/pkg/execuser/execuser_darwin.go @@ -41,7 +41,8 @@ func run(path string, opts eopts) (lastLogs string, err error) { } } - cmd := exec.Command("/usr/bin/open", arg...) + arg = append([]string{"-u", opts.user, "/usr/bin/open"}, arg...) + cmd := exec.Command("sudo", arg...) tw := &TransientWriter{} cmd.Stderr = io.MultiWriter(tw, os.Stderr) cmd.Stdout = io.MultiWriter(tw, os.Stdout) diff --git a/orbit/pkg/user/user_darwin.go b/orbit/pkg/user/user_darwin.go index 90089ef603..5d86dd1b7c 100644 --- a/orbit/pkg/user/user_darwin.go +++ b/orbit/pkg/user/user_darwin.go @@ -4,27 +4,34 @@ package user import ( - "errors" + "bytes" "os/exec" - "strings" + "regexp" ) -// IsUserLoggedInViaGui returns whether or not a user is logged into the machine via the GUI. -func IsUserLoggedInViaGui() (bool, error) { - output, err := exec.Command("/usr/bin/stat", "-f", "%Su", "/dev/console").Output() +var re = regexp.MustCompile(`\s+Name : (\S+)`) + +// UserLoggedInViaGui returns the name of the user logged into the machine via the GUI. +func UserLoggedInViaGui() (*string, error) { + // Attempt to get the console user. + cmd := exec.Command("/bin/sh", "-c", `scutil <<< "show State:/Users/ConsoleUser"`) + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() if err != nil { - var ee *exec.ExitError - if errors.As(err, &ee) && len(ee.Stderr) > 0 { - return false, errors.Join(err, errors.New(string(ee.Stderr))) + return nil, err + } + + // Extract all "Name : username" entries, and return the first one that + // isn't _mbsetupuser (if any). + matches := re.FindAllStringSubmatch(out.String(), -1) + + for _, match := range matches { + if len(match) > 1 && match[1] != "" && match[1] != "_mbsetupuser" { + return &match[1], nil } - - return false, err } - // If no user is logged in via GUI, the command line returns "root". - if strings.TrimSpace(string(output)) == "root" { - return false, nil - } - - return true, nil + // No valid user found + return nil, nil } diff --git a/orbit/pkg/user/user_notdarwin.go b/orbit/pkg/user/user_notdarwin.go index d7be4fe41c..28d48b220d 100644 --- a/orbit/pkg/user/user_notdarwin.go +++ b/orbit/pkg/user/user_notdarwin.go @@ -3,9 +3,10 @@ package user -// IsUserLoggedInViaGui returns whether or not a user is logged into the machine via the GUI. This +// UserLoggedInViaGui returns the name of the user logged into the machine via the GUI. This // function is only relevant on MacOS, where it is used to prevent errors when launching Fleet -// Desktop. We assume yes (effectively a no-op) on all other platforms. -func IsUserLoggedInViaGui() (bool, error) { - return true, nil +// Desktop. For other platforms we return an empty string which can be ignored. +func UserLoggedInViaGui() (*string, error) { + user := "" + return &user, nil }