package config import ( "encoding/json" "fmt" "os" "path/filepath" "runtime" "git.tukangketik.id/swanadiva/hostkeeper/internal/models" ) // Config manages application configuration and paths type Config struct { appName string configDir string dataDir string appConfig *models.AppConfig } // New creates a new Config instance func New() (*Config, error) { appName := "hostkeeper" configDir, err := getConfigDir(appName) if err != nil { return nil, fmt.Errorf("failed to get config directory: %w", err) } dataDir := filepath.Join(configDir, "data") c := &Config{ appName: appName, configDir: configDir, dataDir: dataDir, } // Create directories if err := os.MkdirAll(configDir, 0700); err != nil { return nil, fmt.Errorf("failed to create config directory: %w", err) } if err := os.MkdirAll(dataDir, 0700); err != nil { return nil, fmt.Errorf("failed to create data directory: %w", err) } // Load or create config if err := c.load(); err != nil { return nil, fmt.Errorf("failed to load config: %w", err) } return c, nil } // getConfigDir returns the OS-appropriate config directory for the app func getConfigDir(appName string) (string, error) { switch runtime.GOOS { case "darwin": home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, "Library", "Application Support", appName), nil case "linux": if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" { return filepath.Join(xdg, appName), nil } home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, ".config", appName), nil case "windows": appData := os.Getenv("APPDATA") if appData != "" { return filepath.Join(appData, appName), nil } home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, "AppData", "Roaming", appName), nil default: home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, "."+appName), nil } } // load reads the config file, or creates a default one if it doesn't exist func (c *Config) load() error { configPath := c.GetConfigFilePath() data, err := os.ReadFile(configPath) if err != nil { if os.IsNotExist(err) { // Create default config c.appConfig = models.DefaultConfig() return c.Save() } return err } c.appConfig = models.DefaultConfig() if err := json.Unmarshal(data, c.appConfig); err != nil { return fmt.Errorf("failed to parse config: %w", err) } return nil } // Save writes the current configuration to disk func (c *Config) Save() error { configPath := c.GetConfigFilePath() data, err := json.MarshalIndent(c.appConfig, "", " ") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } return os.WriteFile(configPath, data, 0600) } // GetAppConfig returns the application configuration func (c *Config) GetAppConfig() *models.AppConfig { return c.appConfig } // UpdateAppConfig updates the application configuration func (c *Config) UpdateAppConfig(cfg *models.AppConfig) error { c.appConfig = cfg return c.Save() } // GetConfigDir returns the configuration directory path func (c *Config) GetConfigDir() string { return c.configDir } // GetDataDir returns the data directory path func (c *Config) GetDataDir() string { return c.dataDir } // GetConfigFilePath returns the full path to the config JSON file func (c *Config) GetConfigFilePath() string { return filepath.Join(c.configDir, "config.json") } // GetHostsFilePath returns the full path to the hosts JSON file func (c *Config) GetHostsFilePath() string { return filepath.Join(c.dataDir, "hosts.json") } // GetKeysFilePath returns the full path to the keys JSON file func (c *Config) GetKeysFilePath() string { return filepath.Join(c.dataDir, "keys.json") } // GetSnippetsFilePath returns the full path to the snippets JSON file func (c *Config) GetSnippetsFilePath() string { return filepath.Join(c.dataDir, "snippets.json") }