2020-09-07 19:01:49 +02:00
|
|
|
#include <absl/strings/match.h>
|
|
|
|
#include <absl/strings/str_cat.h>
|
2020-05-27 22:56:34 +02:00
|
|
|
#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"
|
2017-02-07 19:28:40 +01:00
|
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
2020-09-07 19:01:49 +02:00
|
|
|
constexpr std::string_view kUriScheme = "ssh://";
|
2017-02-07 19:28:40 +01:00
|
|
|
|
|
|
|
struct LegacySSHStore : public Store {
|
2017-04-13 15:55:38 +02:00
|
|
|
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"};
|
2018-03-21 15:10:46 +01:00
|
|
|
const Setting<Path> remoteProgram{
|
|
|
|
this, "nix-store", "remote-program",
|
|
|
|
"path to the nix-store executable on the remote system"};
|
2018-08-03 18:07:46 +02:00
|
|
|
const Setting<std::string> remoteStore{
|
|
|
|
this, "", "remote-store", "URI of the store on the remote system"};
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-05-02 12:01:46 +02:00
|
|
|
// Hack for getting remote build log output.
|
|
|
|
const Setting<int> logFD{
|
|
|
|
this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
struct Connection {
|
2017-03-03 19:05:50 +01:00
|
|
|
std::unique_ptr<SSHMaster::Connection> sshConn;
|
2017-02-07 19:28:40 +01:00
|
|
|
FdSink to;
|
|
|
|
FdSource from;
|
2020-08-02 00:32:00 +02:00
|
|
|
int remoteVersion;
|
2018-08-03 21:10:03 +02:00
|
|
|
bool good = true;
|
2017-02-07 19:28:40 +01:00
|
|
|
};
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-03-03 19:05:50 +01:00
|
|
|
std::string host;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
ref<Pool<Connection>> connections;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-03-03 19:05:50 +01:00
|
|
|
SSHMaster master;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
LegacySSHStore(const std::string& host, const Params& params)
|
2017-02-07 19:28:40 +01:00
|
|
|
: Store(params),
|
|
|
|
host(host),
|
|
|
|
connections(make_ref<Pool<Connection>>(
|
2017-04-13 15:55:38 +02:00
|
|
|
std::max(1, (int)maxConnections),
|
2017-02-07 19:28:40 +01:00
|
|
|
[this]() { return openConnection(); },
|
2018-08-03 21:10:03 +02:00
|
|
|
[](const ref<Connection>& r) { return r->good; })),
|
2017-03-03 19:05:50 +01:00
|
|
|
master(host, sshKey,
|
|
|
|
// Use SSH master only if using more than 1 connection.
|
|
|
|
connections->capacity() > 1, compress, logFD) {}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
ref<Connection> openConnection() {
|
|
|
|
auto conn = make_ref<Connection>();
|
2018-08-03 18:07:46 +02:00
|
|
|
conn->sshConn = master.startCommand(
|
2018-08-06 17:27:08 +02:00
|
|
|
fmt("%s --serve --write", remoteProgram) +
|
2020-05-20 23:27:37 +02:00
|
|
|
(remoteStore.get().empty()
|
2020-05-17 17:31:57 +02:00
|
|
|
? ""
|
2018-08-03 18:07:46 +02:00
|
|
|
: " --store " + shellEscape(remoteStore.get())));
|
2017-03-03 19:05:50 +01:00
|
|
|
conn->to = FdSink(conn->sshConn->in.get());
|
2017-05-02 12:01:46 +02:00
|
|
|
conn->from = FdSource(conn->sshConn->out.get());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
|
|
try {
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
|
|
|
|
conn->to.flush();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
unsigned int magic = readInt(conn->from);
|
|
|
|
if (magic != SERVE_MAGIC_2) {
|
2017-05-02 12:01:46 +02:00
|
|
|
throw Error("protocol mismatch with 'nix-store --serve' on '%s'", host);
|
2020-05-19 21:47:23 +02:00
|
|
|
}
|
2017-05-01 16:08:13 +02:00
|
|
|
conn->remoteVersion = readInt(conn->from);
|
2017-05-02 12:01:46 +02:00
|
|
|
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200) {
|
2017-07-30 13:27:57 +02:00
|
|
|
throw Error("unsupported 'nix-store --serve' protocol version on '%s'",
|
2020-05-17 17:31:57 +02:00
|
|
|
host);
|
2020-05-19 21:47:23 +02:00
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
} catch (EndOfFile& e) {
|
2017-07-30 13:27:57 +02:00
|
|
|
throw Error("cannot connect to '%1%'", host);
|
2017-02-07 19:28:40 +01:00
|
|
|
}
|
|
|
|
|
2017-05-01 16:08:13 +02:00
|
|
|
return conn;
|
2017-02-07 19:28:40 +01:00
|
|
|
};
|
|
|
|
|
2020-09-07 19:01:49 +02:00
|
|
|
std::string getUri() override { return absl::StrCat(kUriScheme, host); }
|
2017-02-07 19:28:40 +01:00
|
|
|
|
2017-07-30 13:27:57 +02:00
|
|
|
void queryPathInfoUncached(
|
|
|
|
const Path& path,
|
|
|
|
Callback<std::shared_ptr<ValidPathInfo>> callback) noexcept override {
|
2020-05-17 17:31:57 +02:00
|
|
|
try {
|
2017-07-30 13:27:57 +02:00
|
|
|
auto conn(connections->get());
|
2017-02-07 19:28:40 +01:00
|
|
|
|
2020-05-19 02:02:44 +02:00
|
|
|
DLOG(INFO) << "querying remote host '" << host << "' for info on '"
|
|
|
|
<< path << "'";
|
2017-02-07 19:28:40 +01:00
|
|
|
|
|
|
|
conn->to << cmdQueryPathInfos << PathSet{path};
|
2018-03-27 22:16:01 +02:00
|
|
|
conn->to.flush();
|
2017-02-07 19:28:40 +01:00
|
|
|
|
|
|
|
auto info = std::make_shared<ValidPathInfo>();
|
|
|
|
conn->from >> info->path;
|
|
|
|
if (info->path.empty()) {
|
|
|
|
return callback(nullptr);
|
2020-05-19 19:55:58 +02:00
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
assert(path == info->path);
|
|
|
|
|
2017-09-08 16:55:27 +02:00
|
|
|
PathSet references;
|
|
|
|
conn->from >> info->deriver;
|
|
|
|
info->references = readStorePaths<PathSet>(*this, conn->from);
|
2017-02-07 19:28:40 +01:00
|
|
|
readLongLong(conn->from); // download size
|
2017-09-08 16:55:27 +02:00
|
|
|
info->narSize = readLongLong(conn->from);
|
|
|
|
|
2018-08-31 01:00:01 +02:00
|
|
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 4) {
|
2017-02-07 19:28:40 +01:00
|
|
|
auto s = readString(conn->from);
|
2020-07-28 04:57:04 +02:00
|
|
|
if (s.empty()) {
|
|
|
|
info->narHash = Hash();
|
|
|
|
} else {
|
|
|
|
auto hash_ = Hash::deserialize(s);
|
|
|
|
info->narHash = Hash::unwrap_throw(hash_);
|
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->from >> info->ca;
|
|
|
|
info->sigs = readStrings<StringSet>(conn->from);
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
|
2018-03-27 22:16:01 +02:00
|
|
|
auto s = readString(conn->from);
|
2020-05-20 23:27:37 +02:00
|
|
|
assert(s.empty());
|
2017-02-07 19:28:40 +01:00
|
|
|
|
2017-07-30 13:27:57 +02:00
|
|
|
callback(std::move(info));
|
2018-08-03 21:10:03 +02:00
|
|
|
} catch (...) {
|
2017-07-30 13:27:57 +02:00
|
|
|
callback.rethrow();
|
2017-02-07 19:28:40 +01:00
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
void addToStore(const ValidPathInfo& info, Source& source, RepairFlag repair,
|
|
|
|
CheckSigsFlag checkSigs,
|
|
|
|
std::shared_ptr<FSAccessor> accessor) override {
|
2020-05-19 02:02:44 +02:00
|
|
|
DLOG(INFO) << "adding path '" << info.path << "' to remote host '" << host
|
|
|
|
<< "'";
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
auto conn(connections->get());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2018-08-31 01:00:01 +02:00
|
|
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->to << cmdAddToStoreNar << info.path << info.deriver
|
2018-08-03 21:10:03 +02:00
|
|
|
<< info.narHash.to_string(Base16, false) << info.references
|
2020-05-20 23:27:37 +02:00
|
|
|
<< info.registrationTime << info.narSize
|
|
|
|
<< static_cast<uint64_t>(info.ultimate) << info.sigs << info.ca;
|
2020-05-17 17:31:57 +02:00
|
|
|
try {
|
2018-08-03 21:10:03 +02:00
|
|
|
copyNAR(source, conn->to);
|
2018-03-27 22:16:01 +02:00
|
|
|
} catch (...) {
|
2018-08-03 21:10:03 +02:00
|
|
|
conn->good = false;
|
2020-05-17 17:31:57 +02:00
|
|
|
throw;
|
|
|
|
}
|
2018-08-03 21:10:03 +02:00
|
|
|
conn->to.flush();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
|
|
|
} else {
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->to << cmdImportPaths << 1;
|
2020-05-17 17:31:57 +02:00
|
|
|
try {
|
2018-08-03 21:10:03 +02:00
|
|
|
copyNAR(source, conn->to);
|
|
|
|
} catch (...) {
|
|
|
|
conn->good = false;
|
2020-05-17 17:31:57 +02:00
|
|
|
throw;
|
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->to << exportMagic << info.path << info.references << info.deriver
|
2020-05-17 17:31:57 +02:00
|
|
|
<< 0 << 0;
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->to.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (readInt(conn->from) != 1) {
|
2017-07-30 13:27:57 +02:00
|
|
|
throw Error(
|
|
|
|
"failed to add path '%s' to remote host '%s', info.path, host");
|
2020-05-19 21:47:23 +02:00
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
void narFromPath(const Path& path, Sink& sink) override {
|
|
|
|
auto conn(connections->get());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
conn->to << cmdDumpStorePath << path;
|
2018-08-03 21:10:03 +02:00
|
|
|
conn->to.flush();
|
2018-03-21 22:56:02 +01:00
|
|
|
copyNAR(conn->from, sink);
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
Path queryPathFromHashPart(const std::string& hashPart) override {
|
2019-01-18 13:34:23 +01:00
|
|
|
unsupported("queryPathFromHashPart");
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
Path addToStore(const std::string& name, const Path& srcPath, bool recursive,
|
2017-02-07 19:28:40 +01:00
|
|
|
HashType hashAlgo, PathFilter& filter,
|
2017-06-28 18:11:01 +02:00
|
|
|
RepairFlag repair) override {
|
2019-01-18 13:34:23 +01:00
|
|
|
unsupported("addToStore");
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2020-05-24 23:29:21 +02:00
|
|
|
Path addTextToStore(const std::string& name, const std::string& s,
|
2017-06-28 18:11:01 +02:00
|
|
|
const PathSet& references, RepairFlag repair) override {
|
2019-01-18 13:34:23 +01:00
|
|
|
unsupported("addTextToStore");
|
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
BuildResult buildDerivation(const Path& drvPath, const BasicDerivation& drv,
|
|
|
|
BuildMode buildMode) override {
|
2017-05-01 16:08:13 +02:00
|
|
|
auto conn(connections->get());
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-05-01 16:08:13 +02:00
|
|
|
conn->to << cmdBuildDerivation << drvPath << drv << settings.maxSilentTime
|
|
|
|
<< settings.buildTimeout;
|
|
|
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 2) {
|
|
|
|
conn->to << settings.maxLogSize;
|
2020-05-19 21:47:23 +02:00
|
|
|
}
|
2017-05-01 16:08:13 +02:00
|
|
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3) {
|
2020-05-20 23:27:37 +02:00
|
|
|
conn->to << settings.buildRepeat
|
|
|
|
<< static_cast<uint64_t>(settings.enforceDeterminism);
|
2020-05-19 21:47:23 +02:00
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-05-01 16:08:13 +02:00
|
|
|
conn->to.flush();
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-05-01 16:08:13 +02:00
|
|
|
BuildResult status;
|
2020-08-02 02:17:44 +02:00
|
|
|
status.status = static_cast<BuildResult::Status>(readInt(conn->from));
|
2017-05-01 16:08:13 +02:00
|
|
|
conn->from >> status.errorMsg;
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-05-01 16:08:13 +02:00
|
|
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3) {
|
|
|
|
conn->from >> status.timesBuilt >> status.isNonDeterministic >>
|
|
|
|
status.startTime >> status.stopTime;
|
2020-05-19 21:47:23 +02:00
|
|
|
}
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-05-01 16:08:13 +02:00
|
|
|
return status;
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
|
|
|
|
2017-02-07 19:28:40 +01:00
|
|
|
void ensurePath(const Path& path) override { unsupported("ensurePath"); }
|
2020-05-17 17:31:57 +02:00
|
|
|
|
2017-03-16 11:44:01 +01:00
|
|
|
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);
|
2020-05-17 17:31:57 +02:00
|
|
|
return;
|
2017-05-01 16:08:13 +02:00
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
|
2017-03-16 11:44:01 +01:00
|
|
|
auto conn(connections->get());
|
|
|
|
|
2020-05-20 23:27:37 +02:00
|
|
|
conn->to << cmdQueryClosure << static_cast<uint64_t>(includeOutputs)
|
|
|
|
<< paths;
|
2017-03-16 11:44:01 +01:00
|
|
|
conn->to.flush();
|
|
|
|
|
|
|
|
auto res = readStorePaths<PathSet>(*this, conn->from);
|
|
|
|
|
|
|
|
out.insert(res.begin(), res.end());
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
2017-03-16 11:44:01 +01:00
|
|
|
|
|
|
|
PathSet queryValidPaths(const PathSet& paths, SubstituteFlag maybeSubstitute =
|
|
|
|
NoSubstitute) override {
|
|
|
|
auto conn(connections->get());
|
|
|
|
|
2020-05-20 23:27:37 +02:00
|
|
|
conn->to << cmdQueryValidPaths << 0u // lock
|
2017-06-28 18:11:01 +02:00
|
|
|
<< maybeSubstitute << paths;
|
2017-03-16 13:50:01 +01:00
|
|
|
conn->to.flush();
|
|
|
|
|
|
|
|
return readStorePaths<PathSet>(*this, conn->from);
|
2020-05-17 17:31:57 +02:00
|
|
|
}
|
2017-03-16 13:50:01 +01:00
|
|
|
|
|
|
|
void connect() override { auto conn(connections->get()); }
|
2017-05-02 14:18:46 +02:00
|
|
|
|
2018-08-30 23:28:47 +02:00
|
|
|
unsigned int getProtocol() override {
|
|
|
|
auto conn(connections->get());
|
|
|
|
return conn->remoteVersion;
|
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
static RegisterStoreImplementation regStore(
|
|
|
|
[](const std::string& uri,
|
|
|
|
const Store::Params& params) -> std::shared_ptr<Store> {
|
2020-09-07 19:01:49 +02:00
|
|
|
if (!absl::StartsWith(uri, kUriScheme)) {
|
2020-05-20 05:33:07 +02:00
|
|
|
return nullptr;
|
2020-05-19 19:55:58 +02:00
|
|
|
}
|
2017-02-07 19:28:40 +01:00
|
|
|
return std::make_shared<LegacySSHStore>(
|
2020-09-07 19:01:49 +02:00
|
|
|
std::string(uri, kUriScheme.size()), params);
|
2017-02-07 19:28:40 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
} // namespace nix
|