run as superuser
Some checks failed
CI / test-build (push) Has been cancelled

This commit is contained in:
2025-12-20 19:29:41 +00:00
parent 6202ef8e83
commit b1a47685f9
4 changed files with 42 additions and 95 deletions

View File

@@ -1822,7 +1822,7 @@ func (a *App) syncNFSExportsFromOS() error {
zfsPath = path zfsPath = path
} }
cmd := exec.Command("sudo", "-n", zfsPath, "get", "-H", "-o", "value", "sharenfs", ds.Name) cmd := exec.Command(zfsPath, "get", "-H", "-o", "value", "sharenfs", ds.Name)
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
continue // Skip if can't get property continue // Skip if can't get property
@@ -1918,7 +1918,7 @@ func (a *App) syncNFSExportsFromOS() error {
// syncSMBSharesFromOS syncs SMB shares from /etc/samba/smb.conf to the store // syncSMBSharesFromOS syncs SMB shares from /etc/samba/smb.conf to the store
func (a *App) syncSMBSharesFromOS() error { func (a *App) syncSMBSharesFromOS() error {
configPath := "/etc/samba/smb.conf" configPath := "/etc/samba/smb.conf"
cmd := exec.Command("sudo", "-n", "cat", configPath) cmd := exec.Command("cat", configPath)
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
// If can't read smb.conf, that's okay - might not exist yet // If can't read smb.conf, that's okay - might not exist yet
@@ -2054,8 +2054,8 @@ func (a *App) syncISCSITargetsFromOS() error {
// Create the directories first if they don't exist // Create the directories first if they don't exist
os.MkdirAll("/tmp/.targetcli", 0755) os.MkdirAll("/tmp/.targetcli", 0755)
os.MkdirAll("/tmp/targetcli-run", 0755) os.MkdirAll("/tmp/targetcli-run", 0755)
// Use sudo to run as root, then set environment variables in the command // Service runs as root, no need for sudo
cmd := exec.Command("sh", "-c", "sudo -n sh -c 'TARGETCLI_HOME=/tmp/.targetcli TARGETCLI_LOCK_DIR=/tmp/targetcli-run targetcli /iscsi ls'") cmd := exec.Command("sh", "-c", "TARGETCLI_HOME=/tmp/.targetcli TARGETCLI_LOCK_DIR=/tmp/targetcli-run targetcli /iscsi ls")
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
// Log the error but don't fail - targetcli might not be configured // Log the error but don't fail - targetcli might not be configured
@@ -2121,8 +2121,8 @@ func (a *App) syncISCSITargetsFromOS() error {
// syncLUNsFromOS syncs LUNs for a specific target from targetcli // syncLUNsFromOS syncs LUNs for a specific target from targetcli
func (a *App) syncLUNsFromOS(iqn, targetID string, targetType models.ISCSITargetType) error { func (a *App) syncLUNsFromOS(iqn, targetID string, targetType models.ISCSITargetType) error {
// Get LUNs for this target // Get LUNs for this target
// Use sudo to run as root, then set environment variables in the command // Service runs as root, no need for sudo
cmd := exec.Command("sh", "-c", "sudo -n sh -c 'TARGETCLI_HOME=/tmp/.targetcli TARGETCLI_LOCK_DIR=/tmp/targetcli-run targetcli /iscsi/"+iqn+"/tpg1/luns ls'") cmd := exec.Command("sh", "-c", "TARGETCLI_HOME=/tmp/.targetcli TARGETCLI_LOCK_DIR=/tmp/targetcli-run targetcli /iscsi/"+iqn+"/tpg1/luns ls")
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
// No LUNs or can't read - that's okay, log for debugging // No LUNs or can't read - that's okay, log for debugging

View File

@@ -73,7 +73,7 @@ func (s *ISCSIService) ApplyConfiguration(targets []models.ISCSITarget) error {
func (s *ISCSIService) createTarget(target models.ISCSITarget) error { func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
// Use targetcli to create target // Use targetcli to create target
// Format: targetcli /iscsi create <IQN> // Format: targetcli /iscsi create <IQN>
cmd := exec.Command("sudo", "-n", s.targetcliPath, "/iscsi", "create", target.IQN) cmd := exec.Command(s.targetcliPath, "/iscsi", "create", target.IQN)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
// Target might already exist, which is OK // Target might already exist, which is OK
@@ -88,13 +88,13 @@ func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
// Enable TPG1 (Target Portal Group 1) // Enable TPG1 (Target Portal Group 1)
// Disable authentication // Disable authentication
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1", "set", "attribute", "authentication=0") cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1", "set", "attribute", "authentication=0")
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
fmt.Printf("warning: failed to set authentication=0: %v (output: %s)\n", err, string(output)) fmt.Printf("warning: failed to set authentication=0: %v (output: %s)\n", err, string(output))
} }
// Enable generate_node_acls (allow all initiators if no ACLs specified) // Enable generate_node_acls (allow all initiators if no ACLs specified)
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1", "set", "attribute", "generate_node_acls=1") cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1", "set", "attribute", "generate_node_acls=1")
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
fmt.Printf("warning: failed to set generate_node_acls=1: %v (output: %s)\n", err, string(output)) fmt.Printf("warning: failed to set generate_node_acls=1: %v (output: %s)\n", err, string(output))
} else { } else {
@@ -102,20 +102,20 @@ func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
} }
// Create portal if not exists (listen on all interfaces, port 3260) // Create portal if not exists (listen on all interfaces, port 3260)
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "create") cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "create")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
// Portal might already exist, which is OK // Portal might already exist, which is OK
// Check if portal exists // Check if portal exists
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "ls") cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "ls")
output, err2 := cmd.Output() output, err2 := cmd.Output()
if err2 != nil || len(strings.TrimSpace(string(output))) == 0 { if err2 != nil || len(strings.TrimSpace(string(output))) == 0 {
// No portal exists, try to create with specific IP // No portal exists, try to create with specific IP
// Get system IP // Get system IP
systemIP, _ := s.getSystemIP() systemIP, _ := s.getSystemIP()
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "create", systemIP) cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "create", systemIP)
if err3 := cmd.Run(); err3 != nil { if err3 := cmd.Run(); err3 != nil {
// Try with 0.0.0.0 (all interfaces) // Try with 0.0.0.0 (all interfaces)
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "create", "0.0.0.0") cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1/portals", "create", "0.0.0.0")
if err4 := cmd.Run(); err4 != nil { if err4 := cmd.Run(); err4 != nil {
// Log but don't fail - portal might already exist // Log but don't fail - portal might already exist
fmt.Printf("warning: failed to create portal: %v", err4) fmt.Printf("warning: failed to create portal: %v", err4)
@@ -125,7 +125,7 @@ func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
} }
// Save configuration // Save configuration
cmd = exec.Command("sudo", "-n", s.targetcliPath, "saveconfig") cmd = exec.Command(s.targetcliPath, "saveconfig")
cmd.Run() // Ignore errors cmd.Run() // Ignore errors
return nil return nil
@@ -141,7 +141,7 @@ func (s *ISCSIService) configureACLs(target models.ISCSITarget) error {
return fmt.Errorf("set generate_node_acls: %w", err) return fmt.Errorf("set generate_node_acls: %w", err)
} }
// Disable authentication for open access // Disable authentication for open access
cmd = exec.Command("sudo", "-n", s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1", "set", "attribute", "authentication=0") cmd = exec.Command(s.targetcliPath, "/iscsi/"+target.IQN+"/tpg1", "set", "attribute", "authentication=0")
cmd.Run() // Ignore errors cmd.Run() // Ignore errors
return nil return nil
} }

View File

@@ -61,7 +61,7 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
finalTmpPath := s.exportsPath + ".atlas.tmp" finalTmpPath := s.exportsPath + ".atlas.tmp"
// Use sudo tee to write directly to /etc (requires root permissions) // Use sudo tee to write directly to /etc (requires root permissions)
teeCmd := exec.Command("sudo", "-n", "tee", finalTmpPath) teeCmd := exec.Command("tee", finalTmpPath)
teeCmd.Stdin = strings.NewReader(config) teeCmd.Stdin = strings.NewReader(config)
var teeStderr bytes.Buffer var teeStderr bytes.Buffer
teeCmd.Stderr = &teeStderr teeCmd.Stderr = &teeStderr
@@ -73,13 +73,13 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
} }
// Set proper permissions on temp file // Set proper permissions on temp file
chmodCmd := exec.Command("sudo", "-n", "chmod", "644", finalTmpPath) chmodCmd := exec.Command("chmod", "644", finalTmpPath)
_ = chmodCmd.Run() // Ignore errors, might already have correct permissions _ = chmodCmd.Run() // Ignore errors, might already have correct permissions
// Backup existing exports using sudo // Backup existing exports using sudo
backupPath := s.exportsPath + ".backup" backupPath := s.exportsPath + ".backup"
if _, err := os.Stat(s.exportsPath); err == nil { if _, err := os.Stat(s.exportsPath); err == nil {
cpCmd := exec.Command("sudo", "-n", "cp", s.exportsPath, backupPath) cpCmd := exec.Command("cp", s.exportsPath, backupPath)
if err := cpCmd.Run(); err != nil { if err := cpCmd.Run(); err != nil {
// Non-fatal, log but continue // Non-fatal, log but continue
// Try direct copy as fallback // Try direct copy as fallback
@@ -89,7 +89,7 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
// Atomically replace exports file using sudo // Atomically replace exports file using sudo
// Use cp + rm instead of mv for better cross-device compatibility // Use cp + rm instead of mv for better cross-device compatibility
cpCmd := exec.Command("sudo", "-n", "cp", finalTmpPath, s.exportsPath) cpCmd := exec.Command("cp", finalTmpPath, s.exportsPath)
cpStderr := bytes.Buffer{} cpStderr := bytes.Buffer{}
cpCmd.Stderr = &cpStderr cpCmd.Stderr = &cpStderr
if err := cpCmd.Run(); err != nil { if err := cpCmd.Run(); err != nil {
@@ -100,7 +100,7 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
} }
// Remove temp file after successful copy // Remove temp file after successful copy
rmCmd := exec.Command("sudo", "-n", "rm", "-f", finalTmpPath) rmCmd := exec.Command("rm", "-f", finalTmpPath)
_ = rmCmd.Run() // Ignore errors, file might not exist _ = rmCmd.Run() // Ignore errors, file might not exist
// Reload NFS exports with error recovery // Reload NFS exports with error recovery
@@ -163,7 +163,7 @@ func (s *NFSService) generateExports(exports []models.NFSExport) (string, error)
func (s *NFSService) reloadExports() error { func (s *NFSService) reloadExports() error {
// Use exportfs -ra to reload all exports (requires root) // Use exportfs -ra to reload all exports (requires root)
// Try with sudo first // Try with sudo first
cmd := exec.Command("sudo", "-n", "exportfs", "-ra") cmd := exec.Command("exportfs", "-ra")
var stderr bytes.Buffer var stderr bytes.Buffer
cmd.Stderr = &stderr cmd.Stderr = &stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
@@ -239,7 +239,7 @@ func (s *NFSService) applyZFSShareNFS(exports []models.NFSExport) error {
for _, export := range exports { for _, export := range exports {
if !export.Enabled { if !export.Enabled {
// Disable sharenfs for disabled exports // Disable sharenfs for disabled exports
cmd := exec.Command("sudo", "-n", zfsPath, "set", "sharenfs=off", export.Dataset) cmd := exec.Command(zfsPath, "set", "sharenfs=off", export.Dataset)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
// Log but continue - might not have permission or dataset doesn't exist // Log but continue - might not have permission or dataset doesn't exist
continue continue
@@ -310,7 +310,7 @@ func (s *NFSService) applyZFSShareNFS(exports []models.NFSExport) error {
} }
// Set sharenfs property using sudo (atlas user has permission via sudoers) // Set sharenfs property using sudo (atlas user has permission via sudoers)
cmd := exec.Command("sudo", "-n", zfsPath, "set", fmt.Sprintf("sharenfs=%s", sharenfsValue.String()), export.Dataset) cmd := exec.Command(zfsPath, "set", fmt.Sprintf("sharenfs=%s", sharenfsValue.String()), export.Dataset)
var stderr bytes.Buffer var stderr bytes.Buffer
cmd.Stderr = &stderr cmd.Stderr = &stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {

View File

@@ -122,55 +122,14 @@ func translateZFSError(err error, operation, name string) error {
// execCommand executes a shell command and returns output // execCommand executes a shell command and returns output
// For ZFS operations that require elevated privileges, it uses sudo // For ZFS operations that require elevated privileges, it uses sudo
func (s *Service) execCommand(name string, args ...string) (string, error) { func (s *Service) execCommand(name string, args ...string) (string, error) {
// Commands that require root privileges // Service runs as root, no need for sudo
privilegedCommands := []string{"zpool", "zfs"} cmd := exec.Command(name, args...)
useSudo := false
for _, cmd := range privilegedCommands {
if strings.Contains(name, cmd) {
useSudo = true
break
}
}
var cmd *exec.Cmd
if useSudo {
// Use sudo -n (non-interactive) for privileged commands
// This prevents password prompts and will fail if sudoers is not configured
sudoArgs := append([]string{"-n", name}, args...)
cmd = exec.Command("sudo", sudoArgs...)
} else {
cmd = exec.Command(name, args...)
}
var stdout, stderr bytes.Buffer var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout cmd.Stdout = &stdout
cmd.Stderr = &stderr cmd.Stderr = &stderr
err := cmd.Run() err := cmd.Run()
if err != nil && useSudo {
// Log that sudo failed
log.Printf("sudo command failed, trying direct execution: %s %v (error: %v, stderr: %s)", name, args, err, stderr.String())
// If sudo failed, try running the command directly
// (user might already have permissions or be root)
directCmd := exec.Command(name, args...)
var directStdout, directStderr bytes.Buffer
directCmd.Stdout = &directStdout
directCmd.Stderr = &directStderr
directErr := directCmd.Run()
if directErr == nil {
// Direct execution succeeded, return that result
log.Printf("direct command execution succeeded (without sudo)")
return strings.TrimSpace(directStdout.String()), nil
}
// Both sudo and direct failed, return the direct error (usually cleaner)
log.Printf("both sudo and direct execution failed - sudo error: %v, direct error: %v", err, directErr)
log.Printf("sudo stderr: %s, direct stderr: %s", stderr.String(), directStderr.String())
// Return the direct error (usually has the actual ZFS error message)
return "", fmt.Errorf("%s: %s", name, strings.TrimSpace(directStderr.String()))
}
if err != nil { if err != nil {
log.Printf("command execution failed: %s %v (error: %v, stderr: %s)", name, args, err, stderr.String()) log.Printf("command execution failed: %s %v (error: %v, stderr: %s)", name, args, err, stderr.String())
@@ -650,41 +609,29 @@ func (s *Service) createMountpointWithSudo(path string) error {
} }
} }
// Use sudo to create the directory with proper permissions // Service runs as root, no need for sudo
// Try multiple methods to ensure directory is created cmd := exec.Command("mkdir", "-p", path)
methods := []struct { var stderr bytes.Buffer
name string cmd.Stderr = &stderr
cmd *exec.Cmd
}{
{"sudo mkdir", exec.Command("sudo", "-n", "mkdir", "-p", path)},
{"direct mkdir", exec.Command("mkdir", "-p", path)},
}
var lastErr error if err := cmd.Run(); err == nil {
for _, method := range methods { // Success - verify directory was created and set permissions
var stderr bytes.Buffer if _, err := os.Stat(path); err == nil {
method.cmd.Stderr = &stderr // Set proper permissions (755)
chmodCmd := exec.Command("chmod", "755", path)
if err := method.cmd.Run(); err == nil { _ = chmodCmd.Run() // Ignore errors, permissions might already be correct
// Success - verify directory was created and set permissions return nil
if _, err := os.Stat(path); err == nil {
// Set proper permissions (755) and ownership if needed
chmodCmd := exec.Command("sudo", "-n", "chmod", "755", path)
_ = chmodCmd.Run() // Ignore errors, permissions might already be correct
return nil
}
} else {
lastErr = fmt.Errorf("%s failed: %v: %s", method.name, err, stderr.String())
log.Printf("warning: %s failed: %v", method.name, lastErr)
} }
} else {
log.Printf("warning: mkdir failed: %v: %s", err, stderr.String())
} }
// All methods failed, but check if directory exists anyway (might have been created by ZFS or another process) // Check if directory exists anyway (might have been created by ZFS or another process)
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
return nil return nil
} }
return fmt.Errorf("all methods failed to create mountpoint %s: %v", path, lastErr) return fmt.Errorf("failed to create mountpoint directory %s", path)
} }
// DestroyPool destroys a ZFS pool // DestroyPool destroys a ZFS pool
@@ -1398,7 +1345,7 @@ func (s *Service) getDiskHealth(diskName string) map[string]string {
} }
// Get overall health status // Get overall health status
cmd := exec.Command("sudo", "-n", smartctlPath, "-H", diskPath) cmd := exec.Command(smartctlPath, "-H", diskPath)
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
// Check if it's because SMART is not supported (common for virtual disks) // Check if it's because SMART is not supported (common for virtual disks)
@@ -1443,7 +1390,7 @@ func (s *Service) getDiskHealth(diskName string) map[string]string {
} }
// Get detailed SMART attributes // Get detailed SMART attributes
cmd = exec.Command("sudo", "-n", smartctlPath, "-A", diskPath) cmd = exec.Command(smartctlPath, "-A", diskPath)
attrOutput, err := cmd.Output() attrOutput, err := cmd.Output()
if err != nil { if err != nil {
// Return what we have // Return what we have