about summary refs log tree commit diff
path: root/third_party/nix/src/libstore/legacy-ssh-store.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/libstore/legacy-ssh-store.cc')
-rw-r--r--third_party/nix/src/libstore/legacy-ssh-store.cc282
1 files changed, 282 insertions, 0 deletions
diff --git a/third_party/nix/src/libstore/legacy-ssh-store.cc b/third_party/nix/src/libstore/legacy-ssh-store.cc
new file mode 100644
index 000000000000..8163258179a1
--- /dev/null
+++ b/third_party/nix/src/libstore/legacy-ssh-store.cc
@@ -0,0 +1,282 @@
+#include <absl/strings/match.h>
+#include <absl/strings/str_cat.h>
+#include <glog/logging.h>
+
+#include "libstore/derivations.hh"
+#include "libstore/remote-store.hh"
+#include "libstore/serve-protocol.hh"
+#include "libstore/ssh.hh"
+#include "libstore/store-api.hh"
+#include "libstore/worker-protocol.hh"
+#include "libutil/archive.hh"
+#include "libutil/pool.hh"
+
+namespace nix {
+
+constexpr std::string_view kUriScheme = "ssh://";
+
+struct LegacySSHStore : public Store {
+  const Setting<int> maxConnections{
+      this, 1, "max-connections",
+      "maximum number of concurrent SSH connections"};
+  const Setting<Path> sshKey{this, "", "ssh-key", "path to an SSH private key"};
+  const Setting<bool> compress{this, false, "compress",
+                               "whether to compress the connection"};
+  const Setting<Path> remoteProgram{
+      this, "nix-store", "remote-program",
+      "path to the nix-store executable on the remote system"};
+  const Setting<std::string> remoteStore{
+      this, "", "remote-store", "URI of the store on the remote system"};
+
+  // Hack for getting remote build log output.
+  const Setting<int> logFD{
+      this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
+
+  struct Connection {
+    std::unique_ptr<SSHMaster::Connection> sshConn;
+    FdSink to;
+    FdSource from;
+    int remoteVersion;
+    bool good = true;
+  };
+
+  std::string host;
+
+  ref<Pool<Connection>> connections;
+
+  SSHMaster master;
+
+  LegacySSHStore(const std::string& host, const Params& params)
+      : Store(params),
+        host(host),
+        connections(make_ref<Pool<Connection>>(
+            std::max(1, (int)maxConnections),
+            [this]() { return openConnection(); },
+            [](const ref<Connection>& r) { return r->good; })),
+        master(host, sshKey,
+               // Use SSH master only if using more than 1 connection.
+               connections->capacity() > 1, compress, logFD) {}
+
+  ref<Connection> openConnection() {
+    auto conn = make_ref<Connection>();
+    conn->sshConn = master.startCommand(
+        fmt("%s --serve --write", remoteProgram) +
+        (remoteStore.get().empty()
+             ? ""
+             : " --store " + shellEscape(remoteStore.get())));
+    conn->to = FdSink(conn->sshConn->in.get());
+    conn->from = FdSource(conn->sshConn->out.get());
+
+    try {
+      conn->to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
+      conn->to.flush();
+
+      unsigned int magic = readInt(conn->from);
+      if (magic != SERVE_MAGIC_2) {
+        throw Error("protocol mismatch with 'nix-store --serve' on '%s'", host);
+      }
+      conn->remoteVersion = readInt(conn->from);
+      if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200) {
+        throw Error("unsupported 'nix-store --serve' protocol version on '%s'",
+                    host);
+      }
+
+    } catch (EndOfFile& e) {
+      throw Error("cannot connect to '%1%'", host);
+    }
+
+    return conn;
+  };
+
+  std::string getUri() override { return absl::StrCat(kUriScheme, host); }
+
+  void queryPathInfoUncached(
+      const Path& path,
+      Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override {
+    try {
+      auto conn(connections->get());
+
+      DLOG(INFO) << "querying remote host '" << host << "' for info on '"
+                 << path << "'";
+
+      conn->to << cmdQueryPathInfos << PathSet{path};
+      conn->to.flush();
+
+      auto info = std::make_shared<ValidPathInfo>();
+      conn->from >> info->path;
+      if (info->path.empty()) {
+        return callback(nullptr);
+      }
+      assert(path == info->path);
+
+      PathSet references;
+      conn->from >> info->deriver;
+      info->references = readStorePaths<PathSet>(*this, conn->from);
+      readLongLong(conn->from);  // download size
+      info->narSize = readLongLong(conn->from);
+
+      if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4) {
+        auto s = readString(conn->from);
+        if (s.empty()) {
+          info->narHash = Hash();
+        } else {
+          auto hash_ = Hash::deserialize(s);
+          info->narHash = Hash::unwrap_throw(hash_);
+        }
+        conn->from >> info->ca;
+        info->sigs = readStrings<StringSet>(conn->from);
+      }
+
+      auto s = readString(conn->from);
+      assert(s.empty());
+
+      callback(std::move(info));
+    } catch (...) {
+      callback.rethrow();
+    }
+  }
+
+  void addToStore(const ValidPathInfo& info, Source& source, RepairFlag repair,
+                  CheckSigsFlag checkSigs,
+                  std::shared_ptr<FSAccessor> accessor) override {
+    DLOG(INFO) << "adding path '" << info.path << "' to remote host '" << host
+               << "'";
+
+    auto conn(connections->get());
+
+    if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
+      conn->to << cmdAddToStoreNar << info.path << info.deriver
+               << info.narHash.to_string(Base16, false) << info.references
+               << info.registrationTime << info.narSize
+               << static_cast<uint64_t>(info.ultimate) << info.sigs << info.ca;
+      try {
+        copyNAR(source, conn->to);
+      } catch (...) {
+        conn->good = false;
+        throw;
+      }
+      conn->to.flush();
+
+    } else {
+      conn->to << cmdImportPaths << 1;
+      try {
+        copyNAR(source, conn->to);
+      } catch (...) {
+        conn->good = false;
+        throw;
+      }
+      conn->to << exportMagic << info.path << info.references << info.deriver
+               << 0 << 0;
+      conn->to.flush();
+    }
+
+    if (readInt(conn->from) != 1) {
+      throw Error(
+          "failed to add path '%s' to remote host '%s', info.path, host");
+    }
+  }
+
+  void narFromPath(const Path& path, Sink& sink) override {
+    auto conn(connections->get());
+
+    conn->to << cmdDumpStorePath << path;
+    conn->to.flush();
+    copyNAR(conn->from, sink);
+  }
+
+  Path queryPathFromHashPart(const std::string& hashPart) override {
+    unsupported("queryPathFromHashPart");
+  }
+
+  Path addToStore(const std::string& name, const Path& srcPath, bool recursive,
+                  HashType hashAlgo, PathFilter& filter,
+                  RepairFlag repair) override {
+    unsupported("addToStore");
+  }
+
+  Path addTextToStore(const std::string& name, const std::string& s,
+                      const PathSet& references, RepairFlag repair) override {
+    unsupported("addTextToStore");
+  }
+
+  BuildResult buildDerivation(std::ostream& /*log_sink*/, const Path& drvPath,
+                              const BasicDerivation& drv,
+                              BuildMode buildMode) override {
+    auto conn(connections->get());
+
+    conn->to << cmdBuildDerivation << drvPath << drv << settings.maxSilentTime
+             << settings.buildTimeout;
+    if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 2) {
+      conn->to << settings.maxLogSize;
+    }
+    if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3) {
+      conn->to << settings.buildRepeat
+               << static_cast<uint64_t>(settings.enforceDeterminism);
+    }
+
+    conn->to.flush();
+
+    BuildResult status;
+    status.status = static_cast<BuildResult::Status>(readInt(conn->from));
+    conn->from >> status.errorMsg;
+
+    if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3) {
+      conn->from >> status.timesBuilt >> status.isNonDeterministic >>
+          status.startTime >> status.stopTime;
+    }
+
+    return status;
+  }
+
+  void ensurePath(const Path& path) override { unsupported("ensurePath"); }
+
+  void computeFSClosure(const PathSet& paths, PathSet& out,
+                        bool flipDirection = false, bool includeOutputs = false,
+                        bool includeDerivers = false) override {
+    if (flipDirection || includeDerivers) {
+      Store::computeFSClosure(paths, out, flipDirection, includeOutputs,
+                              includeDerivers);
+      return;
+    }
+
+    auto conn(connections->get());
+
+    conn->to << cmdQueryClosure << static_cast<uint64_t>(includeOutputs)
+             << paths;
+    conn->to.flush();
+
+    auto res = readStorePaths<PathSet>(*this, conn->from);
+
+    out.insert(res.begin(), res.end());
+  }
+
+  PathSet queryValidPaths(const PathSet& paths, SubstituteFlag maybeSubstitute =
+                                                    NoSubstitute) override {
+    auto conn(connections->get());
+
+    conn->to << cmdQueryValidPaths << 0u  // lock
+             << maybeSubstitute << paths;
+    conn->to.flush();
+
+    return readStorePaths<PathSet>(*this, conn->from);
+  }
+
+  void connect() override { auto conn(connections->get()); }
+
+  unsigned int getProtocol() override {
+    auto conn(connections->get());
+    return conn->remoteVersion;
+  }
+};
+
+static RegisterStoreImplementation regStore(
+    [](const std::string& uri,
+       const Store::Params& params) -> std::shared_ptr<Store> {
+      if (!absl::StartsWith(uri, kUriScheme)) {
+        return nullptr;
+      }
+      return std::make_shared<LegacySSHStore>(
+          std::string(uri, kUriScheme.size()), params);
+    });
+
+}  // namespace nix