Support Ubuntu 24.04 with Xorg (by detecting user's DISPLAY environment variable) (#18996)
#18925 (Should also fix #17660.) Tests: - Ubuntu 22.04.2 - Wayland - Works with chrome ✅ - Doesn't work with Firefox. ❌ - Xorg - Works with Chrome. ✅ - Works with Firefox. ✅ - Ubuntu 24.04 - Wayland - Doesn't work with Chrome. ❌ - Doesn't work with Firefox. ❌ - Xorg (when using Xorg it defaults to `DISPLAY=:1`, and with the changes in this PR it works): - Works with Chrome. ✅ - Works with Firefox. ✅ --- How to change between Wayland and Xorg: - Set `WaylandEnable=false` in `/etc/gdm3/custom.conf` and reboot. --- How to determine what's running: ```sh $ loginctl SESSION UID USER SEAT TTY 2 1000 luk seat0 tty2 c2 1000 luk $ loginctl show-session 2 -p Type # will output Type=wayland or Type=x11 ``` --- - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Added/updated tests - [X] Manual QA for all new/changed functionality - For Orbit and Fleet Desktop changes: - [X] Manual QA must be performed in the three main OSs, macOS, Windows and Linux. - [x] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
package execuser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -19,23 +23,44 @@ func run(path string, opts eopts) error {
|
||||
return fmt.Errorf("get user: %w", err)
|
||||
}
|
||||
|
||||
// TODO(lucas): Default to display :0 if user DISPLAY environment variable
|
||||
// could not be found, revisit when working on multi-user/multi-session support.
|
||||
// This assumes there's only one desktop session and belongs to the
|
||||
// user returned in `getLoginUID'.
|
||||
defaultDisplay := ":0"
|
||||
|
||||
log.Info().
|
||||
Str("user", user.name).
|
||||
Int64("id", user.id).
|
||||
Msg("running sudo")
|
||||
Msg("attempting to get user's DISPLAY")
|
||||
|
||||
// Flag `-i` is needed to run the command with the user's context, from `man sudo`:
|
||||
// "The command is run with an environment similar to the one a user would receive at log in"
|
||||
arg := []string{"-i", "-u", user.name, "-H"}
|
||||
for _, nv := range opts.env {
|
||||
arg = append(arg, fmt.Sprintf("%s=%s", nv[0], nv[1]))
|
||||
display, err := getUserDisplay(user.name, opts)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Str("user", user.name).
|
||||
Int64("id", user.id).
|
||||
Err(err).
|
||||
Msgf("failed to get user's DISPLAY, using default %s", defaultDisplay)
|
||||
display = defaultDisplay
|
||||
} else if display == "" {
|
||||
log.Warn().
|
||||
Str("user", user.name).
|
||||
Int64("id", user.id).
|
||||
Msgf("user's DISPLAY not found, using default %s", defaultDisplay)
|
||||
display = defaultDisplay
|
||||
}
|
||||
|
||||
arg = append(arg,
|
||||
// TODO(lucas): Default to display 0, revisit when working on
|
||||
// multi-user/multi-session support. This assumes there's only
|
||||
// one desktop session and belongs to the user returned in `getLoginUID'.
|
||||
"DISPLAY=:0",
|
||||
log.Info().
|
||||
Str("path", path).
|
||||
Str("user", user.name).
|
||||
Int64("id", user.id).
|
||||
Str("display", display).
|
||||
Msg("running sudo")
|
||||
|
||||
args := argsForSudo(user, opts)
|
||||
|
||||
args = append(args,
|
||||
"DISPLAY="+display,
|
||||
// DBUS_SESSION_BUS_ADDRESS sets the location of the user login session bus.
|
||||
// Required by the libayatana-appindicator3 library to display a tray icon
|
||||
// on the desktop session.
|
||||
@@ -44,11 +69,14 @@ func run(path string, opts eopts) error {
|
||||
// (because it's already part of the user).
|
||||
fmt.Sprintf("DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/%d/bus", user.id),
|
||||
// Append the packaged libayatana-appindicator3 libraries path to LD_LIBRARY_PATH.
|
||||
//
|
||||
// Fleet Desktop doesn't use libayatana-appindicator3 since 1.18.3, but we need to
|
||||
// keep this to support older versions of Fleet Desktop.
|
||||
fmt.Sprintf("LD_LIBRARY_PATH=%s:%s", filepath.Dir(path), os.ExpandEnv("$LD_LIBRARY_PATH")),
|
||||
path,
|
||||
)
|
||||
|
||||
cmd := exec.Command("sudo", arg...)
|
||||
cmd := exec.Command("sudo", args...)
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
log.Printf("cmd=%s", cmd.String())
|
||||
@@ -64,6 +92,18 @@ type user struct {
|
||||
id int64
|
||||
}
|
||||
|
||||
func argsForSudo(u *user, opts eopts) []string {
|
||||
// -H: "[...] to set HOME environment to what's specified in the target's user password database entry."
|
||||
// -i: needed to run the command with the user's context, from `man sudo`:
|
||||
// "The command is run with an environment similar to the one a user would receive at log in"
|
||||
// -u: "[..]Run the command as a user other than the default target user (usually root)."
|
||||
args := []string{"-i", "-u", u.name, "-H"}
|
||||
for _, nv := range opts.env {
|
||||
args = append(args, fmt.Sprintf("%s=%s", nv[0], nv[1]))
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// getLoginUID returns the name and uid of the first login user
|
||||
// as reported by the `users' command.
|
||||
//
|
||||
@@ -120,3 +160,30 @@ func parseIDOutput(s string) (int64, error) {
|
||||
}
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
var whoLineRegexp = regexp.MustCompile(`(\w+)\s+(:\d+)\s+`)
|
||||
|
||||
func getUserDisplay(user string, opts eopts) (string, error) {
|
||||
cmd := exec.Command("who")
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("run 'who' to get user display: %w", err)
|
||||
}
|
||||
return parseWhoOutputForDisplay(&stdout, user)
|
||||
}
|
||||
|
||||
func parseWhoOutputForDisplay(output io.Reader, user string) (string, error) {
|
||||
scanner := bufio.NewScanner(output)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
matches := whoLineRegexp.FindStringSubmatch(line)
|
||||
if len(matches) > 1 && matches[1] == user {
|
||||
return matches[2], nil
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return "", fmt.Errorf("scanner error: %w", err)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user