diff --git a/config.go b/config.go index 64888f7..cbd1ac8 100644 --- a/config.go +++ b/config.go @@ -23,7 +23,7 @@ const ( var defaultRoot = path.Join(os.TempDir(), "git-mirror", "src") // WatchConfig polls the config file every interval and reloads if modified -func WatchConfig(ctx context.Context, path string, interval time.Duration, onChange func(*mirror.RepoPoolConfig)) { +func WatchConfig(ctx context.Context, path string, watchConfig bool, interval time.Duration, onChange func(*mirror.RepoPoolConfig)) { var lastModTime time.Time for { @@ -47,6 +47,10 @@ func WatchConfig(ctx context.Context, path string, interval time.Duration, onCha } } + if !watchConfig { + return + } + t := time.NewTimer(interval) select { case <-t.C: diff --git a/go.mod b/go.mod index 86ccedc..0bb2844 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/google/go-cmp v0.7.0 github.com/prometheus/client_golang v1.21.1 github.com/sasha-s/go-deadlock v0.3.5 - github.com/urfave/cli/v3 v3.0.0-beta1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 0b60c85..1acef4d 100644 --- a/go.sum +++ b/go.sum @@ -15,20 +15,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs= github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= -github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= @@ -39,14 +34,8 @@ github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= -github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index a1cf80c..89fd0d3 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,16 @@ package main import ( "context" + "flag" "fmt" "log/slog" "os" "os/signal" + "strconv" "strings" "syscall" "time" - "github.com/urfave/cli/v3" "github.com/utilitywarehouse/git-mirror/pkg/mirror" ) @@ -25,27 +26,6 @@ var ( "warn": slog.LevelWarn, "error": slog.LevelError, } - - flags = []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Sources: cli.EnvVars("GIT_MIRROR_CONFIG"), - Value: "/etc/git-mirror/config.yaml", - Usage: "Absolute path to the config file.", - }, - &cli.StringFlag{ - Name: "log-level", - Sources: cli.EnvVars("LOG_LEVEL"), - Value: "info", - Usage: "Log level", - }, - &cli.BoolFlag{ - Name: "watch-config", - Usage: "watch config for changes and reload when changes encountered.\n" + - "Only changes related to add,remove repository or worktrees will be actioned.", - Value: true, - }, - } ) func init() { @@ -55,62 +35,89 @@ func init() { })) } +func envString(key, fallback string) string { + value, ok := os.LookupEnv(key) + if ok { + return value + } + return fallback +} + +func envBool(key string, fallback bool) bool { + value, ok := os.LookupEnv(key) + if ok { + parsed, err := strconv.ParseBool(value) + if err == nil { + return parsed + } + return fallback + } + return fallback +} + +func usage() { + fmt.Fprintf(os.Stderr, "NAME:\n") + fmt.Fprintf(os.Stderr, "\tgit-mirror - git-mirror is a tool to periodically mirror remote repositories locally.\n") + fmt.Fprintf(os.Stderr, "\nUsage:\n") + fmt.Fprintf(os.Stderr, "\tgit-mirror [global options]\n") + fmt.Fprintf(os.Stderr, "\nGLOBAL OPTIONS:\n") + fmt.Fprintf(os.Stderr, "\t-log-level value (default: 'info') Log level [$LOG_LEVEL]\n") + fmt.Fprintf(os.Stderr, "\t-config value (default: '/etc/git-mirror/config.yaml') Absolute path to the config file. [$GIT_MIRROR_CONFIG]\n") + fmt.Fprintf(os.Stderr, "\t-watch-config value (default: true) watch config for changes and reload when changes encountered. [$GIT_MIRROR_WATCH_CONFIG]\n") + + os.Exit(2) +} + func main() { - cmd := &cli.Command{ - Name: "git-mirror", - Usage: "git-mirror is a tool to periodically mirror remote repositories locally.", - Flags: flags, - Action: func(ctx context.Context, c *cli.Command) error { - ctx, cancel := context.WithCancel(ctx) - - // set log level according to argument - if v, ok := levelStrings[strings.ToLower(c.String("log-level"))]; ok { - loggerLevel.Set(v) - } - - // path to resolve git - gitENV := []string{fmt.Sprintf("PATH=%s", os.Getenv("PATH"))} - - // create empty repo pool which will be populated by watchConfig - repoPool, err := mirror.NewRepoPool(ctx, mirror.RepoPoolConfig{}, logger.With("logger", "git-mirror"), gitENV) - if err != nil { - logger.Error("could not create git mirror pool", "err", err) - os.Exit(1) - } - - onConfigChange := func(config *mirror.RepoPoolConfig) { - ensureConfig(repoPool, config) - // start mirror Loop on newly added repos - repoPool.StartLoop() - } - - // Start watching the config file - go WatchConfig(ctx, c.String("config"), 10*time.Second, onConfigChange) - - //listenForShutdown - stop := make(chan os.Signal, 2) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - - <-stop - - logger.Info("shutting down...") - cancel() - - select { - case <-repoPool.Stopped: - logger.Info("all repositories mirror loop is stopped") - os.Exit(0) - - case <-stop: - logger.Info("second signal received, terminating") - os.Exit(1) - } - return nil - }, + ctx, cancel := context.WithCancel(context.Background()) + + flagLogLevel := flag.String("log-level", envString("LOG_LEVEL", "info"), "Log level") + flagConfig := flag.String("config", envString("GIT_MIRROR_CONFIG", "/etc/git-mirror/config.yaml"), "Absolute path to the config file") + flagWatchConfig := flag.Bool("watch-config", envBool("GIT_MIRROR_WATCH_CONFIG", true), "watch config for changes and reload when changes encountered") + + flag.Usage = usage + flag.Parse() + + // set log level according to argument + if v, ok := levelStrings[strings.ToLower(*flagLogLevel)]; ok { + loggerLevel.Set(v) + } + + // path to resolve git + gitENV := []string{fmt.Sprintf("PATH=%s", os.Getenv("PATH"))} + + // create empty repo pool which will be populated by watchConfig + repoPool, err := mirror.NewRepoPool(ctx, mirror.RepoPoolConfig{}, logger.With("logger", "git-mirror"), gitENV) + if err != nil { + logger.Error("could not create git mirror pool", "err", err) + os.Exit(1) + } + + onConfigChange := func(config *mirror.RepoPoolConfig) { + ensureConfig(repoPool, config) + // start mirror Loop on newly added repos + repoPool.StartLoop() } - if err := cmd.Run(context.Background(), os.Args); err != nil { - logger.Error("failed to run app", "err", err) + // Start watching the config file + go WatchConfig(ctx, *flagConfig, *flagWatchConfig, 10*time.Second, onConfigChange) + + //listenForShutdown + stop := make(chan os.Signal, 2) + signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + + <-stop + + logger.Info("shutting down...") + cancel() + + select { + case <-repoPool.Stopped: + logger.Info("all repositories mirror loop is stopped") + os.Exit(0) + + case <-stop: + logger.Info("second signal received, terminating") os.Exit(1) } }