aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-13 21:10:04 +0000
committertv <tv@krebsco.de>2026-11-13 22:16:47 +0100
commit75f2fddb65c0d0b7659282a1e210bd2ddb3abefa (patch)
tree980badaab53cad26907a3b4c04ac8ea9ae6de305
parent8dfa3f18d42d852ece3f2d9cf1ae117acccc0b15 (diff)
populate: stop if a flake's URL can't be found
The URLs of flakes went into nix flake lock's and nix flake metadata's arguments as command substitutions, whose failure doesn't stop a script under set -e. After "error: source p is not a directory", nix flake lock ran with an empty URL, which Nix takes for the current directory: on a remote target, the login directory, which ended up in the world-readable store, .ssh included. URLs get assigned first now, so failing to find one stops populate. A phase's unexpected lists what populate is expected not to print, whether it succeeds or not; notdir expects populate not to go on locking.
-rw-r--r--CHANGELOG.md4
-rw-r--r--pkgs/populate/default.nix15
-rw-r--r--tests/scenarios.nix9
-rw-r--r--tests/vm.nix5
4 files changed, 27 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d71c57e..730a9d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+- Drawing an input from a source that isn't a directory stops populate,
+ instead of locking the flake with an empty URL, which Nix takes for the
+ current directory, copying it into the store: on a remote target, the
+ login directory.
- Git can't take a `git` source's `url` or `ref`, or a `pass` source's
`name`, for an option.
- `git` sources also refuse work trees whose `.git` directory contains
diff --git a/pkgs/populate/default.nix b/pkgs/populate/default.nix
index 7b22662..163f1de 100644
--- a/pkgs/populate/default.nix
+++ b/pkgs/populate/default.nix
@@ -383,18 +383,23 @@ let
++ inputChecks from (drop (length parent) path);
# Sets the positional parameters to nix flake lock's arguments for
# drawing the inputs of the flake in source name from other sources.
+ # URLs get assigned first, so failing to find one stops the script,
+ # which a command substitution in an argument wouldn't: Nix would take
+ # the empty URL for the current directory.
overrideArgs = name: /* sh */ ''
set --
${concatMapStrings (x: /* sh */ ''
- set -- "$@" --override-input ${quote (concatStringsSep "/" (drawnPath name x.path))} ${url (check name x.source)}
+ drawn_url=${url (check name x.source)}
+ set -- "$@" --override-input ${quote (concatStringsSep "/" (drawnPath name x.path))} "$drawn_url"
'') (flakeFollows sources.${name})}
'';
# Rewrites the lock file of the flake in a file or git source in place.
rewrite = name: /* sh */ ''
(
${overrideArgs name}
+ own_url=$(flake_url ${quote name})
own_lock "$(readlink -f ${quote "${target.path}/${name}"})"
- nix flake lock "$@" "$(flake_url ${quote name})" >&2
+ nix flake lock "$@" "$own_url" >&2
)
'';
# Derives what the target path of a flake or flake-input source gets from
@@ -489,8 +494,9 @@ let
fi
${optionalString rewriteLock /* sh */ ''
${overrideArgs name}
+ own_url=$(flake_url ${quote name})
own_lock ${quote targetPath}
- nix flake lock "$@" "$(flake_url ${quote name})" >&2
+ nix flake lock "$@" "$own_url" >&2
''}
'' else /* sh */ ''
if test -e ${quote "${dir}/key"} &&
@@ -674,8 +680,9 @@ let
${concatMapStrings (x: /* sh */ ''
set -- "$@" --override-input ${quote (concatStringsSep "/" x.path)} "path:$locks_dir"
'') follows}
+ own_url=$(own_flake_url ${quote name})
if ! nix flake metadata --json --no-write-lock-file "$@" \
- "$(own_flake_url ${quote name})" > "$locks_dir"/${quote name}.json; then
+ "$own_url" > "$locks_dir"/${quote name}.json; then
>&2 printf 'error: source %s draws inputs from other sources, but its flake can'"'"'t be locked\n' \
${quote name}
exit 1
diff --git a/tests/scenarios.nix b/tests/scenarios.nix
index 43b63b4..e01c5a9 100644
--- a/tests/scenarios.nix
+++ b/tests/scenarios.nix
@@ -109,10 +109,12 @@ let
# evaluation fail. env sets environment variables for populate, as
# arguments of env. interrupt ends populate after that many seconds,
# which it's expected not to finish before. output lists what a
- # succeeding populate is expected to print.
+ # succeeding populate is expected to print, and unexpected what populate
+ # is expected not to print, succeeding or not.
phase = {
sources, backup ? false, prune ? false, deploy ? null,
targetPath ? path, env ? "", interrupt ? null, output ? [],
+ unexpected ? [],
expect ? "success", control ? null, controlTargetPath ? null,
check ? "", mutate ? null, checkDeployer ? null, mutateDeployer ? null,
}: let
@@ -147,7 +149,7 @@ let
null
else {
populate = toString (command sources targetPath);
- inherit env interrupt output;
+ inherit env interrupt output unexpected;
expect = if expect == "success" then null else expect.fail;
# No phase gets around checking the canaries.
check = script (check + "\ncanary_check\n");
@@ -1258,6 +1260,9 @@ assert !(krops.isLocalTarget (targets.remote "/"));
p.pipe = "/fixtures/pipe";
};
expect.fail = "error: source p is not a directory";
+ # Locking the flake with an empty URL would lock the current
+ # directory, the login directory on a remote target.
+ unexpected = [ "while fetching the input" ];
}];
})
(s "noinput-nested" {
diff --git a/tests/vm.nix b/tests/vm.nix
index d7b2f60..7c362f5 100644
--- a/tests/vm.nix
+++ b/tests/vm.nix
@@ -158,6 +158,11 @@ in pkgs.testers.runNixOSTest {
f"env HOME=/root {phase['env']} timeout {limit} {phase['populate']} </dev/null 2>&1",
timeout=900,
)
+ for unexpected in phase["unexpected"]:
+ if unexpected in out:
+ raise Exception(
+ f"populate {i} prints {unexpected!r}:\n{out}"
+ )
if phase["interrupt"] is not None:
if status != 124:
raise Exception(f"populate {i} ends on its own:\n{out}")