[client] launch macOS UI via sudo -u -H for full user env (HOME/USER/LOGNAME)

Both launch paths (pkg postinstall and the updater's startUIAsUser) run as
root, so launchctl asuser alone moves the launch into the user's GUI domain
but leaves root's HOME/USER/LOGNAME. Use sudo -u <user> -H to drop to the
console user with their environment, fixing os.UserConfigDir() resolution.
Drops the space-fragile dscl/awk home lookup and the duplicate-HOME append;
extracts consoleUser() and aligns the skip list across both paths.
This commit is contained in:
riccardom
2026-07-29 17:43:02 +02:00
parent 4bba5b5f9b
commit 4b5a8c3af3
2 changed files with 27 additions and 31 deletions

View File

@@ -98,39 +98,25 @@ func (u *Installer) startDaemon(daemonFolder string) error {
func (u *Installer) startUIAsUser() error {
log.Infof("starting netbird-ui: %s", uiBinary)
// Get the current console user
cmd := exec.Command("stat", "-f", "%Su", "/dev/console")
output, err := cmd.Output()
username, err := consoleUser()
if err != nil {
return fmt.Errorf("failed to get console user: %w", err)
return err
}
username := strings.TrimSpace(string(output))
if username == "" || username == "root" {
return fmt.Errorf("no active user session found")
}
log.Infof("starting UI for user: %s", username)
// Get user's UID
userInfo, err := user.Lookup(username)
if err != nil {
return fmt.Errorf("failed to lookup user %s: %w", username, err)
return fmt.Errorf("lookup user %s: %w", username, err)
}
// Start the UI process as the console user using launchctl
// This ensures the app runs in the user's context with proper GUI access
launchCmd := exec.Command("launchctl", "asuser", userInfo.Uid, "open", "-a", uiBinary)
log.Infof("starting UI for user: %s (uid %s)", username, userInfo.Uid)
launchCmd := exec.Command("launchctl", "asuser", userInfo.Uid, "sudo", "-u", username, "-H", "open", "-a", uiBinary)
log.Infof("launchCmd: %s", launchCmd.String())
// Set the user's home directory for proper macOS app behavior
launchCmd.Env = append(os.Environ(), "HOME="+userInfo.HomeDir)
log.Infof("set HOME environment variable: %s", userInfo.HomeDir)
if err := launchCmd.Start(); err != nil {
return fmt.Errorf("failed to start UI process: %w", err)
return fmt.Errorf("start UI process: %w", err)
}
// Release the process so it can run independently
if err := launchCmd.Process.Release(); err != nil {
log.Warnf("failed to release UI process: %v", err)
}
@@ -139,6 +125,21 @@ func (u *Installer) startUIAsUser() error {
return nil
}
func consoleUser() (string, error) {
output, err := exec.Command("stat", "-f", "%Su", "/dev/console").Output()
if err != nil {
return "", fmt.Errorf("get console user: %w", err)
}
username := strings.TrimSpace(string(output))
switch username {
case "", "root", "loginwindow", "_mbsetupuser":
return "", fmt.Errorf("no active GUI user session, console user: %q", username)
}
return username, nil
}
func (u *Installer) installPkgFile(ctx context.Context, path string) error {
log.Infof("installing pkg file: %s", path)

View File

@@ -30,10 +30,6 @@ mkdir -p /usr/local/bin/
$AGENT service install || true
$AGENT service start || true
# Launch the GUI as the logged-in console user, NOT as the installer's
# (root) context. When there is no active GUI session
# (unattended MDM install, login window), there is nobody to launch as, skip it;
console_user=$(stat -f%Su /dev/console 2>/dev/null)
case "$console_user" in
""|root|loginwindow|_mbsetupuser)
@@ -41,13 +37,12 @@ mkdir -p /usr/local/bin/
;;
*)
uid=$(id -u "$console_user" 2>/dev/null)
home_dir=$(dscl . -read "/Users/$console_user" NFSHomeDirectory 2>/dev/null | awk '{print $2}')
if [ -z "$uid" ] || [ -z "$home_dir" ]; then
echo "Could not resolve uid/home for console user '$console_user' (uid='$uid', home='$home_dir'); skipping UI launch."
if [ -z "$uid" ]; then
echo "Could not resolve uid for console user '$console_user'; skipping UI launch."
else
echo "Launching NetBird UI as console user $console_user (uid $uid, home $home_dir)."
if ! HOME="$home_dir" launchctl asuser "$uid" open "$APP"; then
echo "Failed to launch NetBird UI; it will start at next login via the per-user LaunchAgent."
echo "Launching NetBird UI as console user $console_user (uid $uid)."
if ! launchctl asuser "$uid" sudo -u "$console_user" -H open "$APP"; then
echo "Failed to launch NetBird UI; if autostart is enabled it will start at next login."
fi
fi
;;