From 76f75e76915329c4f3502abcbea1df8e3ee658c9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Oct 2024 15:28:49 +0200 Subject: [PATCH] nix copy: Add --profile flag This allows `nix copy` to atomically copy a store path and point a profile to it, without the risk that the store path might be GC'ed in between. This is useful for instance when deploying a new NixOS system profile from a remote store. --- src/libcmd/command.cc | 35 ++++++++++++++++++++++------------- src/libcmd/command.hh | 12 ++++++++---- src/nix/build.cc | 2 +- src/nix/copy.cc | 8 +++++--- src/nix/copy.md | 9 +++++++++ src/nix/develop.cc | 2 +- src/nix/profile.cc | 6 +++--- src/nix/realisation.cc | 2 +- tests/functional/zstd.sh | 4 +++- 9 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 6d8bfc19b..e38f982d8 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -179,30 +179,34 @@ BuiltPathsCommand::BuiltPathsCommand(bool recursive) void BuiltPathsCommand::run(ref store, Installables && installables) { - BuiltPaths paths; + BuiltPaths rootPaths, allPaths; + if (all) { if (installables.size()) throw UsageError("'--all' does not expect arguments"); // XXX: Only uses opaque paths, ignores all the realisations for (auto & p : store->queryAllValidPaths()) - paths.emplace_back(BuiltPath::Opaque{p}); + rootPaths.emplace_back(BuiltPath::Opaque{p}); + allPaths = rootPaths; } else { - paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables); + rootPaths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables); + allPaths = rootPaths; + if (recursive) { // XXX: This only computes the store path closure, ignoring // intermediate realisations StorePathSet pathsRoots, pathsClosure; - for (auto & root : paths) { + for (auto & root : rootPaths) { auto rootFromThis = root.outPaths(); pathsRoots.insert(rootFromThis.begin(), rootFromThis.end()); } store->computeFSClosure(pathsRoots, pathsClosure); for (auto & path : pathsClosure) - paths.emplace_back(BuiltPath::Opaque{path}); + allPaths.emplace_back(BuiltPath::Opaque{path}); } } - run(store, std::move(paths)); + run(store, std::move(allPaths), std::move(rootPaths)); } StorePathsCommand::StorePathsCommand(bool recursive) @@ -210,10 +214,10 @@ StorePathsCommand::StorePathsCommand(bool recursive) { } -void StorePathsCommand::run(ref store, BuiltPaths && paths) +void StorePathsCommand::run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths) { StorePathSet storePaths; - for (auto & builtPath : paths) + for (auto & builtPath : allPaths) for (auto & p : builtPath.outPaths()) storePaths.insert(p); @@ -242,17 +246,21 @@ MixProfile::MixProfile() }); } -void MixProfile::updateProfile(const StorePath & storePath) +void MixProfile::updateProfile( + const StorePath & storePath, + ref store_) { if (!profile) return; - auto store = getStore().dynamic_pointer_cast(); + auto store = store_.dynamic_pointer_cast(); if (!store) throw Error("'--profile' is not supported for this Nix store"); auto profile2 = absPath(*profile); switchLink(profile2, createGeneration(*store, profile2, storePath)); } -void MixProfile::updateProfile(const BuiltPaths & buildables) +void MixProfile::updateProfile( + const BuiltPaths & buildables, + ref store) { if (!profile) return; @@ -274,7 +282,7 @@ void MixProfile::updateProfile(const BuiltPaths & buildables) if (result.size() != 1) throw UsageError("'--profile' requires that the arguments produce a single store path, but there are %d", result.size()); - updateProfile(result[0]); + updateProfile(result[0], store); } MixDefaultProfile::MixDefaultProfile() @@ -308,7 +316,8 @@ MixEnvironment::MixEnvironment() : ignoreEnvironment(false) }); } -void MixEnvironment::setEnviron() { +void MixEnvironment::setEnviron() +{ if (ignoreEnvironment) { if (!unset.empty()) throw UsageError("--unset does not make sense with --ignore-environment"); diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 4a72627ed..8ada78782 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -238,7 +238,7 @@ public: BuiltPathsCommand(bool recursive = false); - virtual void run(ref store, BuiltPaths && paths) = 0; + virtual void run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths) = 0; void run(ref store, Installables && installables) override; @@ -251,7 +251,7 @@ struct StorePathsCommand : public BuiltPathsCommand virtual void run(ref store, StorePaths && storePaths) = 0; - void run(ref store, BuiltPaths && paths) override; + void run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths) override; }; /** @@ -301,11 +301,15 @@ struct MixProfile : virtual StoreCommand MixProfile(); /* If 'profile' is set, make it point at 'storePath'. */ - void updateProfile(const StorePath & storePath); + void updateProfile( + const StorePath & storePath, + ref store); /* If 'profile' is set, make it point at the store path produced by 'buildables'. */ - void updateProfile(const BuiltPaths & buildables); + void updateProfile( + const BuiltPaths & buildables, + ref store); }; struct MixDefaultProfile : MixProfile diff --git a/src/nix/build.cc b/src/nix/build.cc index da9132d02..cffbeccb6 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -161,7 +161,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile BuiltPaths buildables2; for (auto & b : buildables) buildables2.push_back(b.path); - updateProfile(buildables2); + updateProfile(buildables2, store); } }; diff --git a/src/nix/copy.cc b/src/nix/copy.cc index 151d28277..c058a7446 100644 --- a/src/nix/copy.cc +++ b/src/nix/copy.cc @@ -4,7 +4,7 @@ using namespace nix; -struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand +struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand, MixProfile { CheckSigsFlag checkSigs = CheckSigs; @@ -43,19 +43,21 @@ struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand Category category() override { return catSecondary; } - void run(ref srcStore, BuiltPaths && paths) override + void run(ref srcStore, BuiltPaths && allPaths, BuiltPaths && rootPaths) override { auto dstStore = getDstStore(); RealisedPath::Set stuffToCopy; - for (auto & builtPath : paths) { + for (auto & builtPath : allPaths) { auto theseRealisations = builtPath.toRealisedPaths(*srcStore); stuffToCopy.insert(theseRealisations.begin(), theseRealisations.end()); } copyPaths( *srcStore, *dstStore, stuffToCopy, NoRepair, checkSigs, substitute); + + updateProfile(rootPaths, dstStore); } }; diff --git a/src/nix/copy.md b/src/nix/copy.md index 6ab7cdee3..813050fcb 100644 --- a/src/nix/copy.md +++ b/src/nix/copy.md @@ -55,6 +55,15 @@ R""( # nix copy --to /tmp/nix nixpkgs#hello --no-check-sigs ``` +* Update the NixOS system profile to point to a closure copied from a + remote machine: + + ```console + # nix copy --from ssh://server \ + --profile /nix/var/nix/profiles/system \ + /nix/store/r14v3km89zm3prwsa521fab5kgzvfbw4-nixos-system-foobar-24.05.20240925.759537f + ``` + # Description `nix copy` copies store path closures between two Nix stores. The diff --git a/src/nix/develop.cc b/src/nix/develop.cc index c7a733025..ca9f6a4af 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -502,7 +502,7 @@ struct Common : InstallableCommand, MixProfile auto strPath = store->printStorePath(shellOutPath); - updateProfile(shellOutPath); + updateProfile(shellOutPath, store); debug("reading environment file '%s'", strPath); diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 324fd6330..ae1f3e6e9 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -424,7 +424,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile } try { - updateProfile(manifest.build(store)); + updateProfile(manifest.build(store), store); } catch (BuildEnvFileConflictError & conflictError) { // FIXME use C++20 std::ranges once macOS has it // See https://github.com/NixOS/nix/compare/3efa476c5439f8f6c1968a6ba20a31d1239c2f04..1fe5d172ece51a619e879c4b86f603d9495cc102 @@ -669,7 +669,7 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem removedCount, newManifest.elements.size()); - updateProfile(newManifest.build(store)); + updateProfile(newManifest.build(store), store); } }; @@ -779,7 +779,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf builtPaths.find(&*installable)->second.first); } - updateProfile(manifest.build(store)); + updateProfile(manifest.build(store), store); } }; diff --git a/src/nix/realisation.cc b/src/nix/realisation.cc index e1f231222..a386d98ea 100644 --- a/src/nix/realisation.cc +++ b/src/nix/realisation.cc @@ -36,7 +36,7 @@ struct CmdRealisationInfo : BuiltPathsCommand, MixJSON Category category() override { return catSecondary; } - void run(ref store, BuiltPaths && paths) override + void run(ref store, BuiltPaths && paths, BuiltPaths && rootPaths) override { experimentalFeatureSettings.require(Xp::CaDerivations); RealisedPath::Set realisations; diff --git a/tests/functional/zstd.sh b/tests/functional/zstd.sh index 90fe58539..cdc30c66e 100755 --- a/tests/functional/zstd.sh +++ b/tests/functional/zstd.sh @@ -18,7 +18,9 @@ HASH=$(nix hash path $outPath) clearStore clearCacheCache -nix copy --from $cacheURI $outPath --no-check-sigs +nix copy --from $cacheURI $outPath --no-check-sigs --profile $TEST_ROOT/profile + +[[ -e $TEST_ROOT/profile ]] if ls $cacheDir/nar/*.zst &> /dev/null; then echo "files do exist"