171 lines
4.3 KiB
Go
171 lines
4.3 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/models"
|
|
)
|
|
|
|
// SMBService manages Samba service integration
|
|
type SMBService struct {
|
|
mu sync.RWMutex
|
|
configPath string
|
|
smbConfPath string
|
|
smbctlPath string
|
|
}
|
|
|
|
// NewSMBService creates a new SMB service manager
|
|
func NewSMBService() *SMBService {
|
|
return &SMBService{
|
|
configPath: "/etc/samba/smb.conf",
|
|
smbctlPath: "smbcontrol",
|
|
}
|
|
}
|
|
|
|
// ApplyConfiguration generates and applies SMB configuration
|
|
func (s *SMBService) ApplyConfiguration(shares []models.SMBShare) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
config, err := s.generateConfig(shares)
|
|
if err != nil {
|
|
return fmt.Errorf("generate config: %w", err)
|
|
}
|
|
|
|
// Write configuration to a temporary file first
|
|
tmpPath := s.configPath + ".atlas.tmp"
|
|
if err := os.WriteFile(tmpPath, []byte(config), 0644); err != nil {
|
|
return fmt.Errorf("write config: %w", err)
|
|
}
|
|
|
|
// Backup existing config
|
|
backupPath := s.configPath + ".backup"
|
|
if _, err := os.Stat(s.configPath); err == nil {
|
|
if err := exec.Command("cp", s.configPath, backupPath).Run(); err != nil {
|
|
// Non-fatal, log but continue
|
|
}
|
|
}
|
|
|
|
// Atomically replace config
|
|
if err := os.Rename(tmpPath, s.configPath); err != nil {
|
|
return fmt.Errorf("replace config: %w", err)
|
|
}
|
|
|
|
// Reload Samba service with retry
|
|
reloadErr := s.reloadService()
|
|
if reloadErr != nil {
|
|
// Try to restore backup on failure
|
|
if _, err2 := os.Stat(backupPath); err2 == nil {
|
|
if restoreErr := os.Rename(backupPath, s.configPath); restoreErr != nil {
|
|
return fmt.Errorf("reload failed and backup restore failed: reload=%v, restore=%v", reloadErr, restoreErr)
|
|
}
|
|
}
|
|
return fmt.Errorf("reload service: %w", reloadErr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateConfig generates Samba configuration from shares
|
|
func (s *SMBService) generateConfig(shares []models.SMBShare) (string, error) {
|
|
var b strings.Builder
|
|
|
|
// Global section
|
|
b.WriteString("[global]\n")
|
|
b.WriteString(" workgroup = WORKGROUP\n")
|
|
b.WriteString(" server string = AtlasOS Storage Server\n")
|
|
b.WriteString(" security = user\n")
|
|
b.WriteString(" map to guest = Bad User\n")
|
|
b.WriteString(" dns proxy = no\n")
|
|
b.WriteString("\n")
|
|
|
|
// Share sections
|
|
for _, share := range shares {
|
|
if !share.Enabled {
|
|
continue
|
|
}
|
|
|
|
b.WriteString(fmt.Sprintf("[%s]\n", share.Name))
|
|
b.WriteString(fmt.Sprintf(" path = %s\n", share.Path))
|
|
if share.Description != "" {
|
|
b.WriteString(fmt.Sprintf(" comment = %s\n", share.Description))
|
|
}
|
|
if share.ReadOnly {
|
|
b.WriteString(" read only = yes\n")
|
|
} else {
|
|
b.WriteString(" read only = no\n")
|
|
b.WriteString(" writable = yes\n")
|
|
}
|
|
if share.GuestOK {
|
|
b.WriteString(" guest ok = yes\n")
|
|
b.WriteString(" public = yes\n")
|
|
} else {
|
|
b.WriteString(" guest ok = no\n")
|
|
}
|
|
if len(share.ValidUsers) > 0 {
|
|
b.WriteString(fmt.Sprintf(" valid users = %s\n", strings.Join(share.ValidUsers, ", ")))
|
|
}
|
|
b.WriteString(" browseable = yes\n")
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
// reloadService reloads Samba configuration
|
|
func (s *SMBService) reloadService() error {
|
|
// Try smbcontrol first (doesn't require root for reload)
|
|
cmd := exec.Command(s.smbctlPath, "all", "reload-config")
|
|
if err := cmd.Run(); err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Fallback to systemctl if available
|
|
cmd = exec.Command("systemctl", "reload", "smbd")
|
|
if err := cmd.Run(); err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Try service command
|
|
cmd = exec.Command("service", "smbd", "reload")
|
|
if err := cmd.Run(); err == nil {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("unable to reload Samba service")
|
|
}
|
|
|
|
// ValidateConfiguration validates SMB configuration syntax
|
|
func (s *SMBService) ValidateConfiguration(config string) error {
|
|
// Use testparm to validate configuration
|
|
cmd := exec.Command("testparm", "-s")
|
|
cmd.Stdin = strings.NewReader(config)
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("configuration validation failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetStatus returns the status of Samba service
|
|
func (s *SMBService) GetStatus() (bool, error) {
|
|
// Check if smbd is running
|
|
cmd := exec.Command("systemctl", "is-active", "smbd")
|
|
if err := cmd.Run(); err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
// Fallback: check process
|
|
cmd = exec.Command("pgrep", "-x", "smbd")
|
|
if err := cmd.Run(); err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|