Compare commits

...

6 Commits

Author SHA1 Message Date
riccardom
b09c808971 [client] wait for macOS UI launch and propagate its error
Replace Start()+Process.Release() with Run() in startUIAsUser: the
launchctl/sudo/open chain is short-lived (open returns once LaunchServices
accepts the request), so waiting surfaces a launch failure to the caller
instead of swallowing it. Addresses CodeRabbit review.
2026-07-29 18:11:57 +02:00
riccardom
4b5a8c3af3 [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.
2026-07-29 17:45:41 +02:00
riccardom
4bba5b5f9b Remove comments 2026-07-29 16:58:37 +02:00
riccardom
53933f9612 [client] validate console user uid/home and set HOME on macOS UI launch
Address CodeRabbit review on the pkg postinstall: resolve and validate the
console user's uid and home dir before launching, skip visibly instead of
masking with `|| true`, and set HOME to the resolved home so the GUI reads
the user's real ui-preferences.json.
2026-07-29 16:17:58 +02:00
riccardom
a5e71db577 Logs on fallback ui preferences 2026-07-29 14:33:24 +02:00
riccardom
451fa42fbd Start the UI only on behalf of a valid logged user or avoid running it
Wails already does this: the GUI is started by a per-user LaunchAgent.
So starting the GUI assumes there is a user that is logging in to refer to.

We are aligning the auto-update to this model here.

IF there is a user, restart the GUI otherwise wait for a login.

WHY it didn't happen before 0.75: cause the Fyne GUI never wrote any ui_preferences.json file
or any other in users $HOME folder.

So it's a problem related to the new GUI and has nothing to do with MDM.
2026-07-29 14:21:44 +02:00
3 changed files with 41 additions and 27 deletions

View File

@@ -98,47 +98,44 @@ 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)
}
// Release the process so it can run independently
if err := launchCmd.Process.Release(); err != nil {
log.Warnf("failed to release UI process: %v", err)
if err := launchCmd.Run(); err != nil {
return fmt.Errorf("run UI launch: %w", err)
}
log.Infof("netbird-ui started successfully for user %s", username)
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

@@ -246,6 +246,7 @@ func (s *Store) ExistedAtLoad() bool {
func (s *Store) load() error {
if _, err := os.Stat(s.path); err != nil {
if errors.Is(err, os.ErrNotExist) {
log.Infof("no ui preferences file at %s; using defaults", s.path)
return nil
}
return fmt.Errorf("stat preferences: %w", err)

View File

@@ -30,7 +30,23 @@ mkdir -p /usr/local/bin/
$AGENT service install || true
$AGENT service start || true
open $APP
console_user=$(stat -f%Su /dev/console 2>/dev/null)
case "$console_user" in
""|root|loginwindow|_mbsetupuser)
echo "No active GUI user session (console user: '${console_user:-none}'); skipping UI launch."
;;
*)
uid=$(id -u "$console_user" 2>/dev/null)
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)."
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
;;
esac
echo "Finished Netbird installation successfully"
exit 0 # all good