nix-cache-login/internal/config/config.go

125 lines
3.2 KiB
Go
Raw Normal View History

2026-02-26 11:05:16 +01:00
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
2026-02-26 11:05:16 +01:00
"github.com/adrg/xdg"
toml "github.com/pelletier/go-toml/v2"
)
type Config struct {
Issuer string `toml:"issuer"`
ClientID string `toml:"client_id"`
ClientSecretFile string `toml:"client_secret_file,omitempty"`
CacheHost string `toml:"cache_host"`
NetrcPath string `toml:"netrc_path"`
// ClientSecret is populated at load time by reading ClientSecretFile.
ClientSecret string `toml:"-"`
2026-02-26 11:05:16 +01:00
}
2026-03-02 07:25:06 +01:00
const configPathEnvVar = "NIX_CACHE_LOGIN_CONFIG"
var systemConfigPath = "/etc/nix-cache-login/config.toml"
2026-02-26 11:05:16 +01:00
// Load reads the config from the given path, or from the default XDG location.
func Load(path string) (*Config, error) {
2026-03-02 07:25:06 +01:00
path = resolveConfigPath(path)
2026-02-26 11:05:16 +01:00
data, err := os.ReadFile(path)
2026-03-02 07:25:06 +01:00
if err != nil && path == defaultXDGConfigPath() {
// On servers, allow a system-wide fallback when per-user XDG config is absent.
if systemData, systemErr := os.ReadFile(systemConfigPath); systemErr == nil {
data = systemData
err = nil
} else {
return nil, fmt.Errorf("reading config file: %w", err)
}
}
2026-02-26 11:05:16 +01:00
if err != nil {
return nil, fmt.Errorf("reading config file: %w", err)
}
var cfg Config
if err := toml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config file: %w", err)
}
cfg.NetrcPath = expandPath(cfg.NetrcPath)
cfg.ClientSecretFile = expandPath(cfg.ClientSecretFile)
if cfg.ClientSecretFile != "" {
secret, err := os.ReadFile(cfg.ClientSecretFile)
if err != nil {
return nil, fmt.Errorf("reading client_secret_file: %w", err)
}
cfg.ClientSecret = strings.TrimSpace(string(secret))
}
2026-02-26 11:05:16 +01:00
if err := cfg.validate(); err != nil {
return nil, err
}
return &cfg, nil
}
2026-03-02 07:25:06 +01:00
func resolveConfigPath(path string) string {
if path != "" {
return path
}
if envPath := strings.TrimSpace(os.Getenv(configPathEnvVar)); envPath != "" {
return envPath
}
return defaultXDGConfigPath()
}
func defaultXDGConfigPath() string {
return filepath.Join(xdg.ConfigHome, "nix-cache-login", "config.toml")
}
2026-02-26 11:05:16 +01:00
func (c *Config) validate() error {
if c.Issuer == "" {
return fmt.Errorf("config: issuer is required")
}
if c.ClientID == "" {
return fmt.Errorf("config: client_id is required")
}
if c.CacheHost == "" {
return fmt.Errorf("config: cache_host is required")
}
if c.NetrcPath == "" {
return fmt.Errorf("config: netrc_path is required")
}
return nil
}
// expandPath expands environment variables in a path. XDG base directory
// variables are resolved using the xdg library, which applies the XDG spec
// fallbacks (e.g. $HOME/.config when $XDG_CONFIG_HOME is unset). This ensures
// correct behaviour in systemd user services, which do not set XDG variables.
func expandPath(s string) string {
return os.Expand(s, func(key string) string {
switch key {
case "XDG_CONFIG_HOME":
return xdg.ConfigHome
case "XDG_DATA_HOME":
return xdg.DataHome
case "XDG_CACHE_HOME":
return xdg.CacheHome
case "XDG_STATE_HOME":
return xdg.StateHome
case "XDG_RUNTIME_DIR":
return xdg.RuntimeDir
default:
return os.Getenv(key)
}
})
}
2026-02-26 11:05:16 +01:00
// RefreshTokenPath returns the path to the stored refresh token.
func RefreshTokenPath() string {
return filepath.Join(xdg.ConfigHome, "nix-cache-login", "refresh-token")
}