diff --git a/doc/manual/rl-next/nix-store-import-export.md b/doc/manual/rl-next/nix-store-import-export.md new file mode 100644 index 000000000..5bd34b28c --- /dev/null +++ b/doc/manual/rl-next/nix-store-import-export.md @@ -0,0 +1,14 @@ +synopsis: Add `nix store import` and `nix store export` +prs: #9474 +issues: #9038 +description: { + +Added a pair of new commands: [`nix store import`] and [`nix store export`]. + +[`nix store export`]: @docroot@/command-ref/new-cli/nix3-store-export.md +[`nix store import`]: @docroot@/command-ref/new-cli/nix3-store-import.md + +} + + + diff --git a/src/nix/store-export.md b/src/nix/store-export.md new file mode 100644 index 000000000..f17510566 --- /dev/null +++ b/src/nix/store-export.md @@ -0,0 +1,15 @@ +R""( + +# Examples + +* Export the closure of a given installable and re-import it in another machine + + ```console + $ nix store export --recursive --format binary nixpkgs#hello > hello-closure.tar + $ ssh user@otherHost nix store import < hello-closure.tar + ``` + +# Description + +This command generates an archive containing the serialisation of *installable*, as well as all the metadata required so that it can be imported with [`nix store import`](@docroot@/command-ref/new-cli/nix3-store-import.md). +)"" diff --git a/src/nix/store-import.cc b/src/nix/store-import.cc new file mode 100644 index 000000000..707b9f792 --- /dev/null +++ b/src/nix/store-import.cc @@ -0,0 +1,88 @@ +#include "command.hh" +#include "store-api.hh" + +using namespace nix; + +struct MixImportExport : virtual Args +{ + enum struct ArchiveFormat + { + Binary, + }; + + std::optional format = std::nullopt; + MixImportExport() + { + addFlag( + {.longName = "format", + .description = R"( + Format of the archive. + The only supported format is `binary`, which corresponds to the format used by [`nix-store --export`](@docroot@/command-ref/nix-store/export.md). + )", + .labels = {"format"}, + .handler = {[&](std::string_view arg) { + if (arg == "binary") + format = ArchiveFormat::Binary; + else + throw Error("Unknown archive format: %s", arg); + }}}); + } + +}; + +struct CmdStoreExport : StorePathsCommand, MixImportExport +{ + std::string description() override + { + return "Export the given store path(s) in a way that can be imported by `nix store import`."; + } + + std::string doc() override + { + return + #include "store-export.md" + ; + } + + void run(ref store, StorePaths && storePaths) override + { + + // We don't use the format parameter now, but we still want to enforce it + // being specified to not block us on backwards-compatibility. + if (!format) + throw UsageError("`--format` must be specified"); + + StorePathSet pathsAsSet; + pathsAsSet.insert(storePaths.begin(), storePaths.end()); + FdSink sink(STDOUT_FILENO); + store->exportPaths(pathsAsSet, sink); + sink.flush(); + } +}; + +struct CmdStoreImport : StoreCommand, MixImportExport +{ + std::string description() override + { + return "Import the given store path(s) from a file created by `nix store export`."; + } + std::string doc() override + { + return + #include "store-import.md" + ; + } + + void run(ref store) override + { + FdSource source(STDIN_FILENO); + auto paths = store->importPaths(source, NoCheckSigs); + + for (auto & path : paths) + logger->cout("%s", store->printStorePath(path)); + } + +}; + +static auto rStoreExport = registerCommand2({"store", "export"}); +static auto rStoreImport = registerCommand2({"store", "import"}); diff --git a/src/nix/store-import.md b/src/nix/store-import.md new file mode 100644 index 000000000..b832adfd7 --- /dev/null +++ b/src/nix/store-import.md @@ -0,0 +1,16 @@ +R""( + +# Examples + +* Import a closure that has been exported from another machine + + ```console + $ ssh user@otherHost nix store export --recursive --format binary nixpkgs#hello > hello-closure.tar + $ nix store import < hello-closure.tar + ``` + +# Description + +This command reads an archive of store paths, as produced by [`nix store export`](@docroot@/command-ref/new-cli/nix3-store-export.md), and adds it to the store. + +)"" diff --git a/tests/functional/local.mk b/tests/functional/local.mk index 8d584142a..fd62fe779 100644 --- a/tests/functional/local.mk +++ b/tests/functional/local.mk @@ -121,6 +121,7 @@ nix_tests = \ flakes/show.sh \ impure-derivations.sh \ path-from-hash-part.sh \ + store-import-export.sh \ path-info.sh \ toString-path.sh \ read-only-store.sh \ diff --git a/tests/functional/store-import-export.sh b/tests/functional/store-import-export.sh new file mode 100644 index 000000000..325f0ae3f --- /dev/null +++ b/tests/functional/store-import-export.sh @@ -0,0 +1,24 @@ +source config.sh + +clearStore + +BUILT_STORE_PATHS=$(nix build -f ./dependencies.nix input1_drv input2_drv --no-link --print-out-paths | sort) + +# Make sure that we require the `--format` argument. +expect 1 nix store export --recursive $BUILT_STORE_PATHS > "$TEST_ROOT/store-export" 2> /dev/null || \ + fail "nix store export should require the --format argument" +nix store export --format binary --recursive $BUILT_STORE_PATHS > "$TEST_ROOT/store-export" + +clearStore +IMPORTED_STORE_PATHS=$(nix store import < "$TEST_ROOT/store-export" | sort) + +# Make sure that the paths we built previously are still valid. +for BUILT_STORE_PATH in $BUILT_STORE_PATHS; do + nix path-info "$BUILT_STORE_PATH" || \ + fail "path $BUILT_STORE_PATH should have been imported but isn't valid" +done +# Make sure that all the imported paths are valid. +for IMPORTED_STORE_PATH in $IMPORTED_STORE_PATHS; do + nix path-info "$IMPORTED_STORE_PATH" || + fail "path $BUILT_STORE_PATH should have been imported but isn't valid" +done