155 lines
3.4 KiB
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
|
|
}
|