27a52d2986
New 'Almacenamiento' page with:
- Auto-detection of rsync, mergerfs, snapraid binaries and mergerfs mount
- Configurable pool settings (source/dest, macOS cleanup, rsync flags)
- Mergerfs mover with dry-run preview and live SSE output streaming
- SnapRAID diff/sync/scrub/check with live SSE output
- Async job system (1 concurrent job) with SSE streaming
- Job history table
Backend:
- internal/storage/ package with capabilities, mergerfs, snapraid, jobs
- storage_config and storage_jobs DB tables (migration 0008)
- GET/PUT /api/storage/config, GET /api/storage/capabilities
- POST/GET /api/storage/jobs, GET /api/storage/jobs/{id}/stream
- Storage operations disabled when NASCTL_EXEC_SYSTEM=false
Closes #new-feature
71 lines
1.5 KiB
Go
71 lines
1.5 KiB
Go
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type SnapraidConfig struct {
|
|
Content string
|
|
DataDirs []string
|
|
ParityDir string
|
|
ScrubPlan int
|
|
}
|
|
|
|
func BuildSnapraidArgs(kind string, cfg SnapraidConfig) ([]string, error) {
|
|
if cfg.Content == "" {
|
|
return nil, fmt.Errorf("snapraid content file is required")
|
|
}
|
|
args := []string{"snapraid", "-c", cfg.Content}
|
|
switch kind {
|
|
case "snapraid_diff":
|
|
args = append(args, "diff")
|
|
case "snapraid_sync":
|
|
args = append(args, "sync")
|
|
case "snapraid_scrub":
|
|
args = append(args, "scrub", "-p", strconv.Itoa(cfg.ScrubPlan))
|
|
case "snapraid_check":
|
|
args = append(args, "check")
|
|
default:
|
|
return nil, fmt.Errorf("unknown snapraid kind: %s", kind)
|
|
}
|
|
return args, nil
|
|
}
|
|
|
|
func ValidateSnapraidContent(content string) error {
|
|
if content == "" {
|
|
return fmt.Errorf("snapraid content file is required")
|
|
}
|
|
info, err := os.Stat(content)
|
|
if err != nil {
|
|
return fmt.Errorf("snapraid content %s: %w", content, err)
|
|
}
|
|
if info.IsDir() {
|
|
return fmt.Errorf("snapraid content %s: is a directory, not a file", content)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateScrubPlan(plan int) error {
|
|
if plan < 1 || plan > 99 {
|
|
return fmt.Errorf("scrub plan must be between 1 and 99, got %d", plan)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ParseSnapraidDataDirs(raw string) []string {
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
var dirs []string
|
|
for _, d := range strings.Split(raw, ",") {
|
|
d = strings.TrimSpace(d)
|
|
if d != "" {
|
|
dirs = append(dirs, d)
|
|
}
|
|
}
|
|
return dirs
|
|
}
|