This commit is contained in:
@@ -1822,7 +1822,7 @@ func (a *App) syncNFSExportsFromOS() error {
|
||||
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()
|
||||
if err != nil {
|
||||
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
|
||||
func (a *App) syncSMBSharesFromOS() error {
|
||||
configPath := "/etc/samba/smb.conf"
|
||||
cmd := exec.Command("sudo", "-n", "cat", configPath)
|
||||
cmd := exec.Command("cat", configPath)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// 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
|
||||
os.MkdirAll("/tmp/.targetcli", 0755)
|
||||
os.MkdirAll("/tmp/targetcli-run", 0755)
|
||||
// Use sudo to run as root, then set environment variables in the command
|
||||
cmd := exec.Command("sh", "-c", "sudo -n sh -c 'TARGETCLI_HOME=/tmp/.targetcli TARGETCLI_LOCK_DIR=/tmp/targetcli-run targetcli /iscsi ls'")
|
||||
// Service runs as root, no need for sudo
|
||||
cmd := exec.Command("sh", "-c", "TARGETCLI_HOME=/tmp/.targetcli TARGETCLI_LOCK_DIR=/tmp/targetcli-run targetcli /iscsi ls")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
// 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
|
||||
func (a *App) syncLUNsFromOS(iqn, targetID string, targetType models.ISCSITargetType) error {
|
||||
// Get LUNs for this target
|
||||
// Use sudo to run as root, then set environment variables in the command
|
||||
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'")
|
||||
// Service runs as root, no need for sudo
|
||||
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()
|
||||
if err != nil {
|
||||
// No LUNs or can't read - that's okay, log for debugging
|
||||
|
||||
@@ -73,7 +73,7 @@ func (s *ISCSIService) ApplyConfiguration(targets []models.ISCSITarget) error {
|
||||
func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
|
||||
// Use targetcli to create target
|
||||
// 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()
|
||||
if err != nil {
|
||||
// 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)
|
||||
// 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 {
|
||||
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)
|
||||
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 {
|
||||
fmt.Printf("warning: failed to set generate_node_acls=1: %v (output: %s)\n", err, string(output))
|
||||
} else {
|
||||
@@ -102,20 +102,20 @@ func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Portal might already exist, which is OK
|
||||
// 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()
|
||||
if err2 != nil || len(strings.TrimSpace(string(output))) == 0 {
|
||||
// No portal exists, try to create with specific IP
|
||||
// Get system IP
|
||||
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 {
|
||||
// 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 {
|
||||
// Log but don't fail - portal might already exist
|
||||
fmt.Printf("warning: failed to create portal: %v", err4)
|
||||
@@ -125,7 +125,7 @@ func (s *ISCSIService) createTarget(target models.ISCSITarget) error {
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
cmd = exec.Command("sudo", "-n", s.targetcliPath, "saveconfig")
|
||||
cmd = exec.Command(s.targetcliPath, "saveconfig")
|
||||
cmd.Run() // Ignore errors
|
||||
|
||||
return nil
|
||||
@@ -141,7 +141,7 @@ func (s *ISCSIService) configureACLs(target models.ISCSITarget) error {
|
||||
return fmt.Errorf("set generate_node_acls: %w", err)
|
||||
}
|
||||
// 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
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
|
||||
finalTmpPath := s.exportsPath + ".atlas.tmp"
|
||||
|
||||
// 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)
|
||||
var teeStderr bytes.Buffer
|
||||
teeCmd.Stderr = &teeStderr
|
||||
@@ -73,13 +73,13 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Backup existing exports using sudo
|
||||
backupPath := s.exportsPath + ".backup"
|
||||
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 {
|
||||
// Non-fatal, log but continue
|
||||
// Try direct copy as fallback
|
||||
@@ -89,7 +89,7 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
|
||||
|
||||
// Atomically replace exports file using sudo
|
||||
// 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{}
|
||||
cpCmd.Stderr = &cpStderr
|
||||
if err := cpCmd.Run(); err != nil {
|
||||
@@ -100,7 +100,7 @@ func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Reload NFS exports with error recovery
|
||||
@@ -163,7 +163,7 @@ func (s *NFSService) generateExports(exports []models.NFSExport) (string, error)
|
||||
func (s *NFSService) reloadExports() error {
|
||||
// Use exportfs -ra to reload all exports (requires root)
|
||||
// Try with sudo first
|
||||
cmd := exec.Command("sudo", "-n", "exportfs", "-ra")
|
||||
cmd := exec.Command("exportfs", "-ra")
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
@@ -239,7 +239,7 @@ func (s *NFSService) applyZFSShareNFS(exports []models.NFSExport) error {
|
||||
for _, export := range exports {
|
||||
if !export.Enabled {
|
||||
// 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 {
|
||||
// Log but continue - might not have permission or dataset doesn't exist
|
||||
continue
|
||||
@@ -310,7 +310,7 @@ func (s *NFSService) applyZFSShareNFS(exports []models.NFSExport) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
|
||||
@@ -122,55 +122,14 @@ func translateZFSError(err error, operation, name string) error {
|
||||
// execCommand executes a shell command and returns output
|
||||
// For ZFS operations that require elevated privileges, it uses sudo
|
||||
func (s *Service) execCommand(name string, args ...string) (string, error) {
|
||||
// Commands that require root privileges
|
||||
privilegedCommands := []string{"zpool", "zfs"}
|
||||
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...)
|
||||
}
|
||||
// Service runs as root, no need for sudo
|
||||
cmd := exec.Command(name, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
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 {
|
||||
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
|
||||
// Try multiple methods to ensure directory is created
|
||||
methods := []struct {
|
||||
name string
|
||||
cmd *exec.Cmd
|
||||
}{
|
||||
{"sudo mkdir", exec.Command("sudo", "-n", "mkdir", "-p", path)},
|
||||
{"direct mkdir", exec.Command("mkdir", "-p", path)},
|
||||
}
|
||||
// Service runs as root, no need for sudo
|
||||
cmd := exec.Command("mkdir", "-p", path)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
var lastErr error
|
||||
for _, method := range methods {
|
||||
var stderr bytes.Buffer
|
||||
method.cmd.Stderr = &stderr
|
||||
|
||||
if err := method.cmd.Run(); err == nil {
|
||||
// Success - verify directory was created and set permissions
|
||||
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)
|
||||
if err := cmd.Run(); err == nil {
|
||||
// Success - verify directory was created and set permissions
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Set proper permissions (755)
|
||||
chmodCmd := exec.Command("chmod", "755", path)
|
||||
_ = chmodCmd.Run() // Ignore errors, permissions might already be correct
|
||||
return nil
|
||||
}
|
||||
} 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 {
|
||||
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
|
||||
@@ -1398,7 +1345,7 @@ func (s *Service) getDiskHealth(diskName string) map[string]string {
|
||||
}
|
||||
|
||||
// Get overall health status
|
||||
cmd := exec.Command("sudo", "-n", smartctlPath, "-H", diskPath)
|
||||
cmd := exec.Command(smartctlPath, "-H", diskPath)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// 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
|
||||
cmd = exec.Command("sudo", "-n", smartctlPath, "-A", diskPath)
|
||||
cmd = exec.Command(smartctlPath, "-A", diskPath)
|
||||
attrOutput, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Return what we have
|
||||
|
||||
Reference in New Issue
Block a user