diff --git a/README.md b/README.md index 40c99b3..04ce50e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ The program: - 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`. +- If saving the Samba config or its backup needs admin rights, 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 f1738e5..36888c1 100644 --- a/app.go +++ b/app.go @@ -238,12 +238,20 @@ func (a *App) deleteShare(doc *Document) error { func (a *App) writeConfig(doc *Document) error { backup := fmt.Sprintf("%s.bak.%s", a.configPath, time.Now().UTC().Format("20060102T150405Z")) + serialized := doc.Serialize() + if err := copyFile(a.configPath, backup); err != nil { - return fmt.Errorf("create backup %s: %w", backup, err) + if shouldOfferPrivilegeRetry(err) { + return a.writeConfigWithPrivilegeRetry(serialized, backup) + } + return friendlyWriteError("create backup", backup, err) } - if err := os.WriteFile(a.configPath, []byte(doc.Serialize()), 0o644); err != nil { - return fmt.Errorf("write config: %w", err) + if err := os.WriteFile(a.configPath, []byte(serialized), 0o644); err != nil { + if shouldOfferPrivilegeRetry(err) { + return a.writeConfigWithPrivilegeRetry(serialized, backup) + } + return friendlyWriteError("write config", a.configPath, err) } a.printf("Config written to %s\n", a.configPath) @@ -357,7 +365,7 @@ 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) { + } else if shouldOfferPrivilegeRetry(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.") @@ -468,7 +476,7 @@ func copyFile(src, dst string) error { return os.WriteFile(dst, data, 0o644) } -func shouldOfferSudoRetry(err error) bool { +func shouldOfferPrivilegeRetry(err error) bool { if os.Geteuid() == 0 { return false } @@ -478,3 +486,65 @@ func shouldOfferSudoRetry(err error) bool { strings.Contains(text, "operation not permitted") || strings.Contains(text, "exit status") } + +func (a *App) writeConfigWithPrivilegeRetry(serialized, backup string) error { + a.println("") + a.println("Saving Samba settings 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 friendlyWriteError("save Samba settings", a.configPath, os.ErrPermission) + } + + retry, err := a.confirm("Retry saving with sudo", true) + if err != nil { + return err + } + if !retry { + return friendlyWriteError("save Samba settings", a.configPath, os.ErrPermission) + } + + tempPath, err := a.writeTempConfig(serialized) + if err != nil { + return fmt.Errorf("prepare config for sudo save: %w", err) + } + defer os.Remove(tempPath) + + if err := a.runner.Run("sudo", "cp", a.configPath, backup); err != nil { + return friendlyWriteError("create backup", backup, err) + } + + if err := a.runner.Run("sudo", "install", "-m", "644", tempPath, a.configPath); err != nil { + return friendlyWriteError("write config", a.configPath, err) + } + + a.printf("Config written to %s\n", a.configPath) + a.printf("Backup saved to %s\n", backup) + return nil +} + +func (a *App) writeTempConfig(serialized string) (string, error) { + file, err := os.CreateTemp("", "samba-configer-*.conf") + if err != nil { + return "", err + } + defer file.Close() + + if _, err := file.WriteString(serialized); err != nil { + return "", err + } + return file.Name(), nil +} + +func friendlyWriteError(action, path string, err error) error { + if errors.Is(err, os.ErrPermission) || strings.Contains(strings.ToLower(err.Error()), "permission denied") { + return fmt.Errorf("%s %s: administrator permission is needed", action, path) + } + return fmt.Errorf("%s %s: %w", action, path, err) +} diff --git a/samba-configer b/samba-configer index 3f3b188..1410828 100755 Binary files a/samba-configer and b/samba-configer differ