Files
ExploreDNS/cmd/exploredns/main.go
Gary HansenandClaude Fable 5 94e41fe5b5 feat(release): version stamping and tag-triggered release workflow
--version/-V on the CLI, version in /api/health via Server.SetVersion,
Makefile/Dockerfile ldflags stamping from git describe, and a release
workflow on v* tags: both binaries for linux/darwin (amd64+arm64) and
windows/amd64 with SHA256SUMS attached to the Gitea release
(idempotent — reuses an existing hand-written release and skips
already-uploaded assets), plus version-tagged Docker images.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 23:31:34 +10:00

212 lines
7.9 KiB
Go

package main
import (
"context"
"flag"
"fmt"
"os"
"time"
"gitea.hansenits.com.au/hits/ExploreDNS/internal/config"
"gitea.hansenits.com.au/hits/ExploreDNS/internal/dns"
"gitea.hansenits.com.au/hits/ExploreDNS/internal/output"
"gitea.hansenits.com.au/hits/ExploreDNS/internal/traverse"
)
// version is stamped at build time via -ldflags "-X main.version=...".
var version = "dev"
func main() {
cfg := config.DefaultConfig()
queryType := flag.String("type", cfg.QueryType, "Record type (A, AAAA, NS, CNAME, MX, TXT, SOA, PTR, ANY)")
rootServer := flag.String("root-server", cfg.RootServer, "Initial root server (hostname or IP)")
allRootServers := flag.Bool("all-root-servers", cfg.AllRootServers, "Traverse from all root servers")
rootAAAA := flag.Bool("root-aaaa", cfg.RootAAAA, "Include IPv6 root addresses (not implemented yet)")
followAAAA := flag.Bool("follow-aaaa", cfg.FollowAAAA, "Only follow AAAA for referrals (not implemented yet)")
dnsUpstream := flag.String("dns-upstream", cfg.DNSUpstream, "Upstream resolver for root discovery (e.g. 8.8.8.8:53, default: system)")
udpSize := flag.Int("udp-size", cfg.UDPSize, "EDNS0 buffer size (512-4096; 512 turns EDNS0 off)")
allowTCP := flag.Bool("allow-tcp", cfg.AllowTCP, "TCP fallback on truncation")
alwaysTCP := flag.Bool("always-tcp", cfg.AlwaysTCP, "Always use TCP")
maxDepth := flag.Int("max-depth", cfg.MaxDepth, "Max traversal depth (1-100)")
retries := flag.Int("retries", cfg.Retries, "Number of 2s retries before timing out (0-10)")
fast := flag.Bool("fast", cfg.Fast, "Fast mode; turn off to be more accurate")
jsonOutput := flag.Bool("json", false, "Output results as JSON")
// Verbose: long and short form share the same variable.
var verboseVal bool
flag.BoolVar(&verboseVal, "verbose", cfg.Verbose, "Verbose output")
flag.BoolVar(&verboseVal, "v", cfg.Verbose, "Verbose output (shorthand)")
// Debug: -d prints debug diagnostics; -dd additionally enables library
// debug (the Go flag package cannot stack -d -d like the Ruby CLI).
var dFlag, ddFlag bool
flag.BoolVar(&dFlag, "d", false, "Print debug diagnostics to stderr")
flag.BoolVar(&dFlag, "debug", false, "Print debug diagnostics to stderr")
flag.BoolVar(&ddFlag, "dd", false, "Like -d plus library-level debug")
// Version: long and short form share the same variable (mirrors
// dnstraverse's -V).
var showVersion bool
flag.BoolVar(&showVersion, "version", false, "Print version and exit")
flag.BoolVar(&showVersion, "V", false, "Print version and exit (shorthand)")
// Quiet: long and short form share the same variable.
var quietVal bool
flag.BoolVar(&quietVal, "quiet", cfg.Quiet, "Suppress the header block")
flag.BoolVar(&quietVal, "q", cfg.Quiet, "Suppress the header block (shorthand)")
// Each show flag defaults to the config default, so --show-X and
// --show-X=false both take effect directly; the --no-show-X aliases force
// the value off, matching the Ruby --[no-]show-X switches.
showProgress := flag.Bool("show-progress", cfg.ShowProgress, "Show traversal progress")
noShowProgress := flag.Bool("no-show-progress", false, "Hide traversal progress")
showResolves := flag.Bool("show-resolves", cfg.ShowResolves, "Show glue resolution progress")
noShowResolves := flag.Bool("no-show-resolves", false, "Hide glue resolution progress")
showServers := flag.Bool("show-servers", cfg.ShowServers, "Show servers encountered")
noShowServers := flag.Bool("no-show-servers", false, "Hide servers encountered")
showVersions := flag.Bool("show-versions", cfg.ShowVersions, "Show server version fingerprints")
noShowVersions := flag.Bool("no-show-versions", false, "Hide server version fingerprints")
showAllStats := flag.Bool("show-all-stats", cfg.ShowAllStats, "Show statistics after every node")
noShowAllStats := flag.Bool("no-show-all-stats", false, "Hide per-node statistics")
showResults := flag.Bool("show-results", cfg.ShowResults, "Show query results")
noShowResults := flag.Bool("no-show-results", false, "Hide query results")
showSummaryResults := flag.Bool("show-summary-results", cfg.ShowSummaryResults, "Show summary of results")
noShowSummaryResults := flag.Bool("no-show-summary-results", false, "Hide summary of results")
flag.Usage = config.PrintUsage
flag.Parse()
if showVersion {
fmt.Printf("exploredns %s\n", version)
return
}
args := flag.Args()
cfg.QueryType = *queryType
cfg.RootServer = *rootServer
cfg.AllRootServers = *allRootServers
cfg.RootAAAA = *rootAAAA
cfg.FollowAAAA = *followAAAA
cfg.DNSUpstream = *dnsUpstream
cfg.UDPSize = *udpSize
cfg.AllowTCP = *allowTCP
cfg.AlwaysTCP = *alwaysTCP
cfg.MaxDepth = *maxDepth
cfg.Retries = *retries
cfg.Fast = *fast
cfg.Verbose = verboseVal
cfg.Debug = config.ParseDebugLevel(dFlag, ddFlag)
cfg.Quiet = quietVal
cfg.ShowProgress = *showProgress && !*noShowProgress
cfg.ShowResolves = *showResolves && !*noShowResolves
cfg.ShowServers = *showServers && !*noShowServers
cfg.ShowVersions = *showVersions && !*noShowVersions
cfg.ShowAllStats = *showAllStats && !*noShowAllStats
cfg.ShowResults = *showResults && !*noShowResults
cfg.ShowSummaryResults = *showSummaryResults && !*noShowSummaryResults
if err := cfg.Validate(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
domain, err := cfg.GetDomain(args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
queryTypeValue, err := config.ParseQueryType(cfg.QueryType)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
queryConfig := &dns.QueryConfig{
UDPSize: cfg.UDPSize,
Timeout: 2 * time.Second,
Retries: cfg.Retries,
UseTCP: cfg.AlwaysTCP,
AllowTCP: cfg.AllowTCP,
}
rootConfig := &dns.RootDiscoveryConfig{
IncludeAAAA: cfg.RootAAAA,
// --root-server accepts a hostname or an IP literal.
Server: cfg.RootServer,
AllRoots: cfg.AllRootServers,
Resolver: cfg.DNSUpstream,
Query: queryConfig,
}
traverserConfig := &traverse.TraverserConfig{
MaxDepth: cfg.MaxDepth,
QueryType: queryTypeValue,
RootConfig: rootConfig,
QueryConfig: queryConfig,
Fast: cfg.Fast,
}
traverser := traverse.NewTraverser(traverserConfig)
if cfg.Debug > 0 {
fmt.Fprintf(os.Stderr, "Debug: Config loaded\n")
fmt.Fprintf(os.Stderr, " Domain: %s\n", domain)
fmt.Fprintf(os.Stderr, " Query Type: %s\n", cfg.QueryType)
fmt.Fprintf(os.Stderr, " Max Depth: %d\n", cfg.MaxDepth)
fmt.Fprintf(os.Stderr, " UDP Size: %d\n", cfg.UDPSize)
fmt.Fprintf(os.Stderr, " Retries: %d\n", cfg.Retries)
fmt.Fprintf(os.Stderr, " Allow TCP: %v\n", cfg.AllowTCP)
fmt.Fprintf(os.Stderr, " Always TCP: %v\n", cfg.AlwaysTCP)
fmt.Fprintf(os.Stderr, " Fast Mode: %v\n", cfg.Fast)
if cfg.DNSUpstream != "" {
fmt.Fprintf(os.Stderr, " DNS Upstream: %s\n", cfg.DNSUpstream)
}
}
outFmt := output.FormatText
if *jsonOutput {
outFmt = output.FormatJSON
}
outCfg := &output.Config{
Format: outFmt,
Domain: domain,
QueryType: cfg.QueryType,
Fast: cfg.Fast,
AllRootServers: cfg.AllRootServers,
UDPSize: cfg.UDPSize,
Retries: cfg.Retries,
MaxDepth: cfg.MaxDepth,
AllowTCP: cfg.AllowTCP,
AlwaysTCP: cfg.AlwaysTCP,
ShowProgress: cfg.ShowProgress,
ShowResolves: cfg.ShowResolves,
ShowServers: cfg.ShowServers,
ShowVersions: cfg.ShowVersions,
ShowAllStats: cfg.ShowAllStats,
ShowResults: cfg.ShowResults,
ShowSummaryResults: cfg.ShowSummaryResults,
Verbose: cfg.Verbose,
Quiet: cfg.Quiet,
Color: output.ColorEnabled(os.Stdout),
Debug: cfg.Debug,
}
ctx := context.Background()
formatter := output.NewFormatter(outCfg, os.Stdout)
_, err = output.RunTraversal(ctx, traverser, outCfg, formatter, domain)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: traversal failed: %v\n", err)
os.Exit(1)
}
if cfg.Debug > 0 {
fmt.Fprintf(os.Stderr, "Debug: Traversal completed\n")
}
}