diff --git a/README.md b/README.md index 207de33..40c99b3 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The program: - Prompts to add, edit, or delete a share. - Checks `valid users` entries against local accounts. - Offers to create missing local accounts with `useradd -M -s /usr/sbin/nologin `. +- 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. If you create a local account for a Samba-authenticated share, you may still need to add the Samba password separately: diff --git a/app.go b/app.go index e5460dc..f1738e5 100644 --- a/app.go +++ b/app.go @@ -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") +} diff --git a/samba-configer b/samba-configer index 7dbed9f..3f3b188 100755 Binary files a/samba-configer and b/samba-configer differ