create user needs sudo

This commit is contained in:
2026-03-19 15:42:41 +00:00
parent cd7d2fcf61
commit 0b34f8cc3e
3 changed files with 50 additions and 8 deletions

57
app.go
View File

@@ -17,7 +17,6 @@ var ErrCancelled = errors.New("cancelled")
type UserManager interface {
UserExists(name string) bool
CreateUser(name string) error
}
type CommandRunner interface {
@@ -33,11 +32,6 @@ func (RealUserManager) UserExists(name string) bool {
return err == nil
}
func (RealUserManager) CreateUser(name string) error {
runner := OSCommandRunner{}
return runner.Run("useradd", "-M", "-s", "/usr/sbin/nologin", name)
}
type OSCommandRunner struct{}
func (OSCommandRunner) Run(name string, args ...string) error {
@@ -348,8 +342,8 @@ func (a *App) ensureUsers(users []string) error {
continue
}
if err := a.users.CreateUser(name); err != nil {
return fmt.Errorf("create user %s: %w", name, err)
if err := a.createUser(name); err != nil {
return err
}
a.printf("Created local user %s\n", name)
@@ -359,6 +353,42 @@ func (a *App) ensureUsers(users []string) error {
return nil
}
func (a *App) createUser(name string) error {
args := []string{"-M", "-s", "/usr/sbin/nologin", name}
if err := a.runner.Run("useradd", args...); err == nil {
return nil
} else if shouldOfferSudoRetry(err) {
a.println("")
a.println("Creating a Linux user usually needs administrator permission.")
a.println("I can try again using sudo so you can enter your admin password.")
a.flush()
canUseSudo := false
if a.lookPath != nil {
_, sudoErr := a.lookPath("sudo")
canUseSudo = sudoErr == nil
}
if !canUseSudo {
return fmt.Errorf("create user %s: %w", name, err)
}
retry, promptErr := a.confirm("Retry user creation with sudo", true)
if promptErr != nil {
return promptErr
}
if !retry {
return fmt.Errorf("create user %s: %w", name, err)
}
if sudoErr := a.runner.Run("sudo", append([]string{"useradd"}, args...)...); sudoErr != nil {
return fmt.Errorf("create user %s with sudo: %w", name, sudoErr)
}
return nil
} else {
return fmt.Errorf("create user %s: %w", name, err)
}
}
func (a *App) prompt(label string) (string, error) {
a.printf("%s: ", label)
a.flush()
@@ -437,3 +467,14 @@ func copyFile(src, dst string) error {
}
return os.WriteFile(dst, data, 0o644)
}
func shouldOfferSudoRetry(err error) bool {
if os.Geteuid() == 0 {
return false
}
text := strings.ToLower(err.Error())
return strings.Contains(text, "permission denied") ||
strings.Contains(text, "operation not permitted") ||
strings.Contains(text, "exit status")
}