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

View File

@@ -24,6 +24,7 @@ The program:
- Prompts to add, edit, or delete a share. - Prompts to add, edit, or delete a share.
- Checks `valid users` entries against local accounts. - Checks `valid users` entries against local accounts.
- Offers to create missing local accounts with `useradd -M -s /usr/sbin/nologin <user>`. - Offers to create missing local accounts with `useradd -M -s /usr/sbin/nologin <user>`.
- If user creation fails because admin rights are needed, explains the issue and offers to retry with `sudo`.
- Writes a timestamped backup before saving changes. - Writes a timestamped backup before saving changes.
If you create a local account for a Samba-authenticated share, you may still need to add the Samba password separately: If you create a local account for a Samba-authenticated share, you may still need to add the Samba password separately:

57
app.go
View File

@@ -17,7 +17,6 @@ var ErrCancelled = errors.New("cancelled")
type UserManager interface { type UserManager interface {
UserExists(name string) bool UserExists(name string) bool
CreateUser(name string) error
} }
type CommandRunner interface { type CommandRunner interface {
@@ -33,11 +32,6 @@ func (RealUserManager) UserExists(name string) bool {
return err == nil return err == nil
} }
func (RealUserManager) CreateUser(name string) error {
runner := OSCommandRunner{}
return runner.Run("useradd", "-M", "-s", "/usr/sbin/nologin", name)
}
type OSCommandRunner struct{} type OSCommandRunner struct{}
func (OSCommandRunner) Run(name string, args ...string) error { func (OSCommandRunner) Run(name string, args ...string) error {
@@ -348,8 +342,8 @@ func (a *App) ensureUsers(users []string) error {
continue continue
} }
if err := a.users.CreateUser(name); err != nil { if err := a.createUser(name); err != nil {
return fmt.Errorf("create user %s: %w", name, err) return err
} }
a.printf("Created local user %s\n", name) a.printf("Created local user %s\n", name)
@@ -359,6 +353,42 @@ func (a *App) ensureUsers(users []string) error {
return nil 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) { func (a *App) prompt(label string) (string, error) {
a.printf("%s: ", label) a.printf("%s: ", label)
a.flush() a.flush()
@@ -437,3 +467,14 @@ func copyFile(src, dst string) error {
} }
return os.WriteFile(dst, data, 0o644) 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")
}

Binary file not shown.