This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user