about summary refs log tree commit diff
path: root/third_party/nix/src/nix/upgrade-nix.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/nix/upgrade-nix.cc')
-rw-r--r--third_party/nix/src/nix/upgrade-nix.cc167
1 files changed, 167 insertions, 0 deletions
diff --git a/third_party/nix/src/nix/upgrade-nix.cc b/third_party/nix/src/nix/upgrade-nix.cc
new file mode 100644
index 0000000000..c7f654d648
--- /dev/null
+++ b/third_party/nix/src/nix/upgrade-nix.cc
@@ -0,0 +1,167 @@
+#include <absl/strings/match.h>
+#include <absl/strings/str_cat.h>
+#include <absl/strings/str_split.h>
+#include <glog/logging.h>
+
+#include "libexpr/attr-path.hh"
+#include "libexpr/eval.hh"
+#include "libexpr/names.hh"
+#include "libmain/common-args.hh"
+#include "libstore/download.hh"
+#include "libstore/store-api.hh"
+#include "nix/command.hh"
+
+namespace nix {
+struct CmdUpgradeNix final : MixDryRun, StoreCommand {
+  Path profileDir;
+  std::string storePathsUrl =
+      "https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/"
+      "tools/nix-fallback-paths.nix";
+
+  CmdUpgradeNix() {
+    mkFlag()
+        .longName("profile")
+        .shortName('p')
+        .labels({"profile-dir"})
+        .description("the Nix profile to upgrade")
+        .dest(&profileDir);
+
+    mkFlag()
+        .longName("nix-store-paths-url")
+        .labels({"url"})
+        .description(
+            "URL of the file that contains the store paths of the latest Nix "
+            "release")
+        .dest(&storePathsUrl);
+  }
+
+  std::string name() override { return "upgrade-nix"; }
+
+  std::string description() override {
+    return "upgrade Nix to the latest stable version";
+  }
+
+  Examples examples() override {
+    return {
+        Example{"To upgrade Nix to the latest stable version:",
+                "nix upgrade-nix"},
+        Example{
+            "To upgrade Nix in a specific profile:",
+            "nix upgrade-nix -p /nix/var/nix/profiles/per-user/alice/profile"},
+    };
+  }
+
+  void run(ref<Store> store) override {
+    evalSettings.pureEval = true;
+
+    if (profileDir.empty()) {
+      profileDir = getProfileDir(store);
+    }
+
+    LOG(INFO) << "upgrading Nix in profile '" << profileDir << "'";
+
+    Path storePath;
+    {
+      LOG(INFO) << "querying latest Nix version";
+      storePath = getLatestNix(store);
+    }
+
+    auto version = DrvName(storePathToName(storePath)).version;
+
+    if (dryRun) {
+      LOG(ERROR) << "would upgrade to version " << version;
+      return;
+    }
+
+    {
+      LOG(INFO) << "downloading '" << storePath << "'...";
+      store->ensurePath(storePath);
+    }
+
+    {
+      LOG(INFO) << "verifying that '" << storePath << "' works...";
+      auto program = storePath + "/bin/nix-env";
+      auto s = runProgram(program, false, {"--version"});
+      if (s.find("Nix") == std::string::npos) {
+        throw Error("could not verify that '%s' works", program);
+      }
+    }
+
+    {
+      LOG(INFO) << "installing '" << storePath << "' into profile '"
+                << profileDir << "'...";
+      runProgram(settings.nixBinDir + "/nix-env", false,
+                 {"--profile", profileDir, "-i", storePath, "--no-sandbox"});
+    }
+
+    LOG(INFO) << ANSI_GREEN << "upgrade to version " << version << " done"
+              << ANSI_NORMAL;
+  }
+
+  /* Return the profile in which Nix is installed. */
+  static Path getProfileDir(const ref<Store>& store) {
+    Path where;
+
+    for (auto& dir : absl::StrSplit(getEnv("PATH").value_or(""),
+                                    absl::ByChar(':'), absl::SkipEmpty())) {
+      if (pathExists(absl::StrCat(dir, "/nix-env"))) {
+        where = dir;
+        break;
+      }
+    }
+
+    if (where.empty()) {
+      throw Error(
+          "couldn't figure out how Nix is installed, so I can't upgrade it");
+    }
+
+    LOG(INFO) << "found Nix in '" << where << "'";
+
+    if (absl::StartsWith(where, "/run/current-system")) {
+      throw Error("Nix on NixOS must be upgraded via 'nixos-rebuild'");
+    }
+
+    Path profileDir = dirOf(where);
+
+    // Resolve profile to /nix/var/nix/profiles/<name> link.
+    while (canonPath(profileDir).find("/profiles/") == std::string::npos &&
+           isLink(profileDir)) {
+      profileDir = readLink(profileDir);
+    }
+
+    LOG(INFO) << "found profile '" << profileDir << "'";
+
+    Path userEnv = canonPath(profileDir, true);
+
+    if (baseNameOf(where) != "bin" ||
+        !absl::EndsWith(userEnv, "user-environment")) {
+      throw Error("directory '%s' does not appear to be part of a Nix profile",
+                  where);
+    }
+
+    if (!store->isValidPath(userEnv)) {
+      throw Error("directory '%s' is not in the Nix store", userEnv);
+    }
+
+    return profileDir;
+  }
+
+  /* Return the store path of the latest stable Nix. */
+  Path getLatestNix(const ref<Store>& store) {
+    // FIXME: use nixos.org?
+    auto req = DownloadRequest(storePathsUrl);
+    auto res = getDownloader()->download(req);
+
+    auto state = std::make_unique<EvalState>(Strings(), store);
+    auto v = state->allocValue();
+    state->eval(state->parseExprFromString(*res.data, "/no-such-path"), *v);
+    std::unique_ptr<Bindings> bindings(Bindings::New());
+    auto v2 =
+        findAlongAttrPath(*state, settings.thisSystem, bindings.get(), *v);
+
+    return state->forceString(*v2);
+  }
+};
+}  // namespace nix
+
+static nix::RegisterCommand r1(nix::make_ref<nix::CmdUpgradeNix>());