diff --git a/crontabd/go.mod b/crontabd/go.mod index efb52ba..944300e 100644 --- a/crontabd/go.mod +++ b/crontabd/go.mod @@ -5,6 +5,7 @@ go 1.23.6 require ( git.site.quack-lab.dev/dave/cylogger v1.2.3 github.com/fsnotify/fsnotify v1.9.0 + github.com/robfig/cron/v3 v3.0.1 ) require golang.org/x/sys v0.13.0 // indirect diff --git a/crontabd/go.sum b/crontabd/go.sum index f8b0ad2..a2ceeaa 100644 --- a/crontabd/go.sum +++ b/crontabd/go.sum @@ -2,5 +2,7 @@ git.site.quack-lab.dev/dave/cylogger v1.2.3 h1:g6fwgrd3HvGsxljvKbjcFaMynTO2AZFWj git.site.quack-lab.dev/dave/cylogger v1.2.3/go.mod h1:sf16Zs5ZRncn0ySgwxRJShkge1M10CM2RAUkKn8Bel8= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/crontabd/main.go b/crontabd/main.go index a1e5ab6..2239980 100644 --- a/crontabd/main.go +++ b/crontabd/main.go @@ -4,9 +4,12 @@ import ( "flag" "fmt" "os" + "os/exec" "path/filepath" + "strings" "github.com/fsnotify/fsnotify" + "github.com/robfig/cron/v3" logger "git.site.quack-lab.dev/dave/cylogger" ) @@ -17,6 +20,7 @@ type CronJob struct { } var crontabFile string +var cronRunner *cron.Cron func main() { flag.Parse() @@ -36,6 +40,20 @@ func main() { } logger.Info("Crontab file resolved as: %s", crontabFile) + // Initialize cron runner with seconds disabled (standard cron format) + cronRunner = cron.New() + defer cronRunner.Stop() + + // Load initial cron jobs + if err := loadCronJobs(); err != nil { + logger.Error("Failed to load initial cron jobs: %v", err) + return + } + + // Start the cron scheduler + cronRunner.Start() + logger.Info("Cron scheduler started") + events, err := watchCrontabFile(crontabFile) if err != nil { logger.Error("Failed to watch crontab file: %v", err) @@ -43,6 +61,73 @@ func main() { } for event := range events { logger.Info("Crontab file updated: %s", event.Name) + // Reload cron jobs when file changes + if err := loadCronJobs(); err != nil { + logger.Error("Failed to reload cron jobs: %v", err) + } + } +} + +func loadCronJobs() error { + // Read the crontab file + content, err := os.ReadFile(crontabFile) + if err != nil { + return fmt.Errorf("failed to read crontab file: %v", err) + } + + // Stop current jobs + cronRunner.Stop() + // Create new cron runner + cronRunner = cron.New() + + // Parse each line + lines := strings.Split(string(content), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + // Split into cron expression and command + fields := strings.Fields(line) + if len(fields) < 6 { + logger.Error("Invalid cron line: %s", line) + continue + } + + cronExpr := strings.Join(fields[0:5], " ") + cmdStr := strings.Join(fields[5:], " ") + + job := CronJob{ + Cron: cronExpr, + Command: cmdStr, + } + + // Add the job to cron + _, err := cronRunner.AddFunc(job.Cron, createJobFunc(job)) + if err != nil { + logger.Error("Failed to add cron job %+v: %v", job, err) + continue + } + logger.Info("Added cron job: %s -> %s", job.Cron, job.Command) + } + + // Start the cron scheduler + cronRunner.Start() + return nil +} + +func createJobFunc(job CronJob) func() { + return func() { + cmd := exec.Command("sh", "-c", job.Command) + output, err := cmd.CombinedOutput() + if err != nil { + logger.Error("Failed to execute command '%s': %v", job.Command, err) + logger.Error("Command output: %s", string(output)) + return + } + logger.Info("Successfully executed command '%s'", job.Command) + logger.Info("Command output: %s", string(output)) } }