cfdns/cmd/main.go

155 lines
3.4 KiB
Go

package main
import (
"context"
"flag"
"os"
"os/signal"
"time"
"github.com/cloudflare/cloudflare-go"
"github.com/goodieshq/cfdns/pkg/cf"
"github.com/goodieshq/cfdns/pkg/config"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
const VERSION = "0.3.0"
const DEFAULT_CONFIG_FILE = "config/cfdns.yaml"
type CLIFlags struct {
ConfigFile string
}
func init() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
func main() {
log.Info().Str("version", VERSION).Msg("starting cfdns...")
info := cli()
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
// load the configuration file from JSON or YAML
cfg, err := config.LoadConfig(info.ConfigFile)
if err != nil {
log.Fatal().Err(err).Msg("failed to load config file")
}
var cfopts []cloudflare.Option
// set the log output verbosity level
if cfg.Verbose {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
} else {
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
}
cfdns, err := cf.NewCFDNS(*cfg, cfopts...)
if err != nil {
log.Fatal().Err(err).Msg("failed to create cfdns instance")
}
// check if the provided zone ID is valid for the scoped token
valid, err := cfdns.ZoneIsValid(ctx)
if !valid {
evt := log.Fatal()
if err != nil {
evt = evt.Err(err)
}
evt.Msg("failed to validate the zone")
}
watcher := watchFile(ctx, info.ConfigFile)
for {
log.Info().Msg("starting cfdns processing cycle...")
cfdns.Process(ctx)
cfdns.Wait()
select {
case <-ctx.Done():
log.Info().Msg("shutting down cfdns...")
cfdns.Close()
log.Info().Msg("cfdns stopped")
return
case <-time.After(cfg.Frequency):
case <-watcher:
// check file modification time and reload if changed
log.Info().Msg("configuration file changed, reloading...")
cfg, err = config.LoadConfig(info.ConfigFile)
if err != nil {
log.Error().Err(err).Msg("failed to reload config file, keeping existing configuration")
continue
}
if cfg.Verbose {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
} else {
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
}
cfdns.SetConfig(cfg)
}
}
}
func cli() *CLIFlags {
var cliFlags CLIFlags
flag.StringVar(&cliFlags.ConfigFile, "config", "", "Configuration File")
flag.StringVar(&cliFlags.ConfigFile, "c", "", "Configuration File (alias)")
flag.Parse()
if cliFlags.ConfigFile == "" {
cliFlags.ConfigFile = DEFAULT_CONFIG_FILE
}
return &cliFlags
}
func getLastModTime(filename string) time.Time {
info, err := os.Stat(filename)
if err != nil {
return time.Time{}
}
return info.ModTime()
}
func watchFile(ctx context.Context, filename string) <-chan struct{} {
ch := make(chan struct{}, 1)
interval := 1 * time.Second
go func() {
modTime := getLastModTime(filename)
for {
select {
case <-ctx.Done():
return
case <-time.After(interval):
// get the last modification time of the file
newModTime := getLastModTime(filename)
// if we couldn't get the mod time, skip this iteration
if newModTime.IsZero() {
continue
}
// if this is the first time checking, just set the modTime
if modTime.IsZero() {
modTime = newModTime
continue
}
// if the modification time has changed, notify via channel
if newModTime.After(modTime) {
modTime = newModTime
<-time.After(50 * time.Millisecond)
select {
case ch <- struct{}{}:
default:
}
}
}
}
}()
return ch
}