From c24af42fc0abc35ca5a596aa2f2619193766a961 Mon Sep 17 00:00:00 2001 From: Abel Luck Date: Mon, 2 Mar 2026 07:25:06 +0100 Subject: [PATCH] Release v0.2.0 --- CHANGELOG.md | 10 ++++++ README.md | 10 ++++++ cmd/root.go | 2 +- flake.nix | 1 + internal/config/config.go | 32 +++++++++++++++--- internal/config/config_test.go | 61 ++++++++++++++++++++++++++++++++++ nixos-module-server.nix | 2 ++ nixos-test-server.nix | 39 ++++++++++++++++++++++ package.nix | 2 +- 9 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 nixos-test-server.nix diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a7cca..12afe1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ Changes yet to be released are documented here. +## v0.2.0 + +- Improve config discovery for server workflows: + - honor `$NIX_CACHE_LOGIN_CONFIG` when `--config` is not set + - fall back to `/etc/nix-cache-login/config.toml` when per-user config is absent +- Update `--config` help text to document config path resolution order +- Set `NIX_CACHE_LOGIN_CONFIG` in the NixOS server module so interactive commands (like `status`) use the same config as the timer service +- Install `/etc/nix-cache-login/config.toml` via the NixOS server module from `services.nix-cache-login-server.configFile` +- Add a NixOS server module VM test and wire it into flake checks + ## v0.1.3 - Improve `status` for server use by detecting service-account mode from config diff --git a/README.md b/README.md index 55a9174..e25a567 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,16 @@ nix-cache-login status # show token expiry info nix-cache-login logout # revoke tokens and clean up ``` +Config path resolution order: + +1. `--config` +2. `$NIX_CACHE_LOGIN_CONFIG` +3. `$XDG_CONFIG_HOME/nix-cache-login/config.toml` +4. `/etc/nix-cache-login/config.toml` (server fallback) + +The NixOS server module exports `NIX_CACHE_LOGIN_CONFIG` and installs +`/etc/nix-cache-login/config.toml` from `services.nix-cache-login-server.configFile`. + ## Maintenance This tool is actively maintained by [Guardian Project](https://guardianproject.info). diff --git a/cmd/root.go b/cmd/root.go index 6ca3e48..885f7f5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -42,5 +42,5 @@ func Execute() { } func init() { - rootCmd.PersistentFlags().StringVar(&cfgPath, "config", "", "path to config file (default: $XDG_CONFIG_HOME/nix-cache-login/config.toml)") + rootCmd.PersistentFlags().StringVar(&cfgPath, "config", "", "path to config file (default resolution: --config, $NIX_CACHE_LOGIN_CONFIG, $XDG_CONFIG_HOME/nix-cache-login/config.toml, /etc/nix-cache-login/config.toml)") } diff --git a/flake.nix b/flake.nix index 72df013..48fe69d 100644 --- a/flake.nix +++ b/flake.nix @@ -40,6 +40,7 @@ } // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { nixos-module = pkgs.testers.runNixOSTest (import ./nixos-test.nix self); + nixos-module-server = pkgs.testers.runNixOSTest (import ./nixos-test-server.nix self); } ); diff --git a/internal/config/config.go b/internal/config/config.go index cc37736..7b6a402 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,13 +21,23 @@ type Config struct { ClientSecret string `toml:"-"` } +const configPathEnvVar = "NIX_CACHE_LOGIN_CONFIG" + +var systemConfigPath = "/etc/nix-cache-login/config.toml" + // Load reads the config from the given path, or from the default XDG location. func Load(path string) (*Config, error) { - if path == "" { - path = filepath.Join(xdg.ConfigHome, "nix-cache-login", "config.toml") - } - + path = resolveConfigPath(path) data, err := os.ReadFile(path) + 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) + } + } if err != nil { return nil, fmt.Errorf("reading config file: %w", err) } @@ -55,6 +65,20 @@ func Load(path string) (*Config, error) { return &cfg, nil } +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") +} + func (c *Config) validate() error { if c.Issuer == "" { return fmt.Errorf("config: issuer is required") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b17bb0f..e7445d0 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,6 +4,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/adrg/xdg" ) func TestLoadValidConfig(t *testing.T) { @@ -192,6 +194,65 @@ func TestMissingRequiredFields(t *testing.T) { } } +func TestLoadUsesNixCacheLoginConfigEnvWhenPathEmpty(t *testing.T) { + dir := t.TempDir() + cfgFile := filepath.Join(dir, "custom-config.toml") + + content := ` +issuer = "https://id.example.com/realms/test" +client_id = "nix-cache-server" +cache_host = "cache.example.com" +netrc_path = "/tmp/netrc" +` + if err := os.WriteFile(cfgFile, []byte(content), 0644); err != nil { + t.Fatal(err) + } + + t.Setenv(configPathEnvVar, cfgFile) + cfg, err := Load("") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if cfg.ClientID != "nix-cache-server" { + t.Fatalf("client_id = %q, want %q", cfg.ClientID, "nix-cache-server") + } +} + +func TestLoadFallsBackToSystemConfigPath(t *testing.T) { + dir := t.TempDir() + systemCfg := filepath.Join(dir, "system-config.toml") + systemConfigPathOrig := systemConfigPath + systemConfigPath = systemCfg + defer func() { + systemConfigPath = systemConfigPathOrig + }() + + content := ` +issuer = "https://id.example.com/realms/test" +client_id = "nix-cache-server" +cache_host = "cache.example.com" +netrc_path = "/tmp/netrc" +` + if err := os.WriteFile(systemCfg, []byte(content), 0644); err != nil { + t.Fatal(err) + } + + t.Setenv(configPathEnvVar, "") + origConfigHome := xdg.ConfigHome + xdg.ConfigHome = filepath.Join(dir, "xdg-missing") + defer func() { + xdg.ConfigHome = origConfigHome + }() + + cfg, err := Load("") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if cfg.ClientID != "nix-cache-server" { + t.Fatalf("client_id = %q, want %q", cfg.ClientID, "nix-cache-server") + } +} + func contains(s, substr string) bool { return len(s) >= len(substr) && searchString(s, substr) } diff --git a/nixos-module-server.nix b/nixos-module-server.nix index 85300b9..5df7679 100644 --- a/nixos-module-server.nix +++ b/nixos-module-server.nix @@ -30,6 +30,8 @@ in config = lib.mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; + environment.variables.NIX_CACHE_LOGIN_CONFIG = toString cfg.configFile; + environment.etc."nix-cache-login/config.toml".source = cfg.configFile; systemd.services.nix-cache-login = { description = "Nix cache login - service account token refresh"; serviceConfig = { diff --git a/nixos-test-server.nix b/nixos-test-server.nix new file mode 100644 index 0000000..d3babfd --- /dev/null +++ b/nixos-test-server.nix @@ -0,0 +1,39 @@ +self: { + name = "nix-cache-login-nixos-module-server"; + + nodes.machine = + { pkgs, ... }: + { + imports = [ self.nixosModules.server ]; + services.nix-cache-login-server = { + enable = true; + configFile = pkgs.writeText "nix-cache-login-server-config.toml" '' + issuer = "https://id.example.com/realms/test" + client_id = "nix-cache-server" + client_secret_file = "/run/secrets/nix-cache-client-secret" + cache_host = "cache.example.com" + netrc_path = "/var/lib/nix-cache-login/netrc" + ''; + }; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + + # Module should install system service and timer unit files. + machine.succeed("test -f /etc/systemd/system/nix-cache-login.timer") + machine.succeed("test -f /etc/systemd/system/nix-cache-login.service") + + # wantedBy = ["timers.target"] should create this symlink. + machine.succeed("test -L /etc/systemd/system/timers.target.wants/nix-cache-login.timer") + + # Config should be available at a standard path for interactive commands. + machine.succeed("test -f /etc/nix-cache-login/config.toml") + + # Service unit should invoke service-account mode with explicit --config. + unit = machine.succeed("cat /etc/systemd/system/nix-cache-login.service") + assert "nix-cache-login --config" in unit and "service-account" in unit, ( + f"ExecStart not found in service unit:\n{unit}" + ) + ''; +} diff --git a/package.nix b/package.nix index 8a1d18f..08c08bd 100644 --- a/package.nix +++ b/package.nix @@ -6,7 +6,7 @@ buildGoModule { pname = "nix-cache-login"; - version = "0.1.3"; + version = "0.2.0"; src = ./.; # src = fetchgit { # url = "https://guardianproject.dev/ops/nix-cache-login.git";