refactor: replace CLI flags with env-based config, add flexible duration

parsing, and use ScanInterval ticker to schedule deletions
This commit is contained in:
2025-08-07 11:19:01 +02:00
parent 51903397f0
commit d105184a1d

105
main.go
View File

@@ -1,9 +1,10 @@
package main package main
import ( import (
"flag"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv"
"strings" "strings"
"time" "time"
@@ -13,10 +14,16 @@ import (
type Config struct { type Config struct {
WorkDir string WorkDir string
ScanSeconds int
Patterns []string Patterns []string
ScanInterval time.Duration
} }
var (
timeUnits = map[string]int64{"ms": 1, "s": 1000, "m": 60_000, "h": 3_600_000, "d": 86_400_000, "M": 2_592_000_000, "y": 31_536_000_000}
valueRegex = regexp.MustCompile(`\d+`)
unitRegex = regexp.MustCompile(`[a-zA-Z]+`)
)
func getenv(key, def string) string { func getenv(key, def string) string {
if v, ok := os.LookupEnv(key); ok { if v, ok := os.LookupEnv(key); ok {
return v return v
@@ -24,51 +31,77 @@ func getenv(key, def string) string {
return def return def
} }
func loadConfig() Config { func parseDurationMS(expr string) int64 {
flagWDir := flag.String("wd", "", "working directory") expr = strings.TrimSpace(expr)
flagScan := flag.Int("scan", 60, "scan interval in seconds") if expr == "" {
logger.InitFlag() return 0
flag.Parse() }
var total int64
workdir := filepath.Clean(*flagWDir) for _, p := range strings.Split(expr, "_") {
if pfx := strings.TrimSpace(getenv("PATH_PREFIX", "")); pfx != "" { p = strings.TrimSpace(p)
workdir = filepath.Join(workdir, pfx) if p == "" {
continue
}
v := valueRegex.FindString(p)
u := unitRegex.FindString(p)
if v == "" || u == "" {
logger.Warning("Invalid duration part: %q", p)
continue
}
unit, ok := timeUnits[u]
if !ok {
logger.Warning("Invalid duration unit: %q", u)
continue
}
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
logger.Warning("Invalid duration value: %q: %v", v, err)
continue
}
total += n * unit
}
return total
} }
workdir = filepath.ToSlash(workdir)
// Gather patterns: args + FORBIDDEN env (comma-separated) func loadConfig() Config {
patterns := []string{} // logger.InitFlag() is optional if you don't use CLI flags to set log level,
patterns = append(patterns, flag.Args()...) // but it is safe to call to honor standard logger flags if provided.
logger.InitFlag()
workdir := filepath.ToSlash(strings.TrimSpace(getenv("WORKDIR", "/tmp")))
if pfx := strings.TrimSpace(getenv("PATH_PREFIX", "")); pfx != "" {
workdir = filepath.ToSlash(filepath.Join(workdir, pfx))
}
// Patterns from FORBIDDEN (comma-separated). Relative to WorkDir.
var patterns []string
if env := strings.TrimSpace(getenv("FORBIDDEN", "")); env != "" { if env := strings.TrimSpace(getenv("FORBIDDEN", "")); env != "" {
for _, p := range strings.Split(env, ",") { for _, p := range strings.Split(env, ",") {
p = strings.TrimSpace(p) p = strings.TrimSpace(p)
if p != "" { if p != "" {
patterns = append(patterns, p) patterns = append(patterns, filepath.ToSlash(p))
} }
} }
} }
// Normalize patterns to slash style; patterns are relative to workdir interval := time.Duration(parseDurationMS(getenv("SCAN_INTERVAL", "60s"))) * time.Millisecond
for i := range patterns {
patterns[i] = filepath.ToSlash(strings.TrimSpace(patterns[i]))
}
logger.Info("Config:") logger.Info("Config:")
logger.Info(" WorkDir: %s", workdir) logger.Info(" WORKDIR: %s", workdir)
logger.Info(" ScanSeconds: %d", *flagScan) logger.Info(" PATH_PREFIX: %s", getenv("PATH_PREFIX", ""))
logger.Info(" Patterns: %v", patterns) logger.Info(" FORBIDDEN: %v", patterns)
logger.Info(" SCAN_INTERVAL(ms): %d", interval.Milliseconds())
return Config{ return Config{
WorkDir: workdir, WorkDir: workdir,
ScanSeconds: *flagScan,
Patterns: patterns, Patterns: patterns,
ScanInterval: interval,
} }
} }
func deleteMatches(cfg Config) { func deleteMatches(cfg Config) {
log := logger.Default.WithPrefix("deleteMatches") log := logger.Default.WithPrefix("deleteMatches")
// Ensure workdir exists
if cfg.WorkDir == "" { if cfg.WorkDir == "" {
log.Error("WorkDir is empty") log.Error("WorkDir is empty")
return return
@@ -82,7 +115,6 @@ func deleteMatches(cfg Config) {
if pat == "" { if pat == "" {
continue continue
} }
// Use doublestar.Glob against the workdir FS; it returns relative paths
matches, err := doublestar.Glob(os.DirFS(cfg.WorkDir), pat) matches, err := doublestar.Glob(os.DirFS(cfg.WorkDir), pat)
if err != nil { if err != nil {
log.Error("glob %q: %v", pat, err) log.Error("glob %q: %v", pat, err)
@@ -94,9 +126,7 @@ func deleteMatches(cfg Config) {
} }
for _, rel := range matches { for _, rel := range matches {
full := filepath.Join(cfg.WorkDir, rel) full := filepath.Clean(filepath.Join(cfg.WorkDir, rel))
full = filepath.Clean(full)
info, err := os.Stat(full) info, err := os.Stat(full)
if err != nil { if err != nil {
log.Warning("stat %s: %v", full, err) log.Warning("stat %s: %v", full, err)
@@ -117,18 +147,23 @@ func deleteMatches(cfg Config) {
} }
} }
func doRun(cfg Config) {
deleteMatches(cfg)
}
func main() { func main() {
cfg := loadConfig() cfg := loadConfig()
logger.Info("Starting forbidden cleaner") logger.Info("Starting forbidden cleaner")
doRun(cfg)
// Run immediately, then on interval t := time.NewTicker(cfg.ScanInterval)
deleteMatches(cfg) defer t.Stop()
ticker := time.NewTicker(time.Duration(cfg.ScanSeconds) * time.Second)
defer ticker.Stop()
for { for {
ts := <-ticker.C select {
case ts := <-t.C:
logger.Info("Tick %d", ts.UnixMilli()) logger.Info("Tick %d", ts.UnixMilli())
deleteMatches(cfg) doRun(cfg)
}
} }
} }