package store import ( "encoding/json" "errors" "os" "os/user" "path/filepath" "slices" ) type Alias struct { Name string `json:"name"` Command string `json:"command"` Description string `json:"description,omitempty"` } type ServerConfig struct { Host string `json:"host"` Port int `json:"port"` } type OIDCConfig struct { Issuer string `json:"issuer"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` PublicURL string `json:"public_url"` UserClaim string `json:"user_claim"` } type Config struct { path string User string `json:"user"` Editor string `json:"editor"` UserAliases []*Alias `json:"aliases"` Serve ServerConfig `json:"serve"` Remote ServerConfig `json:"remote"` OIDC OIDCConfig `json:"oidc"` } func FindDataRoot(std ...string) (string, error) { dir, err := filepath.Abs(".") if err != nil { return "", err } for { p := filepath.Join(dir, ".ax") if stat, err := os.Stat(p); err == nil { if stat.IsDir() { return p, nil } } if parent := filepath.Dir(dir); parent == dir { break } else { dir = parent } } home, err := os.UserHomeDir() if err != nil { return "", err } stdpath := filepath.Join(std...) return filepath.Join(home, stdpath, "ax"), nil } func LoadConfigFile() (*Config, error) { configRoot, err := FindDataRoot(".config") if err != nil { return nil, err } path := filepath.Join(configRoot, "config.json") fc := &Config{path: path, UserAliases: []*Alias{}} data, err := os.ReadFile(path) if err != nil { if !os.IsNotExist(err) { return nil, err } } else { if err := json.Unmarshal(data, fc); err != nil { return nil, err } } return fc, nil } func (c *Config) GetUser() string { if c.User != "" { return c.User } if u := os.Getenv("AX_USER"); u != "" { return u } if u, err := user.Current(); err == nil { return u.Username } return "unknown" } func (c *Config) GetEditor() string { if c.Editor != "" { return c.User } if u := os.Getenv("EDITOR"); u != "" { return u } return "vi" } func (c *Config) GetAlias(name string) (*Alias, error) { for _, a := range c.UserAliases { if a.Name == name { return a, nil } } return nil, errors.New("alias not found") } func (c *Config) SetAlias(alias *Alias) error { for i, a := range c.UserAliases { if a.Name == alias.Name { c.UserAliases[i] = alias return c.Save() } } c.UserAliases = append(c.UserAliases, alias) return c.Save() } func (c *Config) DeleteAlias(name string) error { for i, a := range c.UserAliases { if a.Name == name { c.UserAliases = slices.Delete(c.UserAliases, i, i+1) return c.Save() } } return errors.New("alias not found") } func (c *Config) ListAliases() ([]*Alias, error) { seen := make(map[string]bool) var result []*Alias for _, a := range c.UserAliases { result = append(result, a) seen[a.Name] = true } return result, nil } func (c *Config) GetOIDCConfig() (*OIDCConfig, bool) { if c.OIDC.Issuer == "" { return nil, false } cfg := c.OIDC if cfg.UserClaim == "" { cfg.UserClaim = "preferred_username" } return &cfg, true } func (c *Config) GetRemoteConfig() (*ServerConfig, bool) { if c.Remote.Host == "" { return nil, false } port := c.Remote.Port if port == 0 { port = 7000 } return &ServerConfig{Host: c.Remote.Host, Port: port}, true } func (c *Config) GetServerConfig() *ServerConfig { host := c.Serve.Host if host == "" { host = "localhost" } port := c.Serve.Port if port == 0 { port = 7000 } return &ServerConfig{Host: host, Port: port} } func (c *Config) Save() error { if err := os.MkdirAll(filepath.Dir(c.path), 0755); err != nil { return err } data, err := json.MarshalIndent(c, "", " ") if err != nil { return err } return os.WriteFile(c.path, data, 0644) }