package main import ( "context" "log/slog" "os" "os/signal" "path/filepath" "syscall" "fmt" "linum/internal/blocklist" "linum/internal/cache" "linum/internal/config" "linum/internal/resolver" "linum/internal/server" ) func main() { flags := config.ParseFlags() cfg := config.Default() if _, err := os.Stat(flags.Config); err == nil { fileCfg, err := config.LoadFile(flags.Config) if err != nil { slog.Error("invalid config file", "err", err) os.Exit(1) } cfg = config.Merge(cfg, fileCfg) } cfg = flags.Apply(cfg) if err := cfg.Validate(); err != nil { slog.Error("config validation failed", "err", err) os.Exit(1) } var lvl slog.Level _ = lvl.UnmarshalText([]byte(cfg.Log.Level)) logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: lvl})) slog.SetDefault(logger) fmt.Println("linum - simple dns recursive") fmt.Printf("github.com/radhityax/linum\n\n") logger.Info("config loaded", "file", flags.Config) r := resolver.New( resolver.WithTimeout(2 * 1000 * 1000 * 1000),) c, err := cache.NewCache(cfg.Cache.MaxEntries, cfg.Cache.DBPath) if err != nil { logger.Error("create cache failed", "err", err) os.Exit(1) } defer c.Stop() var bl *blocklist.Blocklist if len(cfg.Blocklist.Files) > 0 || len(cfg.Blocklist.URLs) > 0 { resp := blocklist.ResponseZeroIP if cfg.Blocklist.Response == "nxdomain" { resp = blocklist.ResponseNXDOMAIN } bl = blocklist.New(resp) for _, pattern := range cfg.Blocklist.Files { matches, err := filepath.Glob(pattern) if err != nil { logger.Warn("invalid blocklist glob", "pattern", pattern, "err", err) continue } for _, f := range matches { if err := bl.LoadFile(f); err != nil { logger.Error("load blocklist failed", "file", f, "err", err) os.Exit(1) } logger.Info("blocklist loaded", "file", f, "rules", bl.TotalRules) } } for _, u := range cfg.Blocklist.URLs { if err := bl.LoadURL(u); err != nil { logger.Warn("load blocklist url failed", "url", u, "err", err) continue } logger.Info("blocklist url loaded", "url", u) } } else { logger.Info("no blocklist configured, ad-blocking disabled") } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() srv, err := server.New(cfg.Server.ListenUDP, cfg.Server.ListenTCP, cfg.Server.ListenDOH, logger, r, c, bl) if err != nil { logger.Error("create server failed", "err", err) os.Exit(1) } defer srv.Close() logger.Info("linum starting", "udp", cfg.Server.ListenUDP, "tcp", cfg.Server.ListenTCP, "doh", cfg.Server.ListenDOH, ) if err := srv.Run(ctx); err != nil && err != context.Canceled { logger.Error("server stopped with error", "err", err) os.Exit(1) } logger.Info("linum stopped cleanly") }