216 lines
5.4 KiB
Go
216 lines
5.4 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"kmanage/pkg/secret"
|
|
|
|
"github.com/spf13/viper"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// AppConfig represents the configuration stored in ~/.kmanage.yaml.
|
|
type AppConfig struct {
|
|
KomodoURL string `yaml:"komodo_url" mapstructure:"komodo_url"`
|
|
KomodoKey string `yaml:"komodo_key,omitempty" mapstructure:"komodo_key"` // Keyring fallback only
|
|
KomodoSecret string `yaml:"komodo_secret,omitempty" mapstructure:"komodo_secret"` // Keyring fallback only
|
|
DefaultGitAccount string `yaml:"default_git_account" mapstructure:"default_git_account"`
|
|
GitAccounts map[string]GitAccount `yaml:"git_accounts" mapstructure:"git_accounts"`
|
|
}
|
|
|
|
// GitAccount holds metadata for a configured Git provider account.
|
|
type GitAccount struct {
|
|
Type string `yaml:"type" mapstructure:"type"` // gitea, forgejo, github
|
|
URL string `yaml:"url" mapstructure:"url"`
|
|
Username string `yaml:"username,omitempty" mapstructure:"username,omitempty"`
|
|
Token string `yaml:"token,omitempty" mapstructure:"token"` // Keyring fallback only
|
|
}
|
|
|
|
func getConfigFilePath() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to determine user home directory: %w", err)
|
|
}
|
|
return filepath.Join(home, ".kmanage.yaml"), nil
|
|
}
|
|
|
|
// LoadAppConfig loads configuration from ~/.kmanage.yaml.
|
|
func LoadAppConfig() (*AppConfig, error) {
|
|
configPath, err := getConfigFilePath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg := &AppConfig{
|
|
GitAccounts: make(map[string]GitAccount),
|
|
}
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
if err == nil {
|
|
_ = yaml.Unmarshal(data, cfg)
|
|
}
|
|
|
|
if cfg.GitAccounts == nil {
|
|
cfg.GitAccounts = make(map[string]GitAccount)
|
|
}
|
|
|
|
// Automatic migration of legacy single-account configuration (git_url / git_token)
|
|
oldGitURL := viper.GetString("git_url")
|
|
if oldGitURL != "" && len(cfg.GitAccounts) == 0 {
|
|
accountName := "default"
|
|
accountType := "gitea"
|
|
if strings.Contains(oldGitURL, "github.com") {
|
|
accountType = "github"
|
|
accountName = "github"
|
|
} else if strings.Contains(oldGitURL, "git.") {
|
|
accountName = "gitea"
|
|
}
|
|
|
|
oldToken := viper.GetString("git_token")
|
|
if oldToken != "" {
|
|
_ = secret.SetSecret("git:"+accountName+":token", oldToken)
|
|
}
|
|
|
|
cfg.GitAccounts[accountName] = GitAccount{
|
|
Type: accountType,
|
|
URL: oldGitURL,
|
|
}
|
|
if cfg.DefaultGitAccount == "" {
|
|
cfg.DefaultGitAccount = accountName
|
|
}
|
|
_ = saveAppConfig(cfg)
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func saveAppConfig(cfg *AppConfig) error {
|
|
configPath, err := getConfigFilePath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := yaml.Marshal(cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to serialize configuration: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
|
return fmt.Errorf("failed to write configuration file: %w", err)
|
|
}
|
|
|
|
_ = viper.ReadInConfig()
|
|
return nil
|
|
}
|
|
|
|
// SaveKomodoConfig saves the Komodo URL in config and securely stores credentials in the OS keyring.
|
|
func SaveKomodoConfig(url, key, secretVal string) error {
|
|
cfg, err := LoadAppConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg.KomodoURL = strings.TrimRight(url, "/")
|
|
cfg.KomodoKey = ""
|
|
cfg.KomodoSecret = ""
|
|
|
|
// Store in OS Keyring
|
|
if err := secret.SetSecret("komodo:key", key); err != nil {
|
|
cfg.KomodoKey = key
|
|
}
|
|
if err := secret.SetSecret("komodo:secret", secretVal); err != nil {
|
|
cfg.KomodoSecret = secretVal
|
|
}
|
|
|
|
return saveAppConfig(cfg)
|
|
}
|
|
|
|
// SaveGitAccount saves a Git account configuration and securely stores its token in the OS keyring.
|
|
func SaveGitAccount(name, accountType, url, token, username string) error {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return fmt.Errorf("account name cannot be empty")
|
|
}
|
|
|
|
cfg, err := LoadAppConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
accountType = strings.ToLower(strings.TrimSpace(accountType))
|
|
if accountType == "" {
|
|
if strings.Contains(url, "github.com") {
|
|
accountType = "github"
|
|
} else {
|
|
accountType = "gitea"
|
|
}
|
|
}
|
|
|
|
url = strings.TrimRight(url, "/")
|
|
if url == "" && accountType == "github" {
|
|
url = "https://api.github.com"
|
|
}
|
|
|
|
acc := GitAccount{
|
|
Type: accountType,
|
|
URL: url,
|
|
Username: username,
|
|
}
|
|
|
|
keyringKey := "git:" + name + ":token"
|
|
if err := secret.SetSecret(keyringKey, token); err != nil {
|
|
acc.Token = token
|
|
}
|
|
|
|
cfg.GitAccounts[name] = acc
|
|
|
|
if cfg.DefaultGitAccount == "" || len(cfg.GitAccounts) == 1 {
|
|
cfg.DefaultGitAccount = name
|
|
}
|
|
|
|
return saveAppConfig(cfg)
|
|
}
|
|
|
|
// SetDefaultGitAccount sets the active default Git account.
|
|
func SetDefaultGitAccount(name string) error {
|
|
cfg, err := LoadAppConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, exists := cfg.GitAccounts[name]; !exists {
|
|
return fmt.Errorf("account '%s' not found. View configured accounts with 'kmanage git accounts'", name)
|
|
}
|
|
|
|
cfg.DefaultGitAccount = name
|
|
return saveAppConfig(cfg)
|
|
}
|
|
|
|
// DeleteGitAccount deletes a Git account and removes its secret from the OS keyring.
|
|
func DeleteGitAccount(name string) error {
|
|
cfg, err := LoadAppConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, exists := cfg.GitAccounts[name]; !exists {
|
|
return fmt.Errorf("account '%s' not found", name)
|
|
}
|
|
|
|
delete(cfg.GitAccounts, name)
|
|
_ = secret.DeleteSecret("git:" + name + ":token")
|
|
|
|
if cfg.DefaultGitAccount == name {
|
|
cfg.DefaultGitAccount = ""
|
|
for k := range cfg.GitAccounts {
|
|
cfg.DefaultGitAccount = k
|
|
break
|
|
}
|
|
}
|
|
|
|
return saveAppConfig(cfg)
|
|
}
|