Files
tailscale-bind-ddns/cmd/root.go
2025-09-14 21:26:38 -05:00

120 lines
3.3 KiB
Go

package cmd
import (
"flag"
"fmt"
"os"
"time"
"github.com/aauren/tailscale-bind-ddns/pkg/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/klog/v2"
)
const (
defaultPollInterval = 30 * time.Second
defaultBindPort = 53
defaultTTL = 300 * time.Second
defaultUpdateInterval = 60 * time.Second
testTimeout = 30 * time.Second
)
var (
cfgFile string
cfg *config.Config
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "tailscale-bind-ddns",
Short: "A tool to sync Tailscale machines to Bind DNS records",
Long: `Tailscale-Bind DDNS is a tool that connects to your Tailscale tailnet,
retrieves the list of online machines, and automatically updates DNS A records
in your Bind server using RFC 2136 dynamic updates with TSIG authentication.
The tool supports configuration via:
- Command line flags
- Environment variables (prefixed with TSBD_)
- YAML configuration files
Example usage:
tailscale-bind-ddns run --tailscale-api-key YOUR_KEY --bind-server dns.example.com
tailscale-bind-ddns run --config config.yaml
TSBD_TAILSCALE_API_KEY=your_key tailscale-bind-ddns run`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
var err error
cfg, err = config.LoadConfig()
if err != nil {
return fmt.Errorf("loading configuration: %w", err)
}
return nil
},
}
// setupLogging configures the logging based on the configuration
func setupLogging() error {
// Create a custom flagset for klog
flagset := flag.NewFlagSet("klog", flag.ContinueOnError)
klog.InitFlags(flagset)
// Set verbosity level based on configuration
var verbosity int
switch cfg.General.LogLevel {
case "debug":
verbosity = 2
case "verbose":
verbosity = 1
case "info":
verbosity = 0
default:
return fmt.Errorf("invalid log level: %s", cfg.General.LogLevel)
}
// Set the verbosity flag in the flagset
if err := flagset.Set("v", fmt.Sprintf("%d", verbosity)); err != nil {
return fmt.Errorf("setting klog verbosity: %w", err)
}
// Parse the flagset to apply the settings
if err := flagset.Parse([]string{}); err != nil {
return fmt.Errorf("parsing klog flags: %w", err)
}
klog.V(1).Infof("Logging configured with level: %s (verbosity: %d)", cfg.General.LogLevel, verbosity)
return nil
}
// initializeCommands sets up all commands and flags
func initializeCommands() {
// Add all commands
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(statusCmd)
rootCmd.AddCommand(testCmd)
// Global flags
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is config.yaml)")
rootCmd.PersistentFlags().String("log-level", "info",
"log level (debug, verbose, info) (WARNING: debug level may leak secrets)")
// Bind global flags to viper
bindGlobalFlagsToViper()
}
// bindGlobalFlagsToViper binds global flags to viper configuration
func bindGlobalFlagsToViper() {
// General flags
if err := viper.BindPFlag("general.log_level", rootCmd.PersistentFlags().Lookup("log-level")); err != nil {
klog.Errorf("Failed to bind log-level flag: %v", err)
}
}
// Execute adds all child commands to the root command and sets flags appropriately.
func Execute() {
initializeCommands()
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}