// Package sysuser runs commands as the current process or as another user, // and answers questions about local user accounts. package sysuser import ( "fmt" "os" "os/exec" "os/user" "path/filepath" ) // IsRoot reports whether the process runs with euid 0. func IsRoot() bool { return os.Geteuid() == 0 } // Current returns the username of the effective user. func Current() string { u, err := user.LookupId(fmt.Sprint(os.Geteuid())) if err != nil { return "" } return u.Username } // IsSelf reports whether name is the effective user (or empty). func IsSelf(name string) bool { return name == "" || name == Current() } // HomeDir returns the home directory of the given user. func HomeDir(name string) (string, error) { u, err := user.Lookup(name) if err != nil { return "", fmt.Errorf("looking up user %s: %w", name, err) } return u.HomeDir, nil } // UID returns the numeric uid of the given user as a string. func UID(name string) (string, error) { u, err := user.Lookup(name) if err != nil { return "", fmt.Errorf("looking up user %s: %w", name, err) } return u.Uid, nil } // Command builds a command that runs bin as the given user. // An empty user, or the current user, runs it directly. Otherwise runuser is // used (requires root) with XDG_RUNTIME_DIR pointed at the user's runtime dir, // which rootless podman and systemctl --user need. func Command(name string, bin string, args ...string) (*exec.Cmd, error) { if IsSelf(name) { return exec.Command(bin, args...), nil } if !IsRoot() { return nil, fmt.Errorf("running as user %s requires root", name) } uid, err := UID(name) if err != nil { return nil, err } full := append([]string{"-u", name, "--", "env", "XDG_RUNTIME_DIR=/run/user/" + uid, "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/" + uid + "/bus", bin}, args...) return exec.Command("runuser", full...), nil } // LookPath finds bin in PATH, then in the user's ~/.local/bin. // Returns "" if not found. func LookPath(name string, bin string) string { if p, err := exec.LookPath(bin); err == nil { return p } if name == "" { name = Current() } home, err := HomeDir(name) if err != nil { return "" } p := filepath.Join(home, ".local", "bin", bin) if fi, err := os.Stat(p); err == nil && !fi.IsDir() { return p } return "" } // LingerEnabled reports whether systemd lingering is enabled for the user, // so user units start at boot without a login session. func LingerEnabled(name string) bool { _, err := os.Stat(filepath.Join("/var/lib/systemd/linger", name)) return err == nil }