Compare commits

...

3 commits
v0.2.0 ... main

Author SHA1 Message Date
6a23ddb8cb update lock file
All checks were successful
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
buildbot/nix-effects Build done.
2026-04-08 08:43:16 +02:00
d6d6721c16 stop setting netrc-file 2026-04-08 08:43:08 +02:00
aa4732af7b Fix netrc one-line parsing and enforce dummy login writes
All checks were successful
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
buildbot/nix-effects Build done.
2026-03-03 08:52:31 +01:00
9 changed files with 188 additions and 28 deletions

View file

@ -4,6 +4,22 @@
Changes yet to be released are documented here.
## v0.3.0
- Stop setting `nix.settings.netrc-file` from the Home Manager module
- Document `netrc_path` in `config.toml` as the path operators should wire into their daemon configuration
- Add a module evaluation check to prevent the Home Manager module from reintroducing `nix.settings.netrc-file`
These changes are made to support both cppnix and detsysnix. The latter has [special requirements][additionalnetrcsources] around the `netrc` files.
[additionalnetrcsources]: https://docs.determinate.systems/determinate-nix/#additionalnetrcsources
## v0.2.1
- Fix netrc parsing for one-line entries such as `machine ... login ... password ...`
- Always write netrc entries as one line with a non-empty login (`dummy`)
- Add regression tests for one-line netrc parsing and write format
## v0.2.0
- Improve config discovery for server workflows:

View file

@ -58,6 +58,14 @@ netrc_path = "$XDG_CONFIG_HOME/nix/netrc"
Path values support environment variable expansion (`$VAR` and `${VAR}`).
`netrc_path` is the path this tool writes tokens to.
Configure Nix to read that same path.
This supports both cppnix and detsysnix. The latter has [special
requirements][additionalnetrcsources] around `netrc` files, so set
`additionalNetrcSources` to include the configured `netrc_path`.
## Usage
```bash
@ -78,6 +86,15 @@ Config path resolution order:
The NixOS server module exports `NIX_CACHE_LOGIN_CONFIG` and installs
`/etc/nix-cache-login/config.toml` from `services.nix-cache-login-server.configFile`.
## Module Integration
The Home Manager and NixOS modules in this repo install the package and refresh
services.
Nix and detsysnix daemon configuration stays outside these modules.
Set your daemon to read the `netrc_path` configured in `config.toml`.
## Maintenance
This tool is actively maintained by [Guardian Project](https://guardianproject.info).
@ -92,6 +109,7 @@ For security-related issues, please contact us through our [security policy][sec
[issues]: https://guardianproject.dev/ops/nix-cache-login/issues
[sec]: https://guardianproject.info/contact/
[additionalnetrcsources]: https://docs.determinate.systems/determinate-nix/#additionalnetrcsources
## License

10
flake.lock generated
View file

@ -2,12 +2,12 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1771848320,
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
"revCount": 953160,
"lastModified": 1775423009,
"narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=",
"rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9",
"revCount": 975402,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.953160%2Brev-2fc6539b481e1d2569f25f8799236694180c0993/019c8e05-d2f6-7c7e-9ead-612154b18bfb/source.tar.gz"
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.975402%2Brev-68d8aa3d661f0e6bd5862291b5bb263b2a6595c9/019d657b-b3b7-7288-b3c0-42d420df206b/source.tar.gz"
},
"original": {
"type": "tarball",

View file

@ -36,6 +36,7 @@
'';
doCheck = true;
});
module-checks = import ./module-checks.nix { inherit self pkgs; };
devShell = self.devShells.${pkgs.stdenv.hostPlatform.system}.default;
}
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {

View file

@ -26,7 +26,6 @@ in
};
config = lib.mkIf cfg.enable {
nix.settings.netrc-file = "${config.xdg.configHome}/nix/netrc";
home.packages = [ cfg.package ];
systemd.user.services.nix-cache-login = {
Unit.Description = "Nix cache login - refresh access token";

View file

@ -93,23 +93,34 @@ func parse(path string) ([]entry, error) {
}
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
switch fields[0] {
case "machine":
if current != nil {
entries = append(entries, *current)
}
current = &entry{machine: fields[1]}
case "login":
if current != nil {
current.login = fields[1]
}
case "password":
if current != nil {
current.password = fields[1]
for i := 0; i < len(fields); {
switch fields[i] {
case "machine":
if i+1 >= len(fields) {
i++
continue
}
if current != nil {
entries = append(entries, *current)
}
current = &entry{machine: fields[i+1]}
i += 2
case "login":
if current != nil && i+1 < len(fields) {
current.login = fields[i+1]
i += 2
continue
}
i++
case "password":
if current != nil && i+1 < len(fields) {
current.password = fields[i+1]
i += 2
continue
}
i++
default:
i++
}
}
}
@ -130,11 +141,12 @@ func write(path string, entries []entry) error {
}
var b strings.Builder
for i, e := range entries {
if i > 0 {
b.WriteString("\n")
for _, e := range entries {
login := e.login
if login == "" {
login = "dummy"
}
fmt.Fprintf(&b, "machine %s\nlogin %s\npassword %s\n", e.machine, e.login, e.password)
fmt.Fprintf(&b, "machine %s login %s password %s\n", e.machine, login, e.password)
}
if err := os.WriteFile(path, []byte(b.String()), 0600); err != nil {

View file

@ -3,6 +3,7 @@ package netrc
import (
"os"
"path/filepath"
"strings"
"testing"
)
@ -55,6 +56,28 @@ func TestUpsertUpdateExisting(t *testing.T) {
}
}
func TestUpsertUpdateExistingOneLineEntry(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "netrc")
initial := "machine other.host login dummy password otherpass\nmachine cache.example.com login dummy password oldtoken\n"
if err := os.WriteFile(path, []byte(initial), 0600); err != nil {
t.Fatal(err)
}
if err := Upsert(path, "cache.example.com", "newtoken"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
pw, err := GetPassword(path, "cache.example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pw != "newtoken" {
t.Errorf("password = %q, want %q", pw, "newtoken")
}
}
func TestUpsertAppend(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "netrc")
@ -197,3 +220,29 @@ func TestFilePermissionsCorrected(t *testing.T) {
t.Errorf("file permissions = %o, want 0600", perm)
}
}
func TestWriteUsesOneLineFormatAndDummyLoginFallback(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "netrc")
// Existing one-line entry with missing login should be normalized.
if err := os.WriteFile(path, []byte("machine cache.example.com password oldtoken\n"), 0600); err != nil {
t.Fatal(err)
}
if err := Upsert(path, "cache.example.com", "newtoken"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
content, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read error: %v", err)
}
text := string(content)
if !strings.Contains(text, "machine cache.example.com login dummy password newtoken\n") {
t.Fatalf("unexpected netrc content: %q", text)
}
if strings.Contains(text, "\nlogin ") || strings.Contains(text, "\npassword ") {
t.Fatalf("netrc entry should be written on one line: %q", text)
}
}

65
module-checks.nix Normal file
View file

@ -0,0 +1,65 @@
{ self, pkgs }:
let
lib = pkgs.lib;
fakePackage = pkgs.runCommand "nix-cache-login-fake-package" { } ''
mkdir -p "$out/bin"
touch "$out/bin/nix-cache-login"
chmod +x "$out/bin/nix-cache-login"
'';
hmStubModule =
{ lib, ... }:
{
options = {
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
};
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/home/tester";
};
xdg.configHome = lib.mkOption {
type = lib.types.str;
default = "/home/tester/.config";
};
nix.settings = lib.mkOption {
type = lib.types.attrsOf lib.types.anything;
default = { };
};
systemd.user.services = lib.mkOption {
type = lib.types.attrsOf lib.types.anything;
default = { };
};
systemd.user.timers = lib.mkOption {
type = lib.types.attrsOf lib.types.anything;
default = { };
};
launchd.agents = lib.mkOption {
type = lib.types.attrsOf lib.types.anything;
default = { };
};
};
};
evalHome =
extraConfig:
lib.evalModules {
modules = [
hmStubModule
./home-module.nix
{
services.nix-cache-login.enable = true;
services.nix-cache-login.package = fakePackage;
}
extraConfig
];
};
homeDefault = evalHome { };
in
pkgs.runCommand "nix-cache-login-module-checks" { } ''
test ${lib.escapeShellArg (builtins.toJSON (builtins.hasAttr "netrc-file" homeDefault.config.nix.settings))} = ${lib.escapeShellArg "false"}
test ${lib.escapeShellArg homeDefault.config.systemd.user.services.nix-cache-login.Service.ExecStart} = ${lib.escapeShellArg "${fakePackage}/bin/nix-cache-login refresh"}
touch "$out"
''

View file

@ -6,7 +6,7 @@
buildGoModule {
pname = "nix-cache-login";
version = "0.2.0";
version = "0.3.0";
src = ./.;
# src = fetchgit {
# url = "https://guardianproject.dev/ops/nix-cache-login.git";