Add download-via-ssh substituter

This substituter connects to a remote host, runs nix-store --serve
there, and then forwards substituter commands on to the remote host and
sends their results to the calling program. The ssh-substituter-hosts
option can be specified as a list of hosts to try.

This is an initial implementation and, while it works, it has some
limitations:

* Only the first host is used
* There is no caching of query results (all queries are sent to the
  remote machine)
* There is no informative output (such as progress bars)
* Some failure modes may cause unhelpful error messages
* There is no concept of trusted-ssh-substituter-hosts

Signed-off-by: Shea Levy <shea@shealevy.com>
This commit is contained in:
Shea Levy 2014-02-08 00:05:46 -05:00
parent 5671188eb2
commit 64e23d0a38
6 changed files with 168 additions and 0 deletions

View file

@ -10,6 +10,7 @@ makefiles = \
src/nix-instantiate/local.mk \ src/nix-instantiate/local.mk \
src/nix-env/local.mk \ src/nix-env/local.mk \
src/nix-daemon/local.mk \ src/nix-daemon/local.mk \
src/download-via-ssh/local.mk \
src/nix-log2xml/local.mk \ src/nix-log2xml/local.mk \
src/bsdiff-4.3/local.mk \ src/bsdiff-4.3/local.mk \
perl/local.mk \ perl/local.mk \

View file

@ -0,0 +1,129 @@
#include "shared.hh"
#include "util.hh"
#include "serialise.hh"
#include "archive.hh"
#include "affinity.hh"
#include "globals.hh"
#include <iostream>
#include <unistd.h>
using namespace nix;
using std::pair;
using std::cout;
using std::endl;
// !!! TODO:
// * Respect more than the first host
// * use a database
// * show progress
static pair<FdSink, FdSource> connect(string conn) {
Pipe to, from;
to.create();
from.create();
pid_t child = fork();
switch (child) {
case -1:
throw SysError("unable to fork");
case 0:
try {
restoreAffinity();
if (dup2(to.readSide, STDIN_FILENO) == -1)
throw SysError("dupping stdin");
if (dup2(from.writeSide, STDOUT_FILENO) == -1)
throw SysError("dupping stdout");
execlp("ssh"
, "ssh"
, "-x"
, "-T"
, conn.c_str()
, "nix-store --serve"
, NULL);
throw SysError("executing ssh");
} catch (std::exception & e) {
std::cerr << "error: " << e.what() << std::endl;
}
_exit(1);
}
// If child exits unexpectedly, we'll EPIPE. If we exit unexpectedly, child will
// So no need to keep track of it.
return pair<FdSink, FdSource>(to.writeSide.borrow(), from.readSide.borrow());
}
static void substitute(pair<FdSink, FdSource> & pipes, Path storePath, Path destPath) {
writeString("substitute", pipes.first);
writeString(storePath, pipes.first);
pipes.first.flush();
restorePath(destPath, pipes.second);
cout << endl;
}
static void query(pair<FdSink, FdSource> & pipes) {
using std::cin;
writeString("query", pipes.first);
for (string line; getline(cin, line);) {
Strings tokenized = tokenizeString<Strings>(line);
string cmd = tokenized.front();
writeString(cmd, pipes.first);
tokenized.pop_front();
foreach (Strings::iterator, i, tokenized)
writeStrings(tokenized, pipes.first);
pipes.first.flush();
if (cmd == "have") {
PathSet paths = readStrings<PathSet>(pipes.second);
foreach (PathSet::iterator, i, paths)
cout << *i << endl;
} else if (cmd == "info") {
for (Path path = readString(pipes.second); !path.empty(); path = readString(pipes.second)) {
cout << path << endl;
cout << readString(pipes.second) << endl;
PathSet references = readStrings<PathSet>(pipes.second);
cout << references.size() << endl;
foreach (PathSet::iterator, i, references)
cout << *i << endl;
cout << readLongLong(pipes.second) << endl;
cout << readLongLong(pipes.second) << endl;
}
} else
throw Error(format("Unknown substituter query `%1%'") % cmd);
cout << endl;
}
writeString("", pipes.first);
}
void run(Strings args)
{
if (args.empty())
throw UsageError("download-via-ssh requires an argument");
if (settings.sshSubstituterHosts.empty())
return;
cout << endl;
pair<FdSink, FdSource> pipes = connect(settings.sshSubstituterHosts.front());
Strings::iterator i = args.begin();
if (*i == "--query")
query(pipes);
else if (*i == "--substitute")
if (args.size() != 3)
throw UsageError("download-via-ssh: --substitute takes exactly two arguments");
else {
Path storePath = *++i;
Path destPath = *++i;
substitute(pipes, storePath, destPath);
}
else
throw UsageError(format("download-via-ssh: unknown command `%1%'") % *i);
}
void printHelp()
{
std::cerr << "Usage: download-via-ssh --query|--substitute store-path dest-path" << std::endl;
}
string programId = "download-via-ssh";

View file

@ -0,0 +1,9 @@
programs += download-via-ssh
download-via-ssh_DIR := $(d)
download-via-ssh_SOURCES := $(d)/download-via-ssh.cc
download-via-ssh_INSTALL_DIR := $(libexecdir)/nix/substituters
download-via-ssh_LIBS = libmain libstore libutil libformat

View file

@ -223,6 +223,9 @@ static void initAndRun(int argc, char * * argv)
else remaining.push_back(arg); else remaining.push_back(arg);
} }
if (char *pack = getenv("_NIX_OPTIONS"))
settings.unpack(pack);
settings.update(); settings.update();
run(remaining); run(remaining);

View file

@ -79,6 +79,7 @@ void Settings::processEnvironment()
#endif #endif
substituters.push_back(nixLibexecDir + "/nix/substituters/download-using-manifests.pl"); substituters.push_back(nixLibexecDir + "/nix/substituters/download-using-manifests.pl");
substituters.push_back(nixLibexecDir + "/nix/substituters/download-from-binary-cache.pl"); substituters.push_back(nixLibexecDir + "/nix/substituters/download-from-binary-cache.pl");
substituters.push_back(nixLibexecDir + "/nix/substituters/download-via-ssh");
} else } else
substituters = tokenizeString<Strings>(subs, ":"); substituters = tokenizeString<Strings>(subs, ":");
} }
@ -151,6 +152,7 @@ void Settings::update()
get(gcKeepDerivations, "gc-keep-derivations"); get(gcKeepDerivations, "gc-keep-derivations");
get(autoOptimiseStore, "auto-optimise-store"); get(autoOptimiseStore, "auto-optimise-store");
get(envKeepDerivations, "env-keep-derivations"); get(envKeepDerivations, "env-keep-derivations");
get(sshSubstituterHosts, "ssh-substituter-hosts");
} }
@ -182,6 +184,13 @@ void Settings::get(StringSet & res, const string & name)
res.insert(ss.begin(), ss.end()); res.insert(ss.begin(), ss.end());
} }
void Settings::get(Strings & res, const string & name)
{
SettingsMap::iterator i = settings.find(name);
if (i == settings.end()) return;
res = tokenizeString<Strings>(i->second);
}
template<class N> void Settings::get(N & res, const string & name) template<class N> void Settings::get(N & res, const string & name)
{ {
@ -206,6 +215,17 @@ string Settings::pack()
} }
void Settings::unpack(string pack) {
Strings lines = tokenizeString<Strings>(pack, "\n");
foreach (Strings::iterator, i, lines) {
string::size_type eq = i->find('=');
if (eq == string::npos)
throw Error("illegal option name/value");
set(i->substr(0, eq), i->substr(eq + 1));
}
}
Settings::SettingsMap Settings::getOverrides() Settings::SettingsMap Settings::getOverrides()
{ {
return overrides; return overrides;

View file

@ -25,6 +25,8 @@ struct Settings {
string pack(); string pack();
void unpack(string pack);
SettingsMap getOverrides(); SettingsMap getOverrides();
/* The directory where we store sources and derived files. */ /* The directory where we store sources and derived files. */
@ -144,6 +146,9 @@ struct Settings {
chroot. */ chroot. */
StringSet dirsInChroot; StringSet dirsInChroot;
/* Set of ssh connection strings for the ssh substituter */
Strings sshSubstituterHosts;
/* Whether to impersonate a Linux 2.6 machine on newer kernels. */ /* Whether to impersonate a Linux 2.6 machine on newer kernels. */
bool impersonateLinux26; bool impersonateLinux26;
@ -195,6 +200,7 @@ private:
void get(string & res, const string & name); void get(string & res, const string & name);
void get(bool & res, const string & name); void get(bool & res, const string & name);
void get(StringSet & res, const string & name); void get(StringSet & res, const string & name);
void get(Strings & res, const string & name);
template<class N> void get(N & res, const string & name); template<class N> void get(N & res, const string & name);
}; };