nix/doctor: check nixpkgs provenance

Many issues stems from `<nixpkgs>` not resolving anywhere or having the
wrong version, let's make it awfully obvious again here.

Towards #230.

Change-Id: If630e9616566e77143f028cdcfe0b7f00a1d486d
Signed-off-by: Raito Bezarius <raito@lix.systems>
This commit is contained in:
Raito Bezarius
2026-07-13 16:47:18 +02:00
committed by rootile
parent 08f18285ef
commit 782d967342
5 changed files with 91 additions and 3 deletions
+12
View File
@@ -0,0 +1,12 @@
---
synopsis: "Improve nix doctor"
cls: [5316, 5317, 5318, 5319, 5320]
category: Features
credits: [rootile, raito]
---
The `nix doctor` diagnosics interface now provides a lot more useful information including, but not limited to:
- General system information (OS, Hardware etc)
- Nix Information like Sandbox, Version, Store, State and other directories
- Flake registry
- Search path Information
- Nixpkgs provenance
+39
View File
@@ -113,6 +113,10 @@ struct CmdDoctor : StoreCommand
printInfo("Collecting information about the ambient Nix search paths");
success &= checkAmbientNixSearchPaths();
}
{
printInfo("Collecting information about Nixpkgs provenance");
success &= checkNixpkgsProvenance();
}
if (!success)
throw Exit(2);
@@ -221,6 +225,41 @@ struct CmdDoctor : StoreCommand
checkInfo(fmt("Overridden Nix configuration search path: %s", nixPathS));
}
// Parse all entries one by one to construct the search path.
for (auto entry : evalSettings.nixPath.get()) {
auto elem = SearchPath::Elem::parse(entry);
auto prefix = elem.prefix.s.empty() ? elem.path.s : elem.prefix.s;
searchPathFacts[prefix] = {
.originReference = entry,
};
}
return true;
}
bool checkNixpkgsProvenance()
{
if (!searchPathFacts.contains("nixpkgs")) {
return checkFail(
"Search path does not contain nixpkgs. All evaluations using nixpkgs (including nix-shell) "
"will fail."
);
}
auto entry = searchPathFacts["nixpkgs"];
checkInfo(fmt("Nixpkgs provenance: %s", entry.originReference));
try {
auto nixpkgsVersion = aio().blockOn(runProgram(
"nix-instantiate",
true,
{"--eval", "--raw", "--expr", "(import <nixpkgs> { }).lib.version"},
false
));
checkInfo(fmt("Nixpkgs version: %s", nixpkgsVersion));
} catch (...) {
return checkFail("Failed obtaining the nixpkgs version: nixpkgs is either broken or invalid");
}
return true;
}
-3
View File
@@ -2,9 +2,6 @@ source common.sh
clearStore
# Ensure "fake ssh" remote store works just as legacy fake ssh would.
nix --store ssh-ng://localhost?remote-store=$TEST_ROOT/other-store doctor
# Ensure that store ping trusted works with ssh-ng://
nix --store ssh-ng://localhost?remote-store=$TEST_ROOT/other-store store ping --json | jq -e '.trusted'
+39
View File
@@ -0,0 +1,39 @@
from testlib.fixtures.nix import Nix
import pytest
pytestmark = pytest.mark.no_daemon
@pytest.mark.usefixtures("fake_nixpkgs")
def test_doctor_local_store(nix: Nix):
res = nix.nix(["doctor", "-v"]).run().ok()
out = res.stderr_plain
assert "[PASS] All profiles are gcroots." in out
assert "[PASS] Client protocol matches store protocol." in out
assert "You are trusted" in out
assert "[FAIL] Error: current generation cannot be discovered" in out
@pytest.mark.usefixtures("fake_nixpkgs")
def test_doctor_remote_store(nix: Nix):
res = (
nix.nix(
[
"--store",
f"ssh-ng://localhost?remote-store={nix.env.dirs.test_root}/other-store",
"doctor",
]
)
.run()
.ok()
)
assert "Running checks against store uri ssh-ng://localhost" in res.stderr_plain
@pytest.mark.usefixtures("fake_nixpkgs")
def test_doctor_nixpkgs(nix: Nix):
res = nix.nix(["doctor", "-v"], flake=True).run().ok()
out = res.stderr_plain
assert "[INFO] Nixpkgs provenance: nixpkgs=" in out
@@ -39,6 +39,7 @@ class TestLocalStore:
info = nix.nix(["--store", "./x", "store", "ping", "--json"]).run().json()
assert info["trusted"]
@pytest.mark.usefixtures("fake_nixpkgs")
def test_doctor_shows_trust(self, nix: Nix):
result = nix.nix(["--store", "./x", "doctor"]).run().ok()
assert "You are trusted by" in result.stderr_plain