aboutsummaryrefslogtreecommitdiff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/add-to-store.cc2
-rw-r--r--src/nix/build.cc6
-rw-r--r--src/nix/bundle.cc6
-rw-r--r--src/nix/command.cc14
-rw-r--r--src/nix/command.hh4
-rw-r--r--src/nix/copy.cc8
-rw-r--r--src/nix/daemon.cc361
-rw-r--r--src/nix/daemon.md21
-rw-r--r--src/nix/develop.cc16
-rw-r--r--src/nix/eval.cc6
-rw-r--r--src/nix/flake-prefetch.md28
-rw-r--r--src/nix/flake.cc48
-rw-r--r--src/nix/hash.cc24
-rw-r--r--src/nix/installables.cc26
-rw-r--r--src/nix/key-convert-secret-to-public.md19
-rw-r--r--src/nix/key-generate-secret.md48
-rw-r--r--src/nix/local.mk1
-rw-r--r--src/nix/ls.cc6
-rw-r--r--src/nix/main.cc19
-rw-r--r--src/nix/path-info.cc8
-rw-r--r--src/nix/prefetch.cc319
-rw-r--r--src/nix/profile-history.md26
-rw-r--r--src/nix/profile-list.md (renamed from src/nix/profile-info.md)2
-rw-r--r--src/nix/profile.cc125
-rw-r--r--src/nix/run.cc2
-rw-r--r--src/nix/show-derivation.cc2
-rw-r--r--src/nix/sigs.cc95
-rw-r--r--src/nix/store-delete.cc44
-rw-r--r--src/nix/store-delete.md24
-rw-r--r--src/nix/store-gc.cc43
-rw-r--r--src/nix/store-gc.md21
-rw-r--r--src/nix/store-prefetch-file.md32
-rw-r--r--src/nix/store-repair.cc27
-rw-r--r--src/nix/store-repair.md32
-rw-r--r--src/nix/upgrade-nix.cc4
-rw-r--r--src/nix/verify.cc16
-rw-r--r--src/nix/why-depends.cc2
37 files changed, 1389 insertions, 98 deletions
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc
index ea4bbbab9..2ae042789 100644
--- a/src/nix/add-to-store.cc
+++ b/src/nix/add-to-store.cc
@@ -19,7 +19,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand
addFlag({
.longName = "name",
.shortName = 'n',
- .description = "name component of the store path",
+ .description = "Override the name component of the store path. It defaults to the base name of *path*.",
.labels = {"name"},
.handler = {&namePart},
});
diff --git a/src/nix/build.cc b/src/nix/build.cc
index c2974d983..4cb8ade08 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -19,7 +19,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
addFlag({
.longName = "out-link",
.shortName = 'o',
- .description = "path of the symlink to the build result",
+ .description = "Use *path* as prefix for the symlinks to the build results. It defaults to `result`.",
.labels = {"path"},
.handler = {&outLink},
.completer = completePath
@@ -27,13 +27,13 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
addFlag({
.longName = "no-link",
- .description = "do not create a symlink to the build result",
+ .description = "Do not create symlinks to the build results.",
.handler = {&outLink, Path("")},
});
addFlag({
.longName = "rebuild",
- .description = "rebuild an already built package and compare the result to the existing store paths",
+ .description = "Rebuild an already built package and compare the result to the existing store paths.",
.handler = {&buildMode, bmCheck},
});
}
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 5f558b01e..1789e4598 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -16,7 +16,7 @@ struct CmdBundle : InstallableCommand
{
addFlag({
.longName = "bundler",
- .description = "use custom bundler",
+ .description = fmt("Use a custom bundler instead of the default (`%s`).", bundler),
.labels = {"flake-url"},
.handler = {&bundler},
.completer = {[&](size_t, std::string_view prefix) {
@@ -27,7 +27,7 @@ struct CmdBundle : InstallableCommand
addFlag({
.longName = "out-link",
.shortName = 'o',
- .description = "path of the symlink to the build result",
+ .description = "Override the name of the symlink to the build result. It defaults to the base name of the app.",
.labels = {"path"},
.handler = {&outLink},
.completer = completePath
@@ -90,7 +90,7 @@ struct CmdBundle : InstallableCommand
mkString(*evalState->allocAttr(*arg, evalState->symbols.create("system")), settings.thisSystem.get());
arg->attrs->sort();
-
+
auto vRes = evalState->allocValue();
evalState->callFunction(*bundler.toValue(*evalState).first, *arg, *vRes, noPos);
diff --git a/src/nix/command.cc b/src/nix/command.cc
index 596217775..ba58c7d6b 100644
--- a/src/nix/command.cc
+++ b/src/nix/command.cc
@@ -65,18 +65,18 @@ StorePathsCommand::StorePathsCommand(bool recursive)
if (recursive)
addFlag({
.longName = "no-recursive",
- .description = "apply operation to specified paths only",
+ .description = "Apply operation to specified paths only.",
.handler = {&this->recursive, false},
});
else
addFlag({
.longName = "recursive",
.shortName = 'r',
- .description = "apply operation to closure of the specified paths",
+ .description = "Apply operation to closure of the specified paths.",
.handler = {&this->recursive, true},
});
- mkFlag(0, "all", "apply operation to the entire store", &all);
+ mkFlag(0, "all", "Apply the operation to every store path.", &all);
}
void StorePathsCommand::run(ref<Store> store)
@@ -133,7 +133,7 @@ MixProfile::MixProfile()
{
addFlag({
.longName = "profile",
- .description = "profile to update",
+ .description = "The profile to update.",
.labels = {"path"},
.handler = {&profile},
.completer = completePath
@@ -190,14 +190,14 @@ MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
addFlag({
.longName = "ignore-environment",
.shortName = 'i',
- .description = "clear the entire environment (except those specified with --keep)",
+ .description = "Clear the entire environment (except those specified with `--keep`).",
.handler = {&ignoreEnvironment, true},
});
addFlag({
.longName = "keep",
.shortName = 'k',
- .description = "keep specified environment variable",
+ .description = "Keep the environment variable *name*.",
.labels = {"name"},
.handler = {[&](std::string s) { keep.insert(s); }},
});
@@ -205,7 +205,7 @@ MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
addFlag({
.longName = "unset",
.shortName = 'u',
- .description = "unset specified environment variable",
+ .description = "Unset the environment variable *name*.",
.labels = {"name"},
.handler = {[&](std::string s) { unset.insert(s); }},
});
diff --git a/src/nix/command.hh b/src/nix/command.hh
index 6882db195..f325cd906 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -13,6 +13,8 @@ namespace nix {
extern std::string programPath;
+extern char * * savedArgv;
+
class EvalState;
struct Pos;
class Store;
@@ -261,6 +263,8 @@ void completeFlakeRefWithFragment(
const Strings & defaultFlakeAttrPaths,
std::string_view prefix);
+std::string showVersions(const std::set<std::string> & versions);
+
void printClosureDiff(
ref<Store> store,
const StorePath & beforePath,
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index 2394eb46d..f15031a45 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -21,28 +21,28 @@ struct CmdCopy : StorePathsCommand
{
addFlag({
.longName = "from",
- .description = "URI of the source Nix store",
+ .description = "URL of the source Nix store.",
.labels = {"store-uri"},
.handler = {&srcUri},
});
addFlag({
.longName = "to",
- .description = "URI of the destination Nix store",
+ .description = "URL of the destination Nix store.",
.labels = {"store-uri"},
.handler = {&dstUri},
});
addFlag({
.longName = "no-check-sigs",
- .description = "do not require that paths are signed by trusted keys",
+ .description = "Do not require that paths are signed by trusted keys.",
.handler = {&checkSigs, NoCheckSigs},
});
addFlag({
.longName = "substitute-on-destination",
.shortName = 's',
- .description = "whether to try substitutes on the destination store (only supported by SSH)",
+ .description = "Whether to try substitutes on the destination store (only supported by SSH stores).",
.handler = {&substitute, Substitute},
});
diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc
new file mode 100644
index 000000000..204d4ce6b
--- /dev/null
+++ b/src/nix/daemon.cc
@@ -0,0 +1,361 @@
+#include "command.hh"
+#include "shared.hh"
+#include "local-store.hh"
+#include "remote-store.hh"
+#include "util.hh"
+#include "serialise.hh"
+#include "archive.hh"
+#include "globals.hh"
+#include "derivations.hh"
+#include "finally.hh"
+#include "../nix/legacy.hh"
+#include "daemon.hh"
+
+#include <algorithm>
+#include <climits>
+#include <cstring>
+
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+
+#if __APPLE__ || __FreeBSD__
+#include <sys/ucred.h>
+#endif
+
+using namespace nix;
+using namespace nix::daemon;
+
+#ifndef __linux__
+#define SPLICE_F_MOVE 0
+static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags)
+{
+ // We ignore most parameters, we just have them for conformance with the linux syscall
+ std::vector<char> buf(8192);
+ auto read_count = read(fd_in, buf.data(), buf.size());
+ if (read_count == -1)
+ return read_count;
+ auto write_count = decltype(read_count)(0);
+ while (write_count < read_count) {
+ auto res = write(fd_out, buf.data() + write_count, read_count - write_count);
+ if (res == -1)
+ return res;
+ write_count += res;
+ }
+ return read_count;
+}
+#endif
+
+
+static void sigChldHandler(int sigNo)
+{
+ // Ensure we don't modify errno of whatever we've interrupted
+ auto saved_errno = errno;
+ // Reap all dead children.
+ while (waitpid(-1, 0, WNOHANG) > 0) ;
+ errno = saved_errno;
+}
+
+
+static void setSigChldAction(bool autoReap)
+{
+ struct sigaction act, oact;
+ act.sa_handler = autoReap ? sigChldHandler : SIG_DFL;
+ sigfillset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGCHLD, &act, &oact))
+ throw SysError("setting SIGCHLD handler");
+}
+
+
+bool matchUser(const string & user, const string & group, const Strings & users)
+{
+ if (find(users.begin(), users.end(), "*") != users.end())
+ return true;
+
+ if (find(users.begin(), users.end(), user) != users.end())
+ return true;
+
+ for (auto & i : users)
+ if (string(i, 0, 1) == "@") {
+ if (group == string(i, 1)) return true;
+ struct group * gr = getgrnam(i.c_str() + 1);
+ if (!gr) continue;
+ for (char * * mem = gr->gr_mem; *mem; mem++)
+ if (user == string(*mem)) return true;
+ }
+
+ return false;
+}
+
+
+struct PeerInfo
+{
+ bool pidKnown;
+ pid_t pid;
+ bool uidKnown;
+ uid_t uid;
+ bool gidKnown;
+ gid_t gid;
+};
+
+
+// Get the identity of the caller, if possible.
+static PeerInfo getPeerInfo(int remote)
+{
+ PeerInfo peer = { false, 0, false, 0, false, 0 };
+
+#if defined(SO_PEERCRED)
+
+ ucred cred;
+ socklen_t credLen = sizeof(cred);
+ if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
+ throw SysError("getting peer credentials");
+ peer = { true, cred.pid, true, cred.uid, true, cred.gid };
+
+#elif defined(LOCAL_PEERCRED)
+
+#if !defined(SOL_LOCAL)
+#define SOL_LOCAL 0
+#endif
+
+ xucred cred;
+ socklen_t credLen = sizeof(cred);
+ if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == -1)
+ throw SysError("getting peer credentials");
+ peer = { false, 0, true, cred.cr_uid, false, 0 };
+
+#endif
+
+ return peer;
+}
+
+
+#define SD_LISTEN_FDS_START 3
+
+
+static ref<Store> openUncachedStore()
+{
+ Store::Params params; // FIXME: get params from somewhere
+ // Disable caching since the client already does that.
+ params["path-info-cache-size"] = "0";
+ return openStore(settings.storeUri, params);
+}
+
+
+static void daemonLoop()
+{
+ if (chdir("/") == -1)
+ throw SysError("cannot change current directory");
+
+ // Get rid of children automatically; don't let them become zombies.
+ setSigChldAction(true);
+
+ AutoCloseFD fdSocket;
+
+ // Handle socket-based activation by systemd.
+ auto listenFds = getEnv("LISTEN_FDS");
+ if (listenFds) {
+ if (getEnv("LISTEN_PID") != std::to_string(getpid()) || listenFds != "1")
+ throw Error("unexpected systemd environment variables");
+ fdSocket = SD_LISTEN_FDS_START;
+ closeOnExec(fdSocket.get());
+ }
+
+ // Otherwise, create and bind to a Unix domain socket.
+ else {
+ createDirs(dirOf(settings.nixDaemonSocketFile));
+ fdSocket = createUnixDomainSocket(settings.nixDaemonSocketFile, 0666);
+ }
+
+ // Loop accepting connections.
+ while (1) {
+
+ try {
+ // Accept a connection.
+ struct sockaddr_un remoteAddr;
+ socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+ AutoCloseFD remote = accept(fdSocket.get(),
+ (struct sockaddr *) &remoteAddr, &remoteAddrLen);
+ checkInterrupt();
+ if (!remote) {
+ if (errno == EINTR) continue;
+ throw SysError("accepting connection");
+ }
+
+ closeOnExec(remote.get());
+
+ TrustedFlag trusted = NotTrusted;
+ PeerInfo peer = getPeerInfo(remote.get());
+
+ struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
+ string user = pw ? pw->pw_name : std::to_string(peer.uid);
+
+ struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
+ string group = gr ? gr->gr_name : std::to_string(peer.gid);
+
+ Strings trustedUsers = settings.trustedUsers;
+ Strings allowedUsers = settings.allowedUsers;
+
+ if (matchUser(user, group, trustedUsers))
+ trusted = Trusted;
+
+ if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
+ throw Error("user '%1%' is not allowed to connect to the Nix daemon", user);
+
+ printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""))
+ % (peer.pidKnown ? std::to_string(peer.pid) : "<unknown>")
+ % (peer.uidKnown ? user : "<unknown>"));
+
+ // Fork a child to handle the connection.
+ ProcessOptions options;
+ options.errorPrefix = "unexpected Nix daemon error: ";
+ options.dieWithParent = false;
+ options.runExitHandlers = true;
+ options.allowVfork = false;
+ startProcess([&]() {
+ fdSocket = -1;
+
+ // Background the daemon.
+ if (setsid() == -1)
+ throw SysError("creating a new session");
+
+ // Restore normal handling of SIGCHLD.
+ setSigChldAction(false);
+
+ // For debugging, stuff the pid into argv[1].
+ if (peer.pidKnown && savedArgv[1]) {
+ string processName = std::to_string(peer.pid);
+ strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1]));
+ }
+
+ // Handle the connection.
+ FdSource from(remote.get());
+ FdSink to(remote.get());
+ processConnection(openUncachedStore(), from, to, trusted, NotRecursive, [&](Store & store) {
+#if 0
+ /* Prevent users from doing something very dangerous. */
+ if (geteuid() == 0 &&
+ querySetting("build-users-group", "") == "")
+ throw Error("if you run 'nix-daemon' as root, then you MUST set 'build-users-group'!");
+#endif
+ store.createUser(user, peer.uid);
+ });
+
+ exit(0);
+ }, options);
+
+ } catch (Interrupted & e) {
+ return;
+ } catch (Error & error) {
+ ErrorInfo ei = error.info();
+ ei.hint = std::optional(hintfmt("error processing connection: %1%",
+ (error.info().hint.has_value() ? error.info().hint->str() : "")));
+ logError(ei);
+ }
+ }
+}
+
+static void runDaemon(bool stdio)
+{
+ if (stdio) {
+ if (auto store = openUncachedStore().dynamic_pointer_cast<RemoteStore>()) {
+ auto conn = store->openConnectionWrapper();
+ int from = conn->from.fd;
+ int to = conn->to.fd;
+
+ auto nfds = std::max(from, STDIN_FILENO) + 1;
+ while (true) {
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(from, &fds);
+ FD_SET(STDIN_FILENO, &fds);
+ if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1)
+ throw SysError("waiting for data from client or server");
+ if (FD_ISSET(from, &fds)) {
+ auto res = splice(from, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
+ if (res == -1)
+ throw SysError("splicing data from daemon socket to stdout");
+ else if (res == 0)
+ throw EndOfFile("unexpected EOF from daemon socket");
+ }
+ if (FD_ISSET(STDIN_FILENO, &fds)) {
+ auto res = splice(STDIN_FILENO, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
+ if (res == -1)
+ throw SysError("splicing data from stdin to daemon socket");
+ else if (res == 0)
+ return;
+ }
+ }
+ } else {
+ FdSource from(STDIN_FILENO);
+ FdSink to(STDOUT_FILENO);
+ /* Auth hook is empty because in this mode we blindly trust the
+ standard streams. Limiting access to those is explicitly
+ not `nix-daemon`'s responsibility. */
+ processConnection(openUncachedStore(), from, to, Trusted, NotRecursive, [&](Store & _){});
+ }
+ } else
+ daemonLoop();
+}
+
+static int main_nix_daemon(int argc, char * * argv)
+{
+ {
+ auto stdio = false;
+
+ parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+ if (*arg == "--daemon")
+ ; // ignored for backwards compatibility
+ else if (*arg == "--help")
+ showManPage("nix-daemon");
+ else if (*arg == "--version")
+ printVersion("nix-daemon");
+ else if (*arg == "--stdio")
+ stdio = true;
+ else return false;
+ return true;
+ });
+
+ initPlugins();
+
+ runDaemon(stdio);
+
+ return 0;
+ }
+}
+
+static RegisterLegacyCommand r_nix_daemon("nix-daemon", main_nix_daemon);
+
+struct CmdDaemon : StoreCommand
+{
+ std::string description() override
+ {
+ return "daemon to perform store operations on behalf of non-root clients";
+ }
+
+ Category category() override { return catUtility; }
+
+ std::string doc() override
+ {
+ return
+ #include "daemon.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ runDaemon(false);
+ }
+};
+
+static auto rCmdDaemon = registerCommand2<CmdDaemon>({"daemon"});
diff --git a/src/nix/daemon.md b/src/nix/daemon.md
new file mode 100644
index 000000000..e97016a94
--- /dev/null
+++ b/src/nix/daemon.md
@@ -0,0 +1,21 @@
+R""(
+
+# Example
+
+* Run the daemon in the foreground:
+
+ ```console
+ # nix daemon
+ ```
+
+# Description
+
+This command runs the Nix daemon, which is a required component in
+multi-user Nix installations. It performs build actions and other
+operations on the Nix store on behalf of non-root users. Usually you
+don't run the daemon directly; instead it's managed by a service
+management framework such as `systemd`.
+
+Note that this daemon does not fork into the background.
+
+)""
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index edd87f246..578258394 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -204,7 +204,7 @@ struct Common : InstallableCommand, MixProfile
{
addFlag({
.longName = "redirect",
- .description = "redirect a store path to a mutable location",
+ .description = "Redirect a store path to a mutable location.",
.labels = {"installable", "outputs-dir"},
.handler = {[&](std::string installable, std::string outputsDir) {
redirects.push_back({installable, outputsDir});
@@ -334,7 +334,7 @@ struct CmdDevelop : Common, MixEnvironment
addFlag({
.longName = "command",
.shortName = 'c',
- .description = "command and arguments to be executed instead of an interactive shell",
+ .description = "Instead of starting an interactive shell, start the specified command and arguments.",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty()) throw UsageError("--command requires at least one argument");
@@ -344,38 +344,38 @@ struct CmdDevelop : Common, MixEnvironment
addFlag({
.longName = "phase",
- .description = "phase to run (e.g. `build` or `configure`)",
+ .description = "The stdenv phase to run (e.g. `build` or `configure`).",
.labels = {"phase-name"},
.handler = {&phase},
});
addFlag({
.longName = "configure",
- .description = "run the configure phase",
+ .description = "Run the `configure` phase.",
.handler = {&phase, {"configure"}},
});
addFlag({
.longName = "build",
- .description = "run the build phase",
+ .description = "Run the `build` phase.",
.handler = {&phase, {"build"}},
});
addFlag({
.longName = "check",
- .description = "run the check phase",
+ .description = "Run the `check` phase.",
.handler = {&phase, {"check"}},
});
addFlag({
.longName = "install",
- .description = "run the install phase",
+ .description = "Run the `install` phase.",
.handler = {&phase, {"install"}},
});
addFlag({
.longName = "installcheck",
- .description = "run the installcheck phase",
+ .description = "Run the `installcheck` phase.",
.handler = {&phase, {"installCheck"}},
});
}
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 321df7495..b5049ac65 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -18,18 +18,18 @@ struct CmdEval : MixJSON, InstallableCommand
CmdEval()
{
- mkFlag(0, "raw", "print strings unquoted", &raw);
+ mkFlag(0, "raw", "Print strings without quotes or escaping.", &raw);
addFlag({
.longName = "apply",
- .description = "apply a function to each argument",
+ .description = "Apply the function *expr* to each argument.",
.labels = {"expr"},
.handler = {&apply},
});
addFlag({
.longName = "write-to",
- .description = "write a string or attrset of strings to 'path'",
+ .description = "Write a string or attrset of strings to *path*.",
.labels = {"path"},
.handler = {&writeTo},
});
diff --git a/src/nix/flake-prefetch.md b/src/nix/flake-prefetch.md
new file mode 100644
index 000000000..a1cf0289a
--- /dev/null
+++ b/src/nix/flake-prefetch.md
@@ -0,0 +1,28 @@
+R""(
+
+# Examples
+
+* Download a tarball and unpack it:
+
+ ```console
+ # nix flake prefetch https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.5.tar.xz
+ Downloaded 'https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.5.tar.xz?narHash=sha256-3XYHZANT6AFBV0BqegkAZHbba6oeDkIUCDwbATLMhAY='
+ to '/nix/store/sl5vvk8mb4ma1sjyy03kwpvkz50hd22d-source' (hash
+ 'sha256-3XYHZANT6AFBV0BqegkAZHbba6oeDkIUCDwbATLMhAY=').
+ ```
+
+* Download the `dwarffs` flake (looked up in the flake registry):
+
+ ```console
+ # nix flake prefetch dwarffs --json
+ {"hash":"sha256-VHg3MYVgQ12LeRSU2PSoDeKlSPD8PYYEFxxwkVVDRd0="
+ ,"storePath":"/nix/store/hang3792qwdmm2n0d9nsrs5n6bsws6kv-source"}
+ ```
+
+# Description
+
+This command downloads the source tree denoted by flake reference
+*flake-url*. Note that this does not need to be a flake (i.e. it does
+not have to contain a `flake.nix` file).
+
+)""
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 2b91faa64..4cd7d77a0 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -222,7 +222,7 @@ struct CmdFlakeCheck : FlakeCommand
{
addFlag({
.longName = "no-build",
- .description = "do not build checks",
+ .description = "Do not build checks.",
.handler = {&build, false}
});
}
@@ -573,7 +573,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
addFlag({
.longName = "template",
.shortName = 't',
- .description = "the template to use",
+ .description = "The template to use.",
.labels = {"template"},
.handler = {&templateUrl},
.completer = {[&](size_t, std::string_view prefix) {
@@ -717,7 +717,7 @@ struct CmdFlakeClone : FlakeCommand
addFlag({
.longName = "dest",
.shortName = 'f',
- .description = "destination path",
+ .description = "Clone the flake to path *dest*.",
.labels = {"path"},
.handler = {&destDir}
});
@@ -807,7 +807,7 @@ struct CmdFlakeShow : FlakeCommand
{
addFlag({
.longName = "legacy",
- .description = "show the contents of the 'legacyPackages' output",
+ .description = "Show the contents of the `legacyPackages` output.",
.handler = {&showLegacy, true}
});
}
@@ -960,6 +960,45 @@ struct CmdFlakeShow : FlakeCommand
}
};
+struct CmdFlakePrefetch : FlakeCommand, MixJSON
+{
+ CmdFlakePrefetch()
+ {
+ }
+
+ std::string description() override
+ {
+ return "download the source tree denoted by a flake reference into the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "flake-prefetch.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto originalRef = getFlakeRef();
+ auto resolvedRef = originalRef.resolve(store);
+ auto [tree, lockedRef] = resolvedRef.fetchTree(store);
+ auto hash = store->queryPathInfo(tree.storePath)->narHash;
+
+ if (json) {
+ auto res = nlohmann::json::object();
+ res["storePath"] = store->printStorePath(tree.storePath);
+ res["hash"] = hash.to_string(SRI, true);
+ logger->cout(res.dump());
+ } else {
+ notice("Downloaded '%s' to '%s' (hash '%s').",
+ lockedRef.to_string(),
+ store->printStorePath(tree.storePath),
+ hash.to_string(SRI, true));
+ }
+ }
+};
+
struct CmdFlake : NixMultiCommand
{
CmdFlake()
@@ -973,6 +1012,7 @@ struct CmdFlake : NixMultiCommand
{"clone", []() { return make_ref<CmdFlakeClone>(); }},
{"archive", []() { return make_ref<CmdFlakeArchive>(); }},
{"show", []() { return make_ref<CmdFlakeShow>(); }},
+ {"prefetch", []() { return make_ref<CmdFlakePrefetch>(); }},
})
{
}
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 101b67e6a..79d506ace 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -19,15 +19,15 @@ struct CmdHashBase : Command
CmdHashBase(FileIngestionMethod mode) : mode(mode)
{
- mkFlag(0, "sri", "print hash in SRI format", &base, SRI);
- mkFlag(0, "base64", "print hash in base-64", &base, Base64);
- mkFlag(0, "base32", "print hash in base-32 (Nix-specific)", &base, Base32);
- mkFlag(0, "base16", "print hash in base-16", &base, Base16);
+ mkFlag(0, "sri", "Print the hash in SRI format.", &base, SRI);
+ mkFlag(0, "base64", "Print the hash in base-64 format.", &base, Base64);
+ mkFlag(0, "base32", "Print the hash in base-32 (Nix-specific) format.", &base, Base32);
+ mkFlag(0, "base16", "Print the hash in base-16 format.", &base, Base16);
addFlag(Flag::mkHashTypeFlag("type", &ht));
#if 0
mkFlag()
.longName("modulo")
- .description("compute hash modulo specified string")
+ .description("Compute the hash modulo specified the string.")
.labels({"modulus"})
.dest(&modulus);
#endif
@@ -40,15 +40,14 @@ struct CmdHashBase : Command
std::string description() override
{
- const char* d;
switch (mode) {
case FileIngestionMethod::Flat:
- d = "print cryptographic hash of a regular file";
- break;
+ return "print cryptographic hash of a regular file";
case FileIngestionMethod::Recursive:
- d = "print cryptographic hash of the NAR serialisation of a path";
+ return "print cryptographic hash of the NAR serialisation of a path";
+ default:
+ assert(false);
};
- return d;
}
void run() override
@@ -132,11 +131,6 @@ struct CmdHash : NixMultiCommand
command->second->prepare();
command->second->run();
}
-
- void printHelp(const string & programName, std::ostream & out) override
- {
- MultiCommand::printHelp(programName, out);
- }
};
static auto rCmdHash = registerCommand<CmdHash>("hash");
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 3506c3fcc..50e3b29c4 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -60,37 +60,37 @@ MixFlakeOptions::MixFlakeOptions()
{
addFlag({
.longName = "recreate-lock-file",
- .description = "recreate lock file from scratch",
+ .description = "Recreate the flake's lock file from scratch.",
.handler = {&lockFlags.recreateLockFile, true}
});
addFlag({
.longName = "no-update-lock-file",
- .description = "do not allow any updates to the lock file",
+ .description = "Do not allow any updates to the flake's lock file.",
.handler = {&lockFlags.updateLockFile, false}
});
addFlag({
.longName = "no-write-lock-file",
- .description = "do not write the newly generated lock file",
+ .description = "Do not write the flake's newly generated lock file.",
.handler = {&lockFlags.writeLockFile, false}
});
addFlag({
.longName = "no-registries",
- .description = "don't use flake registries",
+ .description = "Don't allow lookups in the flake registries.",
.handler = {&lockFlags.useRegistries, false}
});
addFlag({
.longName = "commit-lock-file",
- .description = "commit changes to the lock file",
+ .description = "Commit changes to the flake's lock file.",
.handler = {&lockFlags.commitLockFile, true}
});
addFlag({
.longName = "update-input",
- .description = "update a specific flake input",
+ .description = "Update a specific flake input (ignoring its previous entry in the lock file).",
.labels = {"input-path"},
.handler = {[&](std::string s) {
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
@@ -103,7 +103,7 @@ MixFlakeOptions::MixFlakeOptions()
addFlag({
.longName = "override-input",
- .description = "override a specific flake input (e.g. `dwarffs/nixpkgs`)",
+ .description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`).",
.labels = {"input-path", "flake-url"},
.handler = {[&](std::string inputPath, std::string flakeRef) {
lockFlags.inputOverrides.insert_or_assign(
@@ -114,7 +114,7 @@ MixFlakeOptions::MixFlakeOptions()
addFlag({
.longName = "inputs-from",
- .description = "use the inputs of the specified flake as registry entries",
+ .description = "Use the inputs of the specified flake as registry entries.",
.labels = {"flake-url"},
.handler = {[&](std::string flakeRef) {
auto evalState = getEvalState();
@@ -143,22 +143,22 @@ SourceExprCommand::SourceExprCommand()
addFlag({
.longName = "file",
.shortName = 'f',
- .description = "evaluate *file* rather than the default",
+ .description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.",
.labels = {"file"},
.handler = {&file},
.completer = completePath
});
addFlag({
- .longName ="expr",
- .description = "evaluate attributes from *expr*",
+ .longName = "expr",
+ .description = "Interpret installables as attribute paths relative to the Nix expression *expr*.",
.labels = {"expr"},
.handler = {&expr}
});
addFlag({
- .longName ="derivation",
- .description = "operate on the store derivation rather than its outputs",
+ .longName = "derivation",
+ .description = "Operate on the store derivation rather than its outputs.",
.handler = {&operateOn, OperateOn::Derivation},
});
}
diff --git a/src/nix/key-convert-secret-to-public.md b/src/nix/key-convert-secret-to-public.md
new file mode 100644
index 000000000..3adc18502
--- /dev/null
+++ b/src/nix/key-convert-secret-to-public.md
@@ -0,0 +1,19 @@
+R""(
+
+# Examples
+
+* Convert a secret key to a public key:
+
+ ```console
+ # echo cache.example.org-0:E7lAO+MsPwTFfPXsdPtW8GKui/5ho4KQHVcAGnX+Tti1V4dUxoVoqLyWJ4YESuZJwQ67GVIksDt47og+tPVUZw== \
+ | nix key convert-secret-to-public
+ cache.example.org-0:tVeHVMaFaKi8lieGBErmScEOuxlSJLA7eO6IPrT1VGc=
+ ```
+
+# Description
+
+This command reads a Ed25519 secret key from standard input, and
+writes the corresponding public key to standard output. For more
+details, see [nix key generate-secret](./nix3-key-generate-secret.md).
+
+)""
diff --git a/src/nix/key-generate-secret.md b/src/nix/key-generate-secret.md
new file mode 100644
index 000000000..4938f637c
--- /dev/null
+++ b/src/nix/key-generate-secret.md
@@ -0,0 +1,48 @@
+R""(
+
+# Examples
+
+* Generate a new secret key:
+
+ ```console
+ # nix key generate-secret --key-name cache.example.org-1 > ./secret-key
+ ```
+
+ We can then use this key to sign the closure of the Hello package:
+
+ ```console
+ # nix build nixpkgs#hello
+ # nix store sign --key-file ./secret-key --recursive ./result
+ ```
+
+ Finally, we can verify the store paths using the corresponding
+ public key:
+
+ ```
+ # nix store verify --trusted-public-keys $(nix key convert-secret-to-public < ./secret-key) ./result
+ ```
+
+# Description
+
+This command generates a new Ed25519 secret key for signing store
+paths and prints it on standard output. Use `nix key
+convert-secret-to-public` to get the corresponding public key for
+verifying signed store paths.
+
+The mandatory argument `--key-name` specifies a key name (such as
+`cache.example.org-1). It is used to look up keys on the client when
+it verifies signatures. It can be anything, but it’s suggested to use
+the host name of your cache (e.g. `cache.example.org`) with a suffix
+denoting the number of the key (to be incremented every time you need
+to revoke a key).
+
+# Format
+
+Both secret and public keys are represented as the key name followed
+by a base-64 encoding of the Ed25519 key data, e.g.
+
+```
+cache.example.org-0:E7lAO+MsPwTFfPXsdPtW8GKui/5ho4KQHVcAGnX+Tti1V4dUxoVoqLyWJ4YESuZJwQ67GVIksDt47og+tPVUZw==
+```
+
+)""
diff --git a/src/nix/local.mk b/src/nix/local.mk
index f37b73384..23c08fc86 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -12,7 +12,6 @@ nix_SOURCES := \
$(wildcard src/nix-daemon/*.cc) \
$(wildcard src/nix-env/*.cc) \
$(wildcard src/nix-instantiate/*.cc) \
- $(wildcard src/nix-prefetch-url/*.cc) \
$(wildcard src/nix-store/*.cc) \
nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr -I src/libmain
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index d48287f27..c0b1ecb32 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -17,9 +17,9 @@ struct MixLs : virtual Args, MixJSON
MixLs()
{
- mkFlag('R', "recursive", "list subdirectories recursively", &recursive);
- mkFlag('l', "long", "show more file information", &verbose);
- mkFlag('d', "directory", "show directories rather than their contents", &showDirectory);
+ mkFlag('R', "recursive", "List subdirectories recursively.", &recursive);
+ mkFlag('l', "long", "Show detailed file information.", &verbose);
+ mkFlag('d', "directory", "Show directories rather than their contents.", &showDirectory);
}
void listText(ref<FSAccessor> accessor)
diff --git a/src/nix/main.cc b/src/nix/main.cc
index b2406fafe..418396280 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -52,6 +52,7 @@ static bool haveInternet()
}
std::string programPath;
+char * * savedArgv;
struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{
@@ -69,15 +70,15 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "help",
- .description = "show usage information",
+ .description = "Show usage information.",
.handler = {[&]() { if (!completions) showHelpAndExit(); }},
});
addFlag({
.longName = "help-config",
- .description = "show configuration options",
+ .description = "Show configuration settings.",
.handler = {[&]() {
- std::cout << "The following configuration options are available:\n\n";
+ std::cout << "The following configuration settings are available:\n\n";
Table2 tbl;
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
@@ -91,25 +92,25 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "print-build-logs",
.shortName = 'L',
- .description = "print full build logs on stderr",
+ .description = "Print full build logs on standard error.",
.handler = {[&]() {setLogFormat(LogFormat::barWithLogs); }},
});
addFlag({
.longName = "version",
- .description = "show version information",
+ .description = "Show version information.",
.handler = {[&]() { if (!completions) printVersion(programName); }},
});
addFlag({
.longName = "no-net",
- .description = "disable substituters and consider all previously downloaded files up-to-date",
+ .description = "Disable substituters and consider all previously downloaded files up-to-date.",
.handler = {[&]() { useNet = false; }},
});
addFlag({
.longName = "refresh",
- .description = "consider all previously downloaded files out-of-date",
+ .description = "Consider all previously downloaded files out-of-date.",
.handler = {[&]() { refresh = true; }},
});
}
@@ -129,7 +130,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{"make-content-addressable", {"store", "make-content-addressable"}},
{"optimise-store", {"store", "optimise"}},
{"ping-store", {"store", "ping"}},
- {"sign-paths", {"store", "sign-paths"}},
+ {"sign-paths", {"store", "sign"}},
{"to-base16", {"hash", "to-base16"}},
{"to-base32", {"hash", "to-base32"}},
{"to-base64", {"hash", "to-base64"}},
@@ -232,6 +233,8 @@ static auto rCmdHelp = registerCommand<CmdHelp>("help");
void mainWrapped(int argc, char * * argv)
{
+ savedArgv = argv;
+
/* The chroot helper needs to be run before any threads have been
started. */
if (argc > 0 && argv[0] == chrootHelperName) {
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index 30b6a50f8..0fa88f1bf 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -18,10 +18,10 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
CmdPathInfo()
{
- mkFlag('s', "size", "print size of the NAR dump of each path", &showSize);
- mkFlag('S', "closure-size", "print sum size of the NAR dumps of the closure of each path", &showClosureSize);
- mkFlag('h', "human-readable", "with -s and -S, print sizes like 1K 234M 5.67G etc.", &humanReadable);
- mkFlag(0, "sigs", "show signatures", &showSigs);
+ mkFlag('s', "size", "Print the size of the NAR serialisation of each path.", &showSize);
+ mkFlag('S', "closure-size", "Print the sum of the sizes of the NAR serialisations of the closure of each path.", &showClosureSize);
+ mkFlag('h', "human-readable", "With `-s` and `-S`, print sizes in a human-friendly format such as `5.67G`.", &humanReadable);
+ mkFlag(0, "sigs", "Show signatures.", &showSigs);
}
std::string description() override
diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc
new file mode 100644
index 000000000..a831dcd15
--- /dev/null
+++ b/src/nix/prefetch.cc
@@ -0,0 +1,319 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "filetransfer.hh"
+#include "finally.hh"
+#include "progress-bar.hh"
+#include "tarfile.hh"
+#include "attr-path.hh"
+#include "eval-inline.hh"
+#include "legacy.hh"
+
+#include <nlohmann/json.hpp>
+
+using namespace nix;
+
+/* If ‘url’ starts with ‘mirror://’, then resolve it using the list of
+ mirrors defined in Nixpkgs. */
+string resolveMirrorUrl(EvalState & state, string url)
+{
+ if (url.substr(0, 9) != "mirror://") return url;
+
+ std::string s(url, 9);
+ auto p = s.find('/');
+ if (p == std::string::npos) throw Error("invalid mirror URL '%s'", url);
+ std::string mirrorName(s, 0, p);
+
+ Value vMirrors;
+ // FIXME: use nixpkgs flake
+ state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
+ state.forceAttrs(vMirrors);
+
+ auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
+ if (mirrorList == vMirrors.attrs->end())
+ throw Error("unknown mirror name '%s'", mirrorName);
+ state.forceList(*mirrorList->value);
+
+ if (mirrorList->value->listSize() < 1)
+ throw Error("mirror URL '%s' did not expand to anything", url);
+
+ auto mirror = state.forceString(*mirrorList->value->listElems()[0]);
+ return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
+}
+
+std::tuple<StorePath, Hash> prefetchFile(
+ ref<Store> store,
+ std::string_view url,
+ std::optional<std::string> name,
+ HashType hashType,
+ std::optional<Hash> expectedHash,
+ bool unpack,
+ bool executable)
+{
+ auto ingestionMethod = unpack || executable ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
+
+ /* Figure out a name in the Nix store. */
+ if (!name) {
+ name = baseNameOf(url);
+ if (name->empty())
+ throw Error("cannot figure out file name for '%s'", url);
+ }
+
+ std::optional<StorePath> storePath;
+ std::optional<Hash> hash;
+
+ /* If an expected hash is given, the file may already exist in
+ the store. */
+ if (expectedHash) {
+ hashType = expectedHash->type;
+ storePath = store->makeFixedOutputPath(ingestionMethod, *expectedHash, *name);
+ if (store->isValidPath(*storePath))
+ hash = expectedHash;
+ else
+ storePath.reset();
+ }
+
+ if (!storePath) {
+
+ AutoDelete tmpDir(createTempDir(), true);
+ Path tmpFile = (Path) tmpDir + "/tmp";
+
+ /* Download the file. */
+ {
+ auto mode = 0600;
+ if (executable)
+ mode = 0700;
+
+ AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (!fd) throw SysError("creating temporary file '%s'", tmpFile);
+
+ FdSink sink(fd.get());
+
+ FileTransferRequest req(url);
+ req.decompress = false;
+ getFileTransfer()->download(std::move(req), sink);
+ }
+
+ /* Optionally unpack the file. */
+ if (unpack) {
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("unpacking '%s'", url));
+ Path unpacked = (Path) tmpDir + "/unpacked";
+ createDirs(unpacked);
+ unpackTarfile(tmpFile, unpacked);
+
+ /* If the archive unpacks to a single file/directory, then use
+ that as the top-level. */
+ auto entries = readDirectory(unpacked);
+ if (entries.size() == 1)
+ tmpFile = unpacked + "/" + entries[0].name;
+ else
+ tmpFile = unpacked;
+ }
+
+ Activity act(*logger, lvlChatty, actUnknown,
+ fmt("adding '%s' to the store", url));
+
+ auto info = store->addToStoreSlow(*name, tmpFile, ingestionMethod, hashType, expectedHash);
+ storePath = info.path;
+ assert(info.ca);
+ hash = getContentAddressHash(*info.ca);
+ }
+
+ return {storePath.value(), hash.value()};
+}
+
+static int main_nix_prefetch_url(int argc, char * * argv)
+{
+ {
+ HashType ht = htSHA256;
+ std::vector<string> args;
+ bool printPath = getEnv("PRINT_PATH") == "1";
+ bool fromExpr = false;
+ string attrPath;
+ bool unpack = false;
+ bool executable = false;
+ std::optional<std::string> name;
+
+ struct MyArgs : LegacyArgs, MixEvalArgs
+ {
+ using LegacyArgs::LegacyArgs;
+ };
+
+ MyArgs myArgs(std::string(baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) {
+ if (*arg == "--help")
+ showManPage("nix-prefetch-url");
+ else if (*arg == "--version")
+ printVersion("nix-prefetch-url");
+ else if (*arg == "--type") {
+ string s = getArg(*arg, arg, end);
+ ht = parseHashType(s);
+ }
+ else if (*arg == "--print-path")
+ printPath = true;
+ else if (*arg == "--attr" || *arg == "-A") {
+ fromExpr = true;
+ attrPath = getArg(*arg, arg, end);
+ }
+ else if (*arg == "--unpack")
+ unpack = true;
+ else if (*arg == "--executable")
+ executable = true;
+ else if (*arg == "--name")
+ name = getArg(*arg, arg, end);
+ else if (*arg != "" && arg->at(0) == '-')
+ return false;
+ else
+ args.push_back(*arg);
+ return true;
+ });
+
+ myArgs.parseCmdline(argvToStrings(argc, argv));
+
+ initPlugins();
+
+ if (args.size() > 2)
+ throw UsageError("too many arguments");
+
+ Finally f([]() { stopProgressBar(); });
+
+ if (isatty(STDERR_FILENO))
+ startProgressBar();
+
+ auto store = openStore();
+ auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
+
+ Bindings & autoArgs = *myArgs.getAutoArgs(*state);
+
+ /* If -A is given, get the URL from the specified Nix
+ expression. */
+ string url;
+ if (!fromExpr) {
+ if (args.empty())
+ throw UsageError("you must specify a URL");
+ url = args[0];
+ } else {
+ Path path = resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
+ Value vRoot;
+ state->evalFile(path, vRoot);
+ Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
+ state->forceAttrs(v);
+
+ /* Extract the URL. */
+ auto attr = v.attrs->find(state->symbols.create("urls"));
+ if (attr == v.attrs->end())
+ throw Error("attribute set does not contain a 'urls' attribute");
+ state->forceList(*attr->value);
+ if (attr->value->listSize() < 1)
+ throw Error("'urls' list is empty");
+ url = state->forceString(*attr->value->listElems()[0]);
+
+ /* Extract the hash mode. */
+ attr = v.attrs->find(state->symbols.create("outputHashMode"));
+ if (attr == v.attrs->end())
+ printInfo("warning: this does not look like a fetchurl call");
+ else
+ unpack = state->forceString(*attr->value) == "recursive";
+
+ /* Extract the name. */
+ if (!name) {
+ attr = v.attrs->find(state->symbols.create("name"));
+ if (attr != v.attrs->end())
+ name = state->forceString(*attr->value);
+ }
+ }
+
+ std::optional<Hash> expectedHash;
+ if (args.size() == 2)
+ expectedHash = Hash::parseAny(args[1], ht);
+
+ auto [storePath, hash] = prefetchFile(
+ store, resolveMirrorUrl(*state, url), name, ht, expectedHash, unpack, executable);
+
+ stopProgressBar();
+
+ if (!printPath)
+ printInfo("path is '%s'", store->printStorePath(storePath));
+
+ std::cout << printHash16or32(hash) << std::endl;
+ if (printPath)
+ std::cout << store->printStorePath(storePath) << std::endl;
+
+ return 0;
+ }
+}
+
+static RegisterLegacyCommand r_nix_prefetch_url("nix-prefetch-url", main_nix_prefetch_url);
+
+struct CmdStorePrefetchFile : StoreCommand, MixJSON
+{
+ std::string url;
+ bool executable = false;
+ std::optional<std::string> name;
+ HashType hashType = htSHA256;
+ std::optional<Hash> expectedHash;
+
+ CmdStorePrefetchFile()
+ {
+ addFlag({
+ .longName = "name",
+ .description = "Override the name component of the resulting store path. It defaults to the base name of *url*.",
+ .labels = {"name"},
+ .handler = {&name}
+ });
+
+ addFlag({
+ .longName = "expected-hash",
+ .description = "The expected hash of the file.",
+ .labels = {"hash"},
+ .handler = {[&](std::string s) {
+ expectedHash = Hash::parseAny(s, hashType);
+ }}
+ });
+
+ addFlag(Flag::mkHashTypeFlag("hash-type", &hashType));
+
+ addFlag({
+ .longName = "executable",
+ .description =
+ "Make the resulting file executable. Note that this causes the "
+ "resulting hash to be a NAR hash rather than a flat file hash.",
+ .handler = {&executable, true},
+ });
+
+ expectArg("url", &url);
+ }
+
+ Category category() override { return catUtility; }
+
+ std::string description() override
+ {
+ return "download a file into the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-prefetch-file.md"
+ ;
+ }
+ void run(ref<Store> store) override
+ {
+ auto [storePath, hash] = prefetchFile(store, url, name, hashType, expectedHash, false, executable);
+
+ if (json) {
+ auto res = nlohmann::json::object();
+ res["storePath"] = store->printStorePath(storePath);
+ res["hash"] = hash.to_string(SRI, true);
+ logger->cout(res.dump());
+ } else {
+ notice("Downloaded '%s' to '%s' (hash '%s').",
+ url,
+ store->printStorePath(storePath),
+ hash.to_string(SRI, true));
+ }
+ }
+};
+
+static auto rCmdStorePrefetchFile = registerCommand2<CmdStorePrefetchFile>({"store", "prefetch-file"});
diff --git a/src/nix/profile-history.md b/src/nix/profile-history.md
new file mode 100644
index 000000000..d0fe40c82
--- /dev/null
+++ b/src/nix/profile-history.md
@@ -0,0 +1,26 @@
+R""(
+
+# Examples
+
+* Show the changes between each version of your default profile:
+
+ ```console
+ # nix profile history
+ Version 508 -> 509:
+ flake:nixpkgs#legacyPackages.x86_64-linux.awscli: ∅ -> 1.17.13
+
+ Version 509 -> 510:
+ flake:nixpkgs#legacyPackages.x86_64-linux.awscli: 1.17.13 -> 1.18.211
+ ```
+
+# Description
+
+This command shows what packages were added, removed or upgraded
+between subsequent versions of a profile. It only shows top-level
+packages, not dependencies; for that, use [`nix profile
+diff-closures`](./nix3-profile-diff-closures.md).
+
+The addition of a package to a profile is denoted by the string `∅ ->`
+*version*, whereas the removal is denoted by *version* `-> ∅`.
+
+)""
diff --git a/src/nix/profile-info.md b/src/nix/profile-list.md
index a0c04fc8c..5c29c0b02 100644
--- a/src/nix/profile-info.md
+++ b/src/nix/profile-list.md
@@ -5,7 +5,7 @@ R""(
* Show what packages are installed in the default profile:
```console
- # nix profile info
+ # nix profile list
0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.spotify /nix/store/akpdsid105phbbvknjsdh7hl4v3fhjkr-spotify-1.1.46.916.g416cacf1
1 flake:nixpkgs#legacyPackages.x86_64-linux.zoom-us github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.zoom-us /nix/store/89pmjmbih5qpi7accgacd17ybpgp4xfm-zoom-us-5.4.53350.1027
2 flake:blender-bin#defaultPackage.x86_64-linux github:edolstra/nix-warez/d09d7eea893dcb162e89bc67f6dc1ced14abfc27?dir=blender#defaultPackage.x86_64-linux /nix/store/zfgralhqjnam662kqsgq6isjw8lhrflz-blender-bin-2.91.0
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index d8d2b3a70..ca95817d0 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -8,6 +8,7 @@
#include "flake/flakeref.hh"
#include "../nix-env/user-env.hh"
#include "profiles.hh"
+#include "names.hh"
#include <nlohmann/json.hpp>
#include <regex>
@@ -21,6 +22,13 @@ struct ProfileElementSource
FlakeRef resolvedRef;
std::string attrPath;
// FIXME: output names
+
+ bool operator < (const ProfileElementSource & other) const
+ {
+ return
+ std::pair(originalRef.to_string(), attrPath) <
+ std::pair(other.originalRef.to_string(), other.attrPath);
+ }
};
struct ProfileElement
@@ -29,6 +37,29 @@ struct ProfileElement
std::optional<ProfileElementSource> source;
bool active = true;
// FIXME: priority
+
+ std::string describe() const
+ {
+ if (source)
+ return fmt("%s#%s", source->originalRef, source->attrPath);
+ StringSet names;
+ for (auto & path : storePaths)
+ names.insert(DrvName(path.name()).name);
+ return concatStringsSep(", ", names);
+ }
+
+ std::string versions() const
+ {
+ StringSet versions;
+ for (auto & path : storePaths)
+ versions.insert(DrvName(path.name()).version);
+ return showVersions(versions);
+ }
+
+ bool operator < (const ProfileElement & other) const
+ {
+ return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths);
+ }
};
struct ProfileManifest
@@ -142,6 +173,46 @@ struct ProfileManifest
return std::move(info.path);
}
+
+ static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent)
+ {
+ auto prevElems = prev.elements;
+ std::sort(prevElems.begin(), prevElems.end());
+
+ auto curElems = cur.elements;
+ std::sort(curElems.begin(), curElems.end());
+
+ auto i = prevElems.begin();
+ auto j = curElems.begin();
+
+ bool changes = false;
+
+ while (i != prevElems.end() || j != curElems.end()) {
+ if (j != curElems.end() && (i == prevElems.end() || i->describe() > j->describe())) {
+ std::cout << fmt("%s%s: ∅ -> %s\n", indent, j->describe(), j->versions());
+ changes = true;
+ ++j;
+ }
+ else if (i != prevElems.end() && (j == curElems.end() || i->describe() < j->describe())) {
+ std::cout << fmt("%s%s: %s -> ∅\n", indent, i->describe(), i->versions());
+ changes = true;
+ ++i;
+ }
+ else {
+ auto v1 = i->versions();
+ auto v2 = j->versions();
+ if (v1 != v2) {
+ std::cout << fmt("%s%s: %s -> %s\n", indent, i->describe(), v1, v2);
+ changes = true;
+ }
+ ++i;
+ ++j;
+ }
+ }
+
+ if (!changes)
+ std::cout << fmt("%sNo changes.\n", indent);
+ }
};
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
@@ -209,9 +280,8 @@ public:
std::vector<Matcher> res;
for (auto & s : _matchers) {
- size_t n;
- if (string2Int(s, n))
- res.push_back(n);
+ if (auto n = string2Int<size_t>(s))
+ res.push_back(*n);
else if (store->isStorePath(s))
res.push_back(s);
else
@@ -337,7 +407,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
}
};
-struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
+struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
{
std::string description() override
{
@@ -347,7 +417,7 @@ struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
std::string doc() override
{
return
- #include "profile-info.md"
+ #include "profile-list.md"
;
}
@@ -402,6 +472,48 @@ struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
}
};
+struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
+{
+ std::string description() override
+ {
+ return "show all versions of a profile";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "profile-history.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto [gens, curGen] = findGenerations(*profile);
+
+ std::optional<std::pair<Generation, ProfileManifest>> prevGen;
+ bool first = true;
+
+ for (auto & gen : gens) {
+ ProfileManifest manifest(*getEvalState(), gen.path);
+
+ if (!first) std::cout << "\n";
+ first = false;
+
+ if (prevGen)
+ std::cout << fmt("Version %d -> %d:\n", prevGen->first.number, gen.number);
+ else
+ std::cout << fmt("Version %d:\n", gen.number);
+
+ ProfileManifest::printDiff(
+ prevGen ? prevGen->second : ProfileManifest(),
+ manifest,
+ " ");
+
+ prevGen = {gen, std::move(manifest)};
+ }
+ }
+};
+
struct CmdProfile : NixMultiCommand
{
CmdProfile()
@@ -409,8 +521,9 @@ struct CmdProfile : NixMultiCommand
{"install", []() { return make_ref<CmdProfileInstall>(); }},
{"remove", []() { return make_ref<CmdProfileRemove>(); }},
{"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
- {"info", []() { return make_ref<CmdProfileInfo>(); }},
+ {"list", []() { return make_ref<CmdProfileList>(); }},
{"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
+ {"history", []() { return make_ref<CmdProfileHistory>(); }},
})
{ }
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 1340dd46f..ec9388234 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -72,7 +72,7 @@ struct CmdShell : InstallablesCommand, RunCommon, MixEnvironment
addFlag({
.longName = "command",
.shortName = 'c',
- .description = "command and arguments to be executed; defaults to '$SHELL'",
+ .description = "Command and arguments to be executed, defaulting to `$SHELL`",
.labels = {"command", "args"},
.handler = {[&](std::vector<std::string> ss) {
if (ss.empty()) throw UsageError("--command requires at least one argument");
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index 13f2c8e69..2588a011d 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -19,7 +19,7 @@ struct CmdShowDerivation : InstallablesCommand
addFlag({
.longName = "recursive",
.shortName = 'r',
- .description = "include the dependencies of the specified derivations",
+ .description = "Include the dependencies of the specified derivations.",
.handler = {&recursive, true}
});
}
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 37b8a6712..3445182f2 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -16,7 +16,7 @@ struct CmdCopySigs : StorePathsCommand
addFlag({
.longName = "substituter",
.shortName = 's',
- .description = "use signatures from specified store",
+ .description = "Use signatures from specified store.",
.labels = {"store-uri"},
.handler = {[&](std::string s) { substituterUris.push_back(s); }},
});
@@ -92,16 +92,16 @@ struct CmdCopySigs : StorePathsCommand
static auto rCmdCopySigs = registerCommand2<CmdCopySigs>({"store", "copy-sigs"});
-struct CmdSignPaths : StorePathsCommand
+struct CmdSign : StorePathsCommand
{
Path secretKeyFile;
- CmdSignPaths()
+ CmdSign()
{
addFlag({
.longName = "key-file",
.shortName = 'k',
- .description = "file containing the secret signing key",
+ .description = "File containing the secret signing key.",
.labels = {"file"},
.handler = {&secretKeyFile},
.completer = completePath
@@ -140,4 +140,89 @@ struct CmdSignPaths : StorePathsCommand
}
};
-static auto rCmdSignPaths = registerCommand2<CmdSignPaths>({"store", "sign-paths"});
+static auto rCmdSign = registerCommand2<CmdSign>({"store", "sign"});
+
+struct CmdKeyGenerateSecret : Command
+{
+ std::optional<std::string> keyName;
+
+ CmdKeyGenerateSecret()
+ {
+ addFlag({
+ .longName = "key-name",
+ .description = "Identifier of the key (e.g. `cache.example.org-1`).",
+ .labels = {"name"},
+ .handler = {&keyName},
+ });
+ }
+
+ std::string description() override
+ {
+ return "generate a secret key for signing store paths";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "key-generate-secret.md"
+ ;
+ }
+
+ void run() override
+ {
+ if (!keyName)
+ throw UsageError("required argument '--key-name' is missing");
+
+ std::cout << SecretKey::generate(*keyName).to_string();
+ }
+};
+
+struct CmdKeyConvertSecretToPublic : Command
+{
+ std::string description() override
+ {
+ return "generate a public key for verifying store paths from a secret key read from standard input";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "key-convert-secret-to-public.md"
+ ;
+ }
+
+ void run() override
+ {
+ SecretKey secretKey(drainFD(STDIN_FILENO));
+ std::cout << secretKey.toPublicKey().to_string();
+ }
+};
+
+struct CmdKey : NixMultiCommand
+{
+ CmdKey()
+ : MultiCommand({
+ {"generate-secret", []() { return make_ref<CmdKeyGenerateSecret>(); }},
+ {"convert-secret-to-public", []() { return make_ref<CmdKeyConvertSecretToPublic>(); }},
+ })
+ {
+ }
+
+ std::string description() override
+ {
+ return "generate and convert Nix signing keys";
+ }
+
+ Category category() override { return catUtility; }
+
+ void run() override
+ {
+ if (!command)
+ throw UsageError("'nix flake' requires a sub-command.");
+ settings.requireExperimentalFeature("flakes");
+ command->second->prepare();
+ command->second->run();
+ }
+};
+
+static auto rCmdKey = registerCommand<CmdKey>("key");
diff --git a/src/nix/store-delete.cc b/src/nix/store-delete.cc
new file mode 100644
index 000000000..10245978e
--- /dev/null
+++ b/src/nix/store-delete.cc
@@ -0,0 +1,44 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdStoreDelete : StorePathsCommand
+{
+ GCOptions options { .action = GCOptions::gcDeleteSpecific };
+
+ CmdStoreDelete()
+ {
+ addFlag({
+ .longName = "ignore-liveness",
+ .description = "Do not check whether the paths are reachable from a root.",
+ .handler = {&options.ignoreLiveness, true}
+ });
+ }
+
+ std::string description() override
+ {
+ return "delete paths from the Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-delete.md"
+ ;
+ }
+
+ void run(ref<Store> store, std::vector<StorePath> storePaths) override
+ {
+ for (auto & path : storePaths)
+ options.pathsToDelete.insert(path);
+
+ GCResults results;
+ PrintFreed freed(true, results);
+ store->collectGarbage(options, results);
+ }
+};
+
+static auto rCmdStoreDelete = registerCommand2<CmdStoreDelete>({"store", "delete"});
diff --git a/src/nix/store-delete.md b/src/nix/store-delete.md
new file mode 100644
index 000000000..db535f87c
--- /dev/null
+++ b/src/nix/store-delete.md
@@ -0,0 +1,24 @@
+R""(
+
+# Examples
+
+* Delete a specific store path:
+
+ ```console
+ # nix store delete /nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10
+ ```
+
+# Description
+
+This command deletes the store paths specified by *installables*. ,
+but only if it is safe to do so; that is, when the path is not
+reachable from a root of the garbage collector. This means that you
+can only delete paths that would also be deleted by `nix store
+gc`. Thus, `nix store delete` is a more targeted version of `nix store
+gc`.
+
+With the option `--ignore-liveness`, reachability from the roots is
+ignored. However, the path still won't be deleted if there are other
+paths in the store that refer to it (i.e., depend on it).
+
+)""
diff --git a/src/nix/store-gc.cc b/src/nix/store-gc.cc
new file mode 100644
index 000000000..a2d74066e
--- /dev/null
+++ b/src/nix/store-gc.cc
@@ -0,0 +1,43 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdStoreGC : StoreCommand, MixDryRun
+{
+ GCOptions options;
+
+ CmdStoreGC()
+ {
+ addFlag({
+ .longName = "max",
+ .description = "Stop after freeing *n* bytes of disk space.",
+ .labels = {"n"},
+ .handler = {&options.maxFreed}
+ });
+ }
+
+ std::string description() override
+ {
+ return "perform garbage collection on a Nix store";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-gc.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ options.action = dryRun ? GCOptions::gcReturnDead : GCOptions::gcDeleteDead;
+ GCResults results;
+ PrintFreed freed(options.action == GCOptions::gcDeleteDead, results);
+ store->collectGarbage(options, results);
+ }
+};
+
+static auto rCmdStoreGC = registerCommand2<CmdStoreGC>({"store", "gc"});
diff --git a/src/nix/store-gc.md b/src/nix/store-gc.md
new file mode 100644
index 000000000..956b3c872
--- /dev/null
+++ b/src/nix/store-gc.md
@@ -0,0 +1,21 @@
+R""(
+
+# Examples
+
+* Delete unreachable paths in the Nix store:
+
+ ```console
+ # nix store gc
+ ```
+
+* Delete up to 1 gigabyte of garbage:
+
+ ```console
+ # nix store gc --max 1G
+ ```
+
+# Description
+
+This command deletes unreachable paths in the Nix store.
+
+)""
diff --git a/src/nix/store-prefetch-file.md b/src/nix/store-prefetch-file.md
new file mode 100644
index 000000000..1663b847b
--- /dev/null
+++ b/src/nix/store-prefetch-file.md
@@ -0,0 +1,32 @@
+R""(
+
+# Examples
+
+* Download a file to the Nix store:
+
+ ```console
+ # nix store prefetch-file https://releases.nixos.org/nix/nix-2.3.10/nix-2.3.10.tar.xz
+ Downloaded 'https://releases.nixos.org/nix/nix-2.3.10/nix-2.3.10.tar.xz' to
+ '/nix/store/vbdbi42hgnc4h7pyqzp6h2yf77kw93aw-source' (hash
+ 'sha256-qKheVd5D0BervxMDbt+1hnTKE2aRWC8XCAwc0SeHt6s=').
+ ```
+
+* Download a file and get the SHA-512 hash:
+
+ ```console
+ # nix store prefetch-file --json --hash-type sha512 \
+ https://releases.nixos.org/nix/nix-2.3.10/nix-2.3.10.tar.xz \
+ | jq -r .hash
+ sha512-6XJxfym0TNH9knxeH4ZOvns6wElFy3uahunl2hJgovACCMEMXSy42s69zWVyGJALXTI+86tpDJGlIcAySEKBbA==
+ ```
+
+# Description
+
+This command downloads the file *url* to the Nix store. It prints out
+the resulting store path and the cryptographic hash of the contents of
+the file.
+
+The name component of the store path defaults to the last component of
+*url*, but this can be overriden using `--name`.
+
+)""
diff --git a/src/nix/store-repair.cc b/src/nix/store-repair.cc
new file mode 100644
index 000000000..1c7a4392e
--- /dev/null
+++ b/src/nix/store-repair.cc
@@ -0,0 +1,27 @@
+#include "command.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdStoreRepair : StorePathsCommand
+{
+ std::string description() override
+ {
+ return "repair store paths";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "store-repair.md"
+ ;
+ }
+
+ void run(ref<Store> store, std::vector<StorePath> storePaths) override
+ {
+ for (auto & path : storePaths)
+ store->repairPath(path);
+ }
+};
+
+static auto rStoreRepair = registerCommand2<CmdStoreRepair>({"store", "repair"});
diff --git a/src/nix/store-repair.md b/src/nix/store-repair.md
new file mode 100644
index 000000000..92d2205a9
--- /dev/null
+++ b/src/nix/store-repair.md
@@ -0,0 +1,32 @@
+R""(
+
+# Examples
+
+* Repair a store path, after determining that it is corrupt:
+
+ ```console
+ # nix store verify /nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10
+ path '/nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10' was
+ modified! expected hash
+ 'sha256:1hd5vnh6xjk388gdk841vflicy8qv7qzj2hb7xlyh8lpb43j921l', got
+ 'sha256:1a25lf78x5wi6pfkrxalf0n13kdaca0bqmjqnp7wfjza2qz5ssgl'
+
+ # nix store repair /nix/store/yb5q57zxv6hgqql42d5r8b5k5mcq6kay-hello-2.10
+ ```
+
+# Description
+
+This command attempts to "repair" the store paths specified by
+*installables* by redownloading them using the available
+substituters. If no substitutes are available, then repair is not
+possible.
+
+> **Warning**
+>
+> During repair, there is a very small time window during which the old
+> path (if it exists) is moved out of the way and replaced with the new
+> path. If repair is interrupted in between, then the system may be left
+> in a broken state (e.g., if the path contains a critical system
+> component like the GNU C Library).
+
+)""
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index 79be31e73..299ea40aa 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -19,14 +19,14 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
addFlag({
.longName = "profile",
.shortName = 'p',
- .description = "the Nix profile to upgrade",
+ .description = "The path to the Nix profile to upgrade.",
.labels = {"profile-dir"},
.handler = {&profileDir}
});
addFlag({
.longName = "nix-store-paths-url",
- .description = "URL of the file that contains the store paths of the latest Nix release",
+ .description = "The URL of the file that contains the store paths of the latest Nix release.",
.labels = {"url"},
.handler = {&storePathsUrl}
});
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index 16d42349f..b2963cf74 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -18,16 +18,24 @@ struct CmdVerify : StorePathsCommand
CmdVerify()
{
- mkFlag(0, "no-contents", "do not verify the contents of each store path", &noContents);
- mkFlag(0, "no-trust", "do not verify whether each store path is trusted", &noTrust);
+ mkFlag(0, "no-contents", "Do not verify the contents of each store path.", &noContents);
+ mkFlag(0, "no-trust", "Do not verify whether each store path is trusted.", &noTrust);
+
addFlag({
.longName = "substituter",
.shortName = 's',
- .description = "use signatures from specified store",
+ .description = "Use signatures from the specified store.",
.labels = {"store-uri"},
.handler = {[&](std::string s) { substituterUris.push_back(s); }}
});
- mkIntFlag('n', "sigs-needed", "require that each path has at least N valid signatures", &sigsNeeded);
+
+ addFlag({
+ .longName = "sigs-needed",
+ .shortName = 'n',
+ .description = "Require that each path has at least *n* valid signatures.",
+ .labels = {"n"},
+ .handler = {&sigsNeeded}
+ });
}
std::string description() override
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index 297b638cc..7a4ca5172 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -40,7 +40,7 @@ struct CmdWhyDepends : SourceExprCommand
addFlag({
.longName = "all",
.shortName = 'a',
- .description = "show all edges in the dependency graph leading from 'package' to 'dependency', rather than just a shortest path",
+ .description = "Show all edges in the dependency graph leading from *package* to *dependency*, rather than just a shortest path.",
.handler = {&all, true},
});
}