Files
sslh-multiplex-lab/internal/config/config.go
2026-01-29 00:03:02 +00:00

157 lines
4.4 KiB
Go

package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type Config struct {
HetznerAPIKey string
NamecheapAPIKey string
NamecheapUser string
Domain string
Region string
ServerType string
DeploymentID string
ConfigDir string
LetsEncryptEmail string
}
func LoadConfig(cmd *cobra.Command) (*Config, error) {
return LoadConfigWithValidation(cmd, true)
}
func LoadConfigWithValidation(cmd *cobra.Command, validate bool) (*Config, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
}
configDir := filepath.Join(homeDir, ".sslh-lab")
configFile := filepath.Join(configDir, "config.yaml")
viper.SetConfigType("yaml")
viper.SetConfigFile(configFile)
viper.SetEnvPrefix("SSLH")
viper.AutomaticEnv()
viper.SetDefault("region", "nbg1")
viper.SetDefault("server_type", "cpx22")
viper.SetDefault("config_dir", configDir)
if err := viper.ReadInConfig(); err != nil {
var configFileNotFoundError viper.ConfigFileNotFoundError
var pathError *os.PathError
errMsg := strings.ToLower(err.Error())
if errors.As(err, &configFileNotFoundError) {
// Config file not found is OK - we'll use defaults/env vars/flags
} else if errors.As(err, &pathError) && os.IsNotExist(pathError) {
// File doesn't exist - this is OK
} else if os.IsNotExist(err) {
// Direct IsNotExist check
} else if strings.Contains(errMsg, "no such file") || strings.Contains(errMsg, "not found") {
// File not found error (handled as OK)
} else {
// Any other error is a real problem
return nil, fmt.Errorf("failed to read config file: %w", err)
}
}
config := &Config{
HetznerAPIKey: getStringValue(cmd, "hetzner-key", "HETZNER_KEY"),
NamecheapAPIKey: getStringValue(cmd, "namecheap-key", "NAMECHEAP_KEY"),
NamecheapUser: getStringValue(cmd, "namecheap-user", "NAMECHEAP_USER"),
Domain: getStringValue(cmd, "domain", "DOMAIN"),
Region: getStringValue(cmd, "region", "REGION"),
ServerType: getStringValue(cmd, "server-type", "SERVER_TYPE"),
ConfigDir: viper.GetString("config_dir"),
LetsEncryptEmail: getStringValue(cmd, "letsencrypt-email", "LETSENCRYPT_EMAIL"),
}
if validate {
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("configuration validation failed: %w", err)
}
}
return config, nil
}
func getStringValue(cmd *cobra.Command, flagName, envKey string) string {
if cmd != nil {
if flagValue, err := cmd.Flags().GetString(flagName); err == nil && flagValue != "" {
return flagValue
}
}
if envValue := viper.GetString(envKey); envValue != "" {
return envValue
}
// For viper config file, convert hyphen to underscore (viper uses underscores)
viperKey := strings.ReplaceAll(flagName, "-", "_")
return viper.GetString(viperKey)
}
func (c *Config) Validate() error {
if c.HetznerAPIKey == "" {
return fmt.Errorf("hetzner API key is required (set via --hetzner-key, SSLH_HETZNER_KEY, or config file)")
}
if c.NamecheapAPIKey == "" {
return fmt.Errorf("namecheap API key is required (set via --namecheap-key, SSLH_NAMECHEAP_KEY, or config file)")
}
if c.NamecheapUser == "" {
return fmt.Errorf("namecheap username is required (set via --namecheap-user, SSLH_NAMECHEAP_USER, or config file)")
}
if c.Domain == "" {
return fmt.Errorf("domain is required (set via --domain, SSLH_DOMAIN, or config file)")
}
return nil
}
func (c *Config) ValidateForTeardown() error {
if c.HetznerAPIKey == "" {
return fmt.Errorf("hetzner API key is required (set via --hetzner-key, SSLH_HETZNER_KEY, or config file)")
}
if c.NamecheapAPIKey == "" {
return fmt.Errorf("namecheap API key is required (set via --namecheap-key, SSLH_NAMECHEAP_KEY, or config file)")
}
if c.NamecheapUser == "" {
return fmt.Errorf("namecheap username is required (set via --namecheap-user, SSLH_NAMECHEAP_USER, or config file)")
}
return nil
}
func (c *Config) GetDeploymentDir() string {
if c.DeploymentID == "" {
return ""
}
return filepath.Join(c.ConfigDir, "deployments", c.DeploymentID)
}
func (c *Config) EnsureConfigDir() error {
return os.MkdirAll(c.ConfigDir, 0755)
}
func (c *Config) EnsureDeploymentDir() error {
if c.DeploymentID == "" {
return fmt.Errorf("deployment ID not set")
}
return os.MkdirAll(c.GetDeploymentDir(), 0700)
}