initial commit

This commit is contained in:
developer committed 2026-09-27 11:20:34 +00:00
1 parent 5b935301e5
commit 85453852d5
105 files changed
+24846

No files matched your search

+126
View File
@@ -0,0 +1,126 @@
// Command fancy-dns is a caching, blocklist-aware recursive DNS server with
// DNS-over-TLS, DNS-over-HTTPS and an embedded admin console.
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"git.k8s-git-k6n.net/mats/fancy-dns/internal/app"
)
func main() {
var (
configPath = flag.String("config", envOr("FANCYDNS_CONFIG", "/etc/fancy-dns/config.yaml"), "path to the configuration file")
showVersion = flag.Bool("version", false, "print the version and exit")
checkOnly = flag.Bool("check", false, "validate the configuration and exit")
logLevel = flag.String("log-level", os.Getenv("FANCYDNS_LOG_LEVEL"), "override log level (debug, info, warn, error)")
logFormat = flag.String("log-format", os.Getenv("FANCYDNS_LOG_FORMAT"), "override log format (text, json)")
adminAddr = flag.String("admin-addr", os.Getenv("FANCYDNS_ADMIN_ADDR"), "override the admin API listen address")
)
flag.Parse()
if *showVersion {
fmt.Println("fancy-dns", app.Version)
return
}
if err := run(runOptions{
configPath: *configPath,
logLevel: *logLevel,
logFormat: *logFormat,
adminAddr: *adminAddr,
checkOnly: *checkOnly,
}); err != nil {
fmt.Fprintln(os.Stderr, "fancy-dns:", err)
os.Exit(1)
}
}
type runOptions struct {
configPath string
logLevel string
logFormat string
adminAddr string
checkOnly bool
}
func run(o runOptions) error {
a, err := app.New(app.Options{
ConfigPath: o.configPath,
Version: app.Version,
LogLevel: o.logLevel,
LogFormat: o.logFormat,
})
if err != nil {
return err
}
if o.checkOnly {
fmt.Println("configuration is valid:", o.configPath)
return nil
}
if o.adminAddr != "" {
// Bind the admin API elsewhere without touching the config file, which
// is what a container platform wants.
if err := a.SetAdminAddr(o.adminAddr); err != nil {
return err
}
}
// SIGINT/SIGTERM start a graceful shutdown; SIGHUP reloads the config.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sig := make(chan os.Signal, 4)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
defer signal.Stop(sig)
if err := a.Start(ctx); err != nil {
return err
}
reloadErr := make(chan error, 1)
go func() {
for s := range sig {
switch s {
case syscall.SIGHUP:
a.Log().Info("SIGHUP received, reloading configuration")
if err := a.Reload(); err != nil {
// A failed reload must not kill the server: the previous
// configuration keeps serving and the error shows in /api.
a.Log().Error("reload failed, keeping the running configuration", "error", err)
}
default:
a.Log().Info("signal received, shutting down", "signal", s.String())
cancel()
select {
case reloadErr <- nil:
default:
}
return
}
}
}()
<-ctx.Done()
// Give in-flight queries a moment to finish before tearing listeners down.
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer shutdownCancel()
if err := a.Stop(shutdownCtx); err != nil {
return err
}
return <-reloadErr
}
func envOr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}