From 5c25d275095d5b6617fceb6f04ade48bbb570da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 21 Apr 2026 14:35:55 +0200 Subject: [PATCH 1/5] feat: import nixos/config/nix-flakes.nix --- nix/modules/upstream/nixpkgs/default.nix | 1 + testFlake/container-tests/nix-flakes.nix | 52 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 testFlake/container-tests/nix-flakes.nix diff --git a/nix/modules/upstream/nixpkgs/default.nix b/nix/modules/upstream/nixpkgs/default.nix index df9a2b90..47fb8446 100644 --- a/nix/modules/upstream/nixpkgs/default.nix +++ b/nix/modules/upstream/nixpkgs/default.nix @@ -30,6 +30,7 @@ "/services/web-servers/nginx/" # nix settings "/config/nix.nix" + "/config/nix-flakes.nix" "/services/system/userborn.nix" "/system/build.nix" ]; diff --git a/testFlake/container-tests/nix-flakes.nix b/testFlake/container-tests/nix-flakes.nix new file mode 100644 index 00000000..53b7187a --- /dev/null +++ b/testFlake/container-tests/nix-flakes.nix @@ -0,0 +1,52 @@ +{ forEachDistro, ... }: + +forEachDistro "nix-flakes" { + modules = [ + ( + { ... }: + { + nix.enable = true; + nix.registry.example = { + from = { + type = "indirect"; + id = "example"; + }; + to = { + type = "github"; + owner = "foo"; + repo = "bar"; + }; + }; + } + ) + ]; + testScriptFunction = + { toplevel, hostPkgs, ... }: + '' + start_all() + + machine.wait_for_unit("multi-user.target") + + activation_logs = machine.activate() + for line in activation_logs.split("\n"): + assert not "ERROR" in line, line + machine.wait_for_unit("system-manager.target") + + registry_file = machine.file("/etc/nix/registry.json") + + with subtest("/etc/nix/registry.json exists and is valid JSON"): + assert registry_file.exists, "/etc/nix/registry.json should exist" + import json + data = json.loads(registry_file.content) + + with subtest("registry.json contains the example entry"): + assert data["version"] == 2, f"Expected version 2, got: {data}" + flakes = data["flakes"] + example_entries = [f for f in flakes if f["from"].get("id") == "example"] + assert len(example_entries) == 1, f"Expected one 'example' entry, got: {example_entries}" + entry = example_entries[0] + assert entry["to"]["type"] == "github", f"Expected github to, got: {entry}" + assert entry["to"]["owner"] == "foo", f"Expected owner foo, got: {entry}" + assert entry["to"]["repo"] == "bar", f"Expected repo bar, got: {entry}" + ''; +} From f80363f063247634b7ccbb61aee3d505c41b56c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 21 Apr 2026 15:14:07 +0200 Subject: [PATCH 2/5] feat: add nix package to systemPackages when nix.enable --- nix/modules/upstream/nixpkgs/nix.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/modules/upstream/nixpkgs/nix.nix b/nix/modules/upstream/nixpkgs/nix.nix index 3d338355..a9be483a 100644 --- a/nix/modules/upstream/nixpkgs/nix.nix +++ b/nix/modules/upstream/nixpkgs/nix.nix @@ -31,6 +31,7 @@ config = lib.mkIf config.nix.enable { environment.etc."nix/nix.conf".replaceExisting = true; + environment.systemPackages = [ config.nix.package ]; nix.settings.experimental-features = lib.mkDefault [ "nix-command" "flakes" From 2722687a76df0caf92a4839e6f56a0e47fb24439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 21 Apr 2026 15:14:07 +0200 Subject: [PATCH 3/5] feat: import nixos/config/nix-channel.nix --- nix/modules/upstream/nixpkgs/default.nix | 8 ++++++++ testFlake/container-tests/nix-enabled.nix | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/nix/modules/upstream/nixpkgs/default.nix b/nix/modules/upstream/nixpkgs/default.nix index 47fb8446..c5cac4ff 100644 --- a/nix/modules/upstream/nixpkgs/default.nix +++ b/nix/modules/upstream/nixpkgs/default.nix @@ -30,6 +30,7 @@ "/services/web-servers/nginx/" # nix settings "/config/nix.nix" + "/config/nix-channel.nix" "/config/nix-flakes.nix" "/services/system/userborn.nix" "/system/build.nix" @@ -53,5 +54,12 @@ default = ""; }; + # nix-channel.nix just emits a warning in an activation script. + # As we don't support NixOS activation scripts, we just ignore it. + system.activationScripts.no-nix-channel = lib.mkOption { + type = lib.types.raw; + default = ""; + }; + }; } diff --git a/testFlake/container-tests/nix-enabled.nix b/testFlake/container-tests/nix-enabled.nix index 31e3957e..f2cc9c82 100644 --- a/testFlake/container-tests/nix-enabled.nix +++ b/testFlake/container-tests/nix-enabled.nix @@ -37,6 +37,27 @@ forEachDistro "nix-enabled" { assert nix_conf.exists, "/etc/nix/nix.conf should still exist after re-activation" assert nix_conf.contains("flakes"), "nix.conf should still contain flakes" + with subtest("/root/.nix-channels is created with the default channel"): + channels_file = machine.file("/root/.nix-channels") + assert channels_file.exists, "/root/.nix-channels should exist" + channels_content = channels_file.content_string + assert "channels.nixos.org/nixos-unstable" in channels_content, ( + f"Expected nixos-unstable channel, got: {channels_content!r}" + ) + assert "nixos" in channels_content, ( + f"Expected nixos channel name, got: {channels_content!r}" + ) + + with subtest("NIX_PATH is exported in login shell"): + nix_path = machine.succeed("bash --login -c 'echo $NIX_PATH'").strip() + assert "nixpkgs=" in nix_path, f"Expected nixpkgs= entry, got: {nix_path!r}" + assert "/nix/var/nix/profiles/per-user/root/channels" in nix_path, ( + f"Expected per-user root channels path, got: {nix_path!r}" + ) + + with subtest("nix-channel binary is available on PATH"): + machine.succeed("test -e /run/system-manager/sw/bin/nix-channel") + with subtest("Deactivation restores original nix.conf"): machine.succeed("${toplevel}/bin/deactivate") restored_nix_conf = machine.succeed("cat /etc/nix/nix.conf") From e58df8bbc335a7aacd7babfa9904847c2e5e968b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 21 Apr 2026 15:27:15 +0200 Subject: [PATCH 4/5] feat: import nixos/misc/nixpkgs-flake.nix --- nix/modules/upstream/nixpkgs/default.nix | 1 + testFlake/container-tests/nixpkgs-flake.nix | 46 +++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 testFlake/container-tests/nixpkgs-flake.nix diff --git a/nix/modules/upstream/nixpkgs/default.nix b/nix/modules/upstream/nixpkgs/default.nix index c5cac4ff..8375730a 100644 --- a/nix/modules/upstream/nixpkgs/default.nix +++ b/nix/modules/upstream/nixpkgs/default.nix @@ -32,6 +32,7 @@ "/config/nix.nix" "/config/nix-channel.nix" "/config/nix-flakes.nix" + "/misc/nixpkgs-flake.nix" "/services/system/userborn.nix" "/system/build.nix" ]; diff --git a/testFlake/container-tests/nixpkgs-flake.nix b/testFlake/container-tests/nixpkgs-flake.nix new file mode 100644 index 00000000..b5fa5819 --- /dev/null +++ b/testFlake/container-tests/nixpkgs-flake.nix @@ -0,0 +1,46 @@ +{ forEachDistro, nixpkgs, ... }: + +forEachDistro "nixpkgs-flake" { + modules = [ + ( + { ... }: + { + nix.enable = true; + nixpkgs.flake.source = nixpkgs.outPath or (toString nixpkgs); + } + ) + ]; + testScriptFunction = + { toplevel, hostPkgs, ... }: + '' + start_all() + + machine.wait_for_unit("multi-user.target") + + activation_logs = machine.activate() + for line in activation_logs.split("\n"): + assert not "ERROR" in line, line + machine.wait_for_unit("system-manager.target") + + registry_file = machine.file("/etc/nix/registry.json") + + with subtest("/etc/nix/registry.json contains a nixpkgs entry"): + assert registry_file.exists, "/etc/nix/registry.json should exist" + import json + data = json.loads(registry_file.content) + flakes = data["flakes"] + nixpkgs_entries = [f for f in flakes if f["from"].get("id") == "nixpkgs"] + assert len(nixpkgs_entries) == 1, f"Expected one nixpkgs entry, got: {nixpkgs_entries}" + entry = nixpkgs_entries[0] + assert entry["to"]["type"] == "path", f"Expected path to, got: {entry}" + assert "/nix/store/" in entry["to"]["path"], ( + f"Expected a store path, got: {entry['to']['path']!r}" + ) + + with subtest("NIX_PATH contains nixpkgs=flake:nixpkgs"): + nix_path = machine.succeed("bash --login -c 'echo $NIX_PATH'").strip() + assert "nixpkgs=flake:nixpkgs" in nix_path, ( + f"Expected 'nixpkgs=flake:nixpkgs' in NIX_PATH, got: {nix_path!r}" + ) + ''; +} From faa7e89199b91a5c72bee4ffc1e9744b4dd81555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Tue, 21 Apr 2026 16:09:16 +0200 Subject: [PATCH 5/5] feat: import nixos/config/nix-remote-build.nix --- nix/modules/upstream/nixpkgs/default.nix | 1 + .../container-tests/nix-remote-build.nix | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 testFlake/container-tests/nix-remote-build.nix diff --git a/nix/modules/upstream/nixpkgs/default.nix b/nix/modules/upstream/nixpkgs/default.nix index 8375730a..edb64dca 100644 --- a/nix/modules/upstream/nixpkgs/default.nix +++ b/nix/modules/upstream/nixpkgs/default.nix @@ -32,6 +32,7 @@ "/config/nix.nix" "/config/nix-channel.nix" "/config/nix-flakes.nix" + "/config/nix-remote-build.nix" "/misc/nixpkgs-flake.nix" "/services/system/userborn.nix" "/system/build.nix" diff --git a/testFlake/container-tests/nix-remote-build.nix b/testFlake/container-tests/nix-remote-build.nix new file mode 100644 index 00000000..8a1e2848 --- /dev/null +++ b/testFlake/container-tests/nix-remote-build.nix @@ -0,0 +1,62 @@ +{ forEachDistro, ... }: + +forEachDistro "nix-remote-build" { + modules = [ + ( + { ... }: + { + nix.enable = true; + nix.distributedBuilds = true; + nix.buildMachines = [ + { + hostName = "builder.example.org"; + sshUser = "builder"; + sshKey = "/root/.ssh/id_builder"; + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + maxJobs = 4; + speedFactor = 2; + supportedFeatures = [ + "kvm" + "big-parallel" + ]; + } + ]; + } + ) + ]; + testScriptFunction = + { toplevel, hostPkgs, ... }: + '' + start_all() + + machine.wait_for_unit("multi-user.target") + + activation_logs = machine.activate() + for line in activation_logs.split("\n"): + assert not "ERROR" in line, line + machine.wait_for_unit("system-manager.target") + + machines_file = machine.file("/etc/nix/machines") + + with subtest("/etc/nix/machines exists"): + assert machines_file.exists, "/etc/nix/machines should exist" + + with subtest("/etc/nix/machines contains the builder spec"): + content = machines_file.content_string + assert "ssh://builder@builder.example.org" in content, ( + f"Expected ssh://builder@builder.example.org, got: {content!r}" + ) + assert "x86_64-linux,aarch64-linux" in content, ( + f"Expected comma-joined systems, got: {content!r}" + ) + assert "/root/.ssh/id_builder" in content, ( + f"Expected ssh key path, got: {content!r}" + ) + assert "4 2" in content, ( + f"Expected '4 2' for maxJobs and speedFactor, got: {content!r}" + ) + ''; +}