wrtie config needs sudo usually

This commit is contained in:
2026-03-19 15:46:22 +00:00
parent 0b34f8cc3e
commit 1b8807010d
3 changed files with 76 additions and 5 deletions

View File

@@ -25,6 +25,7 @@ The program:
- 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`. - 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. - 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:

80
app.go
View File

@@ -238,12 +238,20 @@ func (a *App) deleteShare(doc *Document) error {
func (a *App) writeConfig(doc *Document) error { func (a *App) writeConfig(doc *Document) error {
backup := fmt.Sprintf("%s.bak.%s", a.configPath, time.Now().UTC().Format("20060102T150405Z")) backup := fmt.Sprintf("%s.bak.%s", a.configPath, time.Now().UTC().Format("20060102T150405Z"))
serialized := doc.Serialize()
if err := copyFile(a.configPath, backup); err != nil { 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 { if err := os.WriteFile(a.configPath, []byte(serialized), 0o644); err != nil {
return fmt.Errorf("write config: %w", err) if shouldOfferPrivilegeRetry(err) {
return a.writeConfigWithPrivilegeRetry(serialized, backup)
}
return friendlyWriteError("write config", a.configPath, err)
} }
a.printf("Config written to %s\n", a.configPath) 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} args := []string{"-M", "-s", "/usr/sbin/nologin", name}
if err := a.runner.Run("useradd", args...); err == nil { if err := a.runner.Run("useradd", args...); err == nil {
return nil return nil
} else if shouldOfferSudoRetry(err) { } else if shouldOfferPrivilegeRetry(err) {
a.println("") a.println("")
a.println("Creating a Linux user usually needs administrator permission.") 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.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) return os.WriteFile(dst, data, 0o644)
} }
func shouldOfferSudoRetry(err error) bool { func shouldOfferPrivilegeRetry(err error) bool {
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
return false return false
} }
@@ -478,3 +486,65 @@ func shouldOfferSudoRetry(err error) bool {
strings.Contains(text, "operation not permitted") || strings.Contains(text, "operation not permitted") ||
strings.Contains(text, "exit status") 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)
}

Binary file not shown.