Skip to content

Commit

Permalink
feat: Add cron mode for scheduled certificate renewals
Browse files Browse the repository at this point in the history
  • Loading branch information
yarlson committed Oct 28, 2024
1 parent 6359518 commit 8d9bc6c
Showing 1 changed file with 74 additions and 3 deletions.
77 changes: 74 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"

"log"
"os"
"os/signal"
Expand All @@ -28,6 +27,8 @@ type Config struct {
CertDir string
Issue bool
Renew bool
Cron bool
Time string
}

func parseFlags() (*Config, error) {
Expand All @@ -38,10 +39,12 @@ func parseFlags() (*Config, error) {
pflag.StringVarP(&cfg.CertDir, "cert-dir", "c", defaultCertDir, "Directory to store certificates")
pflag.BoolVarP(&cfg.Issue, "issue", "i", false, "Issue a new certificate")
pflag.BoolVarP(&cfg.Renew, "renew", "r", false, "Renew the existing certificate")
pflag.BoolVar(&cfg.Cron, "cron", false, "Run in cron mode for daily renewals")
pflag.StringVar(&cfg.Time, "time", "02:00", "Time for daily renewal in HH:mm format (24-hour or 12-hour with AM/PM)")

pflag.Usage = func() {
_, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
_, _ = fmt.Fprintf(os.Stderr, " %s -d example.com -e user@example.com [-c /path/to/certs] [-i] [-r]\n\n", os.Args[0])
_, _ = fmt.Fprintf(os.Stderr, " %s -d example.com -e user@example.com [-c /path/to/certs] [-i] [-r] [--cron] [--time HH:mm]\n\n", os.Args[0])
_, _ = fmt.Fprintf(os.Stderr, "Options:\n")
pflag.PrintDefaults()
}
Expand All @@ -56,9 +59,32 @@ func parseFlags() (*Config, error) {
return nil, errors.New("cannot specify both --issue and --renew")
}

if cfg.Cron {
if _, err := parseTime(cfg.Time); err != nil {
return nil, fmt.Errorf("invalid time format: %w", err)
}
}

return cfg, nil
}

func parseTime(timeStr string) (time.Time, error) {
formats := []string{
"15:04",
"3:04PM",
"3:04 PM",
}

for _, format := range formats {
t, err := time.Parse(format, timeStr)
if err == nil {
return t, nil
}
}

return time.Time{}, fmt.Errorf("unable to parse time: %s", timeStr)
}

func run(cfg *Config) error {
if err := os.MkdirAll(cfg.CertDir, 0700); err != nil {
return fmt.Errorf("create cert directory: %w", err)
Expand All @@ -85,7 +111,6 @@ func run(cfg *Config) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Set up signal handling for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

Expand All @@ -112,12 +137,58 @@ func run(cfg *Config) error {
return nil
}

func runCron(cfg *Config) error {
renewalTime, err := parseTime(cfg.Time)
if err != nil {
return fmt.Errorf("parse renewal time: %w", err)
}

log.Printf("Starting cron mode. Daily renewal scheduled at %s", renewalTime.Format("15:04"))

ticker := time.NewTicker(getNextTickDuration(renewalTime))
defer ticker.Stop()

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

for {
select {
case <-ticker.C:
log.Println("Running scheduled renewal")
if err := run(cfg); err != nil {
log.Printf("Error during scheduled renewal: %v", err)
}
ticker.Reset(24 * time.Hour)
case <-sigChan:
log.Println("Received interrupt signal. Shutting down...")
return nil
}
}
}

func getNextTickDuration(t time.Time) time.Duration {
now := time.Now()
next := time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), 0, 0, now.Location())
if next.Before(now) {
next = next.Add(24 * time.Hour)
}

return next.Sub(now)
}

func main() {
cfg, err := parseFlags()
if err != nil {
log.Fatalf("Error parsing flags: %v", err)
}

if cfg.Cron {
if err := runCron(cfg); err != nil {
log.Fatalf("Error in cron mode: %v", err)
}
return
}

if err := run(cfg); err != nil {
log.Fatalf("Error: %v", err)
}
Expand Down

0 comments on commit 8d9bc6c

Please sign in to comment.