Merge remote-tracking branch 'shlevy/ssh-substituter'
This commit is contained in:
commit
61fd494d76
8 changed files with 271 additions and 0 deletions
1
Makefile
1
Makefile
|
@ -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 \
|
||||||
|
|
138
src/download-via-ssh/download-via-ssh.cc
Normal file
138
src/download-via-ssh/download-via-ssh.cc
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
#include "shared.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
#include "serialise.hh"
|
||||||
|
#include "archive.hh"
|
||||||
|
#include "affinity.hh"
|
||||||
|
#include "globals.hh"
|
||||||
|
#include "serve-protocol.hh"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
// !!! TODO:
|
||||||
|
// * Respect more than the first host
|
||||||
|
// * use a database
|
||||||
|
// * show progress
|
||||||
|
|
||||||
|
static std::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 or EOF early.
|
||||||
|
// If we exit unexpectedly, child will EPIPE or EOF early.
|
||||||
|
// So no need to keep track of it.
|
||||||
|
|
||||||
|
return std::pair<FdSink, FdSource>(to.writeSide.borrow(), from.readSide.borrow());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void substitute(std::pair<FdSink, FdSource> & pipes, Path storePath, Path destPath) {
|
||||||
|
writeInt(cmdSubstitute, pipes.first);
|
||||||
|
writeString(storePath, pipes.first);
|
||||||
|
pipes.first.flush();
|
||||||
|
restorePath(destPath, pipes.second);
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void query(std::pair<FdSink, FdSource> & pipes) {
|
||||||
|
writeInt(cmdQuery, pipes.first);
|
||||||
|
for (string line; getline(std::cin, line);) {
|
||||||
|
Strings tokenized = tokenizeString<Strings>(line);
|
||||||
|
string cmd = tokenized.front();
|
||||||
|
tokenized.pop_front();
|
||||||
|
if (cmd == "have") {
|
||||||
|
writeInt(qCmdHave, pipes.first);
|
||||||
|
writeStrings(tokenized, pipes.first);
|
||||||
|
pipes.first.flush();
|
||||||
|
PathSet paths = readStrings<PathSet>(pipes.second);
|
||||||
|
foreach (PathSet::iterator, i, paths)
|
||||||
|
std::cout << *i << std::endl;
|
||||||
|
} else if (cmd == "info") {
|
||||||
|
writeInt(qCmdInfo, pipes.first);
|
||||||
|
writeStrings(tokenized, pipes.first);
|
||||||
|
pipes.first.flush();
|
||||||
|
for (Path path = readString(pipes.second); !path.empty(); path = readString(pipes.second)) {
|
||||||
|
std::cout << path << std::endl;
|
||||||
|
std::cout << readString(pipes.second) << std::endl;
|
||||||
|
PathSet references = readStrings<PathSet>(pipes.second);
|
||||||
|
std::cout << references.size() << std::endl;
|
||||||
|
foreach (PathSet::iterator, i, references)
|
||||||
|
std::cout << *i << std::endl;
|
||||||
|
std::cout << readLongLong(pipes.second) << std::endl;
|
||||||
|
std::cout << readLongLong(pipes.second) << std::endl;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw Error(format("unknown substituter query `%1%'") % cmd);
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(Strings args)
|
||||||
|
{
|
||||||
|
if (args.empty())
|
||||||
|
throw UsageError("download-via-ssh requires an argument");
|
||||||
|
|
||||||
|
if (settings.sshSubstituterHosts.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
std::pair<FdSink, FdSource> pipes = connect(settings.sshSubstituterHosts.front());
|
||||||
|
|
||||||
|
/* Exchange the greeting */
|
||||||
|
writeInt(SERVE_MAGIC_1, pipes.first);
|
||||||
|
pipes.first.flush();
|
||||||
|
unsigned int magic = readInt(pipes.second);
|
||||||
|
if (magic != SERVE_MAGIC_2)
|
||||||
|
throw Error("protocol mismatch");
|
||||||
|
readInt(pipes.second); // Server version, unused for now
|
||||||
|
writeInt(SERVE_PROTOCOL_VERSION, pipes.first);
|
||||||
|
pipes.first.flush();
|
||||||
|
|
||||||
|
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";
|
11
src/download-via-ssh/local.mk
Normal file
11
src/download-via-ssh/local.mk
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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_CXXFLAGS = -Isrc/nix-store
|
||||||
|
|
||||||
|
download-via-ssh_LIBS = libmain libstore libutil libformat
|
|
@ -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);
|
||||||
|
|
|
@ -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(const 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;
|
||||||
|
|
|
@ -25,6 +25,8 @@ struct Settings {
|
||||||
|
|
||||||
string pack();
|
string pack();
|
||||||
|
|
||||||
|
void unpack(const 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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "xmlgraph.hh"
|
#include "xmlgraph.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
|
#include "serve-protocol.hh"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -834,6 +835,71 @@ static void opClearFailedPaths(Strings opFlags, Strings opArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Serve the nix store in a way usable by a restricted ssh user
|
||||||
|
static void opServe(Strings opFlags, Strings opArgs)
|
||||||
|
{
|
||||||
|
if (!opArgs.empty() || !opFlags.empty())
|
||||||
|
throw UsageError("no arguments or flags expected");
|
||||||
|
|
||||||
|
FdSource in(STDIN_FILENO);
|
||||||
|
FdSink out(STDOUT_FILENO);
|
||||||
|
|
||||||
|
/* Exchange the greeting. */
|
||||||
|
unsigned int magic = readInt(in);
|
||||||
|
if (magic != SERVE_MAGIC_1) throw Error("protocol mismatch");
|
||||||
|
writeInt(SERVE_MAGIC_2, out);
|
||||||
|
writeInt(SERVE_PROTOCOL_VERSION, out);
|
||||||
|
out.flush();
|
||||||
|
readInt(in); // Client version, unused for now
|
||||||
|
|
||||||
|
ServeCommand cmd = (ServeCommand) readInt(in);
|
||||||
|
switch (cmd) {
|
||||||
|
case cmdQuery:
|
||||||
|
while (true) {
|
||||||
|
QueryCommand qCmd;
|
||||||
|
try {
|
||||||
|
qCmd = (QueryCommand) readInt(in);
|
||||||
|
} catch (EndOfFile & e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (qCmd) {
|
||||||
|
case qCmdHave: {
|
||||||
|
PathSet paths = readStrings<PathSet>(in);
|
||||||
|
writeStrings(store->queryValidPaths(paths), out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case qCmdInfo: {
|
||||||
|
PathSet paths = readStrings<PathSet>(in);
|
||||||
|
// !!! Maybe we want a queryPathInfos?
|
||||||
|
foreach (PathSet::iterator, i, paths) {
|
||||||
|
if (!store->isValidPath(*i))
|
||||||
|
continue;
|
||||||
|
ValidPathInfo info = store->queryPathInfo(*i);
|
||||||
|
writeString(info.path, out);
|
||||||
|
writeString(info.deriver, out);
|
||||||
|
writeStrings(info.references, out);
|
||||||
|
// !!! Maybe we want compression?
|
||||||
|
writeLongLong(info.narSize, out); // downloadSize
|
||||||
|
writeLongLong(info.narSize, out);
|
||||||
|
}
|
||||||
|
writeString("", out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw Error(format("unknown serve query `%1%'") % cmd);
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case cmdSubstitute:
|
||||||
|
dumpPath(readString(in), out);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Error(format("unknown serve command `%1%'") % cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Scan the arguments; find the operation, set global flags, put all
|
/* Scan the arguments; find the operation, set global flags, put all
|
||||||
other flags in a list, and put all other arguments in another
|
other flags in a list, and put all other arguments in another
|
||||||
list. */
|
list. */
|
||||||
|
@ -904,6 +970,8 @@ void run(Strings args)
|
||||||
indirectRoot = true;
|
indirectRoot = true;
|
||||||
else if (arg == "--no-output")
|
else if (arg == "--no-output")
|
||||||
noOutput = true;
|
noOutput = true;
|
||||||
|
else if (arg == "--serve")
|
||||||
|
op = opServe;
|
||||||
else if (arg[0] == '-') {
|
else if (arg[0] == '-') {
|
||||||
opFlags.push_back(arg);
|
opFlags.push_back(arg);
|
||||||
if (arg == "--max-freed" || arg == "--max-links" || arg == "--max-atime") { /* !!! hack */
|
if (arg == "--max-freed" || arg == "--max-links" || arg == "--max-atime") { /* !!! hack */
|
||||||
|
|
24
src/nix-store/serve-protocol.hh
Normal file
24
src/nix-store/serve-protocol.hh
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
#define SERVE_MAGIC_1 0x390c9deb
|
||||||
|
#define SERVE_MAGIC_2 0x5452eecb
|
||||||
|
|
||||||
|
#define SERVE_PROTOCOL_VERSION 0x101
|
||||||
|
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||||
|
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
cmdQuery = 0,
|
||||||
|
cmdSubstitute = 1,
|
||||||
|
} ServeCommand;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
qCmdHave = 0,
|
||||||
|
qCmdInfo = 1,
|
||||||
|
} QueryCommand;
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue