package platform import ( "context" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/backupper-unraid/backupper/internal/model" ) type CredentialGetter interface{ Get(string) (string, error) } type MountManager struct { RuntimeDir string Secrets CredentialGetter } type MountedRepository struct { Repository model.Repository Mounted bool CredentialFile string } func (m *MountManager) Prepare(ctx context.Context, repo model.Repository) (MountedRepository, error) { result := MountedRepository{Repository: repo} if repo.Type != model.RepositorySMB && repo.Type != model.RepositoryNFS { return result, nil } if repo.Mount == nil || !repo.Mount.Managed { if _, err := os.Stat(repo.Location); err != nil { return result, fmt.Errorf("connectivity: external mount unavailable: %w", err) } return result, nil } point := filepath.Clean(repo.Mount.MountPoint) if !strings.HasPrefix(point, "/mnt/remotes/") && !strings.HasPrefix(point, "/mnt/disks/") { return result, errors.New("validation: managed mountPoint must be below /mnt/remotes or /mnt/disks") } if err := os.MkdirAll(point, 0700); err != nil { return result, err } mountType := string(repo.Type) if repo.Type == model.RepositorySMB { mountType = "cifs" } args := []string{"-t", mountType, repo.Mount.Remote, point} if repo.Type == model.RepositorySMB { options := []string{"nosuid", "nodev"} if repo.CredentialRef != "" { credential, err := m.Secrets.Get(repo.CredentialRef) if err != nil { return result, fmt.Errorf("authentication: load mount credential: %w", err) } dir := filepath.Join(m.RuntimeDir, "secrets") if err := os.MkdirAll(dir, 0700); err != nil { return result, err } f, err := os.CreateTemp(dir, "smb-credentials-*") if err != nil { return result, err } result.CredentialFile = f.Name() if err := f.Chmod(0600); err != nil { f.Close() return result, err } if _, err := f.WriteString(credential); err != nil { f.Close() return result, err } if err := f.Close(); err != nil { return result, err } options = append(options, "credentials="+result.CredentialFile) } args = append(args, "-o", strings.Join(options, ",")) } else { args = append(args, "-o", "nosuid,nodev") } if err := exec.CommandContext(ctx, "mount", args...).Run(); err != nil { m.Cleanup(context.Background(), result) return result, fmt.Errorf("connectivity: mount repository: %w", err) } result.Mounted = true result.Repository.Location = point return result, nil } func (m *MountManager) Cleanup(ctx context.Context, mounted MountedRepository) error { if mounted.CredentialFile != "" { defer os.Remove(mounted.CredentialFile) } if !mounted.Mounted { return nil } return exec.CommandContext(ctx, "umount", mounted.Repository.Location).Run() }