149 lines
3.7 KiB
Go
149 lines
3.7 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/models"
|
|
)
|
|
|
|
// NFSService manages NFS service integration
|
|
type NFSService struct {
|
|
mu sync.RWMutex
|
|
exportsPath string
|
|
}
|
|
|
|
// NewNFSService creates a new NFS service manager
|
|
func NewNFSService() *NFSService {
|
|
return &NFSService{
|
|
exportsPath: "/etc/exports",
|
|
}
|
|
}
|
|
|
|
// ApplyConfiguration generates and applies NFS exports configuration
|
|
func (s *NFSService) ApplyConfiguration(exports []models.NFSExport) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
config, err := s.generateExports(exports)
|
|
if err != nil {
|
|
return fmt.Errorf("generate exports: %w", err)
|
|
}
|
|
|
|
// Write configuration to a temporary file first
|
|
tmpPath := s.exportsPath + ".atlas.tmp"
|
|
if err := os.WriteFile(tmpPath, []byte(config), 0644); err != nil {
|
|
return fmt.Errorf("write exports: %w", err)
|
|
}
|
|
|
|
// Backup existing exports
|
|
backupPath := s.exportsPath + ".backup"
|
|
if _, err := os.Stat(s.exportsPath); err == nil {
|
|
if err := exec.Command("cp", s.exportsPath, backupPath).Run(); err != nil {
|
|
// Non-fatal, log but continue
|
|
}
|
|
}
|
|
|
|
// Atomically replace exports file
|
|
if err := os.Rename(tmpPath, s.exportsPath); err != nil {
|
|
return fmt.Errorf("replace exports: %w", err)
|
|
}
|
|
|
|
// Reload NFS exports with error recovery
|
|
reloadErr := s.reloadExports()
|
|
if reloadErr != nil {
|
|
// Try to restore backup on failure
|
|
if _, err2 := os.Stat(backupPath); err2 == nil {
|
|
if restoreErr := os.Rename(backupPath, s.exportsPath); restoreErr != nil {
|
|
return fmt.Errorf("reload failed and backup restore failed: reload=%v, restore=%v", reloadErr, restoreErr)
|
|
}
|
|
}
|
|
return fmt.Errorf("reload exports: %w", reloadErr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateExports generates /etc/exports format from NFS exports
|
|
func (s *NFSService) generateExports(exports []models.NFSExport) (string, error) {
|
|
var b strings.Builder
|
|
|
|
for _, export := range exports {
|
|
if !export.Enabled {
|
|
continue
|
|
}
|
|
|
|
// Build export options
|
|
var options []string
|
|
if export.ReadOnly {
|
|
options = append(options, "ro")
|
|
} else {
|
|
options = append(options, "rw")
|
|
}
|
|
|
|
if export.RootSquash {
|
|
options = append(options, "root_squash")
|
|
} else {
|
|
options = append(options, "no_root_squash")
|
|
}
|
|
|
|
options = append(options, "sync", "subtree_check")
|
|
|
|
// Format: path client1(options) client2(options)
|
|
optStr := "(" + strings.Join(options, ",") + ")"
|
|
|
|
if len(export.Clients) == 0 {
|
|
// Default to all clients if none specified
|
|
b.WriteString(fmt.Sprintf("%s *%s\n", export.Path, optStr))
|
|
} else {
|
|
for _, client := range export.Clients {
|
|
b.WriteString(fmt.Sprintf("%s %s%s\n", export.Path, client, optStr))
|
|
}
|
|
}
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
// reloadExports reloads NFS exports
|
|
func (s *NFSService) reloadExports() error {
|
|
// Use exportfs -ra to reload all exports
|
|
cmd := exec.Command("exportfs", "-ra")
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("exportfs failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateConfiguration validates NFS exports syntax
|
|
func (s *NFSService) ValidateConfiguration(exports string) error {
|
|
// Use exportfs -v to validate (dry-run)
|
|
cmd := exec.Command("exportfs", "-v")
|
|
cmd.Stdin = strings.NewReader(exports)
|
|
|
|
// Note: exportfs doesn't have a direct validation mode
|
|
// We'll rely on the reload to catch errors
|
|
return nil
|
|
}
|
|
|
|
// GetStatus returns the status of NFS service
|
|
func (s *NFSService) GetStatus() (bool, error) {
|
|
// Check if nfs-server is running
|
|
cmd := exec.Command("systemctl", "is-active", "nfs-server")
|
|
if err := cmd.Run(); err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
// Fallback: check process
|
|
cmd = exec.Command("pgrep", "-x", "nfsd")
|
|
if err := cmd.Run(); err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|