satoru/cmd/satoru/input.go

107 lines
2.7 KiB
Go

package main
import (
"errors"
"regexp"
"strconv"
"strings"
"satoru/internal/store"
"satoru/internal/webui"
)
var usernamePattern = regexp.MustCompile(`^[a-z0-9._-]{3,32}$`)
func normalizeUsername(v string) string {
return strings.ToLower(strings.TrimSpace(v))
}
func validUsername(v string) bool {
return usernamePattern.MatchString(v)
}
func defaultWorkflowStages() []webui.WorkflowStage {
return []webui.WorkflowStage{
{Title: "Pull from Edge over SSH", Description: "Satoru connects to Linux edge hosts using local keys and pulls approved paths."},
{Title: "Stage on Backup Server", Description: "Pulled data lands on the backup host first, keeping edge systems isolated from B2."},
{Title: "Restic to B2", Description: "Restic runs centrally on this Satoru instance and uploads snapshots to Backblaze B2."},
{Title: "Audit and Recover", Description: "Each site records run output/status for operational visibility before full job history is added."},
}
}
func parsePathList(raw string) []string {
split := strings.FieldsFunc(raw, func(r rune) bool {
return r == '\n' || r == ',' || r == ';'
})
out := make([]string, 0, len(split))
for _, item := range split {
item = strings.TrimSpace(item)
if item == "" {
continue
}
out = append(out, item)
}
return out
}
func parseLineList(raw string) []string {
split := strings.Split(raw, "\n")
seen := map[string]struct{}{}
out := make([]string, 0, len(split))
for _, item := range split {
item = strings.TrimSpace(item)
if item == "" {
continue
}
if _, ok := seen[item]; ok {
continue
}
seen[item] = struct{}{}
out = append(out, item)
}
return out
}
func buildTargets(directoryPaths, sqlitePaths []string) []store.SiteTarget {
seen := map[string]struct{}{}
out := make([]store.SiteTarget, 0, len(directoryPaths)+len(sqlitePaths))
for _, p := range directoryPaths {
addTarget(&out, seen, store.SiteTarget{Path: p, Mode: "directory"})
}
for _, p := range sqlitePaths {
addTarget(&out, seen, store.SiteTarget{Path: p, Mode: "sqlite_dump"})
}
return out
}
func addTarget(out *[]store.SiteTarget, seen map[string]struct{}, t store.SiteTarget) {
key := t.Mode + "\x00" + t.Path
if _, ok := seen[key]; ok {
return
}
seen[key] = struct{}{}
*out = append(*out, t)
}
func targetsAreValid(targets []store.SiteTarget) bool {
for _, t := range targets {
if t.Path == "" || !strings.HasPrefix(t.Path, "/") {
return false
}
}
return true
}
func parsePort(raw string) (int, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return 22, nil
}
port, err := strconv.Atoi(raw)
if err != nil || port < 1 || port > 65535 {
return 0, errors.New("invalid port")
}
return port, nil
}