package config import ( "fmt" "log/slog" "os" "path/filepath" "github.com/spf13/viper" "github.com/zalando/go-keyring" ) const ( serviceName = "workctl" keySSHPassword = "ssh-password" keyRDPPassword = "rdp-password" ) type Config struct { SSHUser string `mapstructure:"SSH_USER"` SSHPassword string `mapstructure:"SSH_PASSWORD"` SSHHost string `mapstructure:"SSH_HOST"` JumpUser string `mapstructure:"JUMP_USER"` JumpHost string `mapstructure:"JUMP_HOST"` WorkstationHost string `mapstructure:"WORKSTATION_HOST"` WorkstationUser string `mapstructure:"WORKSTATION_USER"` WorkstationMac string `mapstructure:"WORKSTATION_MAC"` RDPUser string `mapstructure:"RDP_USER"` RDPPassword string `mapstructure:"RDP_PASSWORD"` WorkstationIP string `mapstructure:"WORKSTATION_IP"` SSHPort int `mapstructure:"SSH_PORT"` } func Load() (Config, error) { var cfg Config configPath, err := os.UserConfigDir() if err != nil { return cfg, fmt.Errorf("could not get user config dir: %w", err) } workConfigPath := filepath.Join(configPath, "work") configFile := filepath.Join(workConfigPath, "config.toml") if err := os.MkdirAll(workConfigPath, 0o750); err != nil { return cfg, fmt.Errorf("could not create config directory '%s': %w", workConfigPath, err) } viper.SetConfigFile(configFile) viper.SetConfigType("toml") viper.AutomaticEnv() if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return cfg, fmt.Errorf("error reading config file '%s': %w", configFile, err) } slog.Debug(fmt.Sprintf("Config file '%s' not found, using defaults/env vars.", configFile)) } if err := viper.UnmarshalKey("default", &cfg); err != nil { if err := viper.Unmarshal(&cfg); err != nil { return cfg, fmt.Errorf("error decoding config from '%s': %w", configFile, err) } } if cfg.SSHPort == 0 { cfg.SSHPort = 22 } if cfg.SSHPassword == "" { if secret, err := GetSecret(keySSHPassword); err == nil { cfg.SSHPassword = secret slog.Debug("Loaded SSH password from keyring.") } } if cfg.RDPPassword == "" { if secret, err := GetSecret(keyRDPPassword); err == nil { cfg.RDPPassword = secret slog.Debug("Loaded RDP password from keyring.") } } return cfg, nil } func GetSecret(key string) (string, error) { return keyring.Get(serviceName, key) } func SetSecret(key, value string) error { if value == "" { return fmt.Errorf("secret cannot be empty") } return keyring.Set(serviceName, key, value) } func KeySSHPassword() string { return keySSHPassword } func KeyRDPPassword() string { return keyRDPPassword }