package main import ( "flag" "os" "path/filepath" "strings" "time" logger "git.site.quack-lab.dev/dave/cylogger" "github.com/bmatcuk/doublestar/v4" ) type Config struct { WorkDir string ScanSeconds int Patterns []string } func getenv(key, def string) string { if v, ok := os.LookupEnv(key); ok { return v } return def } func loadConfig() Config { flagWDir := flag.String("wd", "", "working directory") flagScan := flag.Int("scan", 60, "scan interval in seconds") logger.InitFlag() flag.Parse() workdir := filepath.Clean(*flagWDir) if pfx := strings.TrimSpace(getenv("PATH_PREFIX", "")); pfx != "" { workdir = filepath.Join(workdir, pfx) } workdir = filepath.ToSlash(workdir) // Gather patterns: args + FORBIDDEN env (comma-separated) patterns := []string{} patterns = append(patterns, flag.Args()...) if env := strings.TrimSpace(getenv("FORBIDDEN", "")); env != "" { for _, p := range strings.Split(env, ",") { p = strings.TrimSpace(p) if p != "" { patterns = append(patterns, p) } } } // Normalize patterns to slash style; patterns are relative to workdir for i := range patterns { patterns[i] = filepath.ToSlash(strings.TrimSpace(patterns[i])) } logger.Info("Config:") logger.Info(" WorkDir: %s", workdir) logger.Info(" ScanSeconds: %d", *flagScan) logger.Info(" Patterns: %v", patterns) return Config{ WorkDir: workdir, ScanSeconds: *flagScan, Patterns: patterns, } } func deleteMatches(cfg Config) { log := logger.Default.WithPrefix("deleteMatches") // Ensure workdir exists if cfg.WorkDir == "" { log.Error("WorkDir is empty") return } if _, err := os.Stat(cfg.WorkDir); err != nil { log.Error("WorkDir not accessible %s: %v", cfg.WorkDir, err) return } for _, pat := range cfg.Patterns { if pat == "" { continue } // Use doublestar.Glob against the workdir FS; it returns relative paths matches, err := doublestar.Glob(os.DirFS(cfg.WorkDir), pat) if err != nil { log.Error("glob %q: %v", pat, err) continue } if len(matches) == 0 { log.Debug("No matches for pattern %q", pat) continue } for _, rel := range matches { full := filepath.Join(cfg.WorkDir, rel) full = filepath.Clean(full) info, err := os.Stat(full) if err != nil { log.Warning("stat %s: %v", full, err) continue } if info.IsDir() { log.Info("Removing directory %s", full) } else { log.Info("Removing file %s", full) } if err := os.RemoveAll(full); err != nil { log.Error("remove %s: %v", full, err) continue } } } } func main() { cfg := loadConfig() logger.Info("Starting forbidden cleaner") // Run immediately, then on interval deleteMatches(cfg) ticker := time.NewTicker(time.Duration(cfg.ScanSeconds) * time.Second) defer ticker.Stop() for { ts := <-ticker.C logger.Info("Tick %d", ts.UnixMilli()) deleteMatches(cfg) } }