From b1a47685f93426a2e801b81f21c9c8591d2561f9 Mon Sep 17 00:00:00 2001 From: Othman Hendy Suseo Date: Sat, 20 Dec 2025 19:29:41 +0000 Subject: [PATCH] run as superuser --- internal/httpapp/api_handlers.go | 12 ++--- internal/services/iscsi.go | 18 +++---- internal/services/nfs.go | 16 +++--- internal/zfs/service.go | 91 +++++++------------------------- 4 files changed, 42 insertions(+), 95 deletions(-) diff --git a/internal/httpapp/api_handlers.go b/internal/httpapp/api_handlers.go index 22267d0..34be9d1 100644 --- a/internal/httpapp/api_handlers.go +++ b/internal/httpapp/api_handlers.go @@ -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 diff --git a/internal/services/iscsi.go b/internal/services/iscsi.go index edc1524..bbad764 100644 --- a/internal/services/iscsi.go +++ b/internal/services/iscsi.go @@ -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 - 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 } diff --git a/internal/services/nfs.go b/internal/services/nfs.go index 0e5f1e8..c009658 100644 --- a/internal/services/nfs.go +++ b/internal/services/nfs.go @@ -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 { diff --git a/internal/zfs/service.go b/internal/zfs/service.go index af88bc1..94dcfb4 100644 --- a/internal/zfs/service.go +++ b/internal/zfs/service.go @@ -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