diff --git a/src/libstore/build/derivation-building-goal.cc b/src/libstore/build/derivation-building-goal.cc index 9cc3016d0dd1..e1399a0cbc92 100644 --- a/src/libstore/build/derivation-building-goal.cc +++ b/src/libstore/build/derivation-building-goal.cc @@ -18,6 +18,7 @@ #include "nix/store/local-store.hh" // TODO remove, along with remaining downcasts #include "nix/store/outputs-query.hh" #include "nix/store/globals.hh" +#include "nix/store/machines.hh" #include "nix/util/current-process.hh" #include @@ -1233,9 +1234,27 @@ HookReply DerivationBuildingGoal::tryBuildHook(const DerivationOptions( - worker.settings.buildHook, std::chrono::milliseconds(worker.settings.buildHookKillTimeout)); + if (!worker.hook) { + auto timeout = std::chrono::milliseconds(worker.settings.buildHookKillTimeout); + /* Only a configured hook runs as a program. Compare by value: + `loadConfFile` resets `isOverridden()`. */ + if (!worker.settings.buildHook.isDefault()) + worker.hook = HookInstance::external(worker.settings.buildHook, timeout); + else { + /* No builders: the hook would decline permanently. */ + bool haveMachines = true; + try { + haveMachines = !Machine::parseConfig({settings.thisSystem}, worker.settings.builders).empty(); + } catch (Error &) { + /* the hook reports it */ + } + if (!haveMachines) { + worker.tryBuildHook = false; + return rpDecline; + } + worker.hook = HookInstance::builtin(worker.store.config, timeout); + } + } try { diff --git a/src/libstore/include/nix/store/machine-selection.hh b/src/libstore/include/nix/store/machine-selection.hh new file mode 100644 index 000000000000..a00ac5448181 --- /dev/null +++ b/src/libstore/include/nix/store/machine-selection.hh @@ -0,0 +1,85 @@ +#pragma once +///@file + +#include +#include + +#include "nix/store/machines.hh" +#include "nix/store/pathlocks.hh" + +namespace nix { + +class Store; + +/** + * The directory holding the slot locks that record how many builds are + * running on each machine: `/current-load`. + */ +std::filesystem::path getCurrentLoadDir(Store & store); + +/** + * Replace `/` with `_` so that a store URI can be used as a single path + * component of a lock file name. + */ +std::string escapeUri(std::string uri); + +/** + * Whether `machine` could run a build for `neededSystem` with + * `requiredFeatures` at all, ignoring how busy it currently is. + */ +bool machineIsCandidate(const Machine & machine, const std::string & neededSystem, const StringSet & requiredFeatures); + +/** + * Whether `cand`, running `candLoad` builds, beats `best`, running + * `bestLoad`. Load is divided by speed factor; ties go to the faster. + */ +bool machineIsBetter(const Machine & cand, uint64_t candLoad, const Machine & best, uint64_t bestLoad); + +/** + * A machine with one of its build slots reserved. The reservation lasts + * as long as `slotLock` is held. + */ +struct AcquiredMachine +{ + /** + * Borrowed from the `machines` argument of `acquireMachineSlot`. + */ + Machine * machine; + AutoCloseFD slotLock; +}; + +/** + * Why `acquireMachineSlot` found no machine. + */ +enum class NoMachine { + /** + * No machine supports this system and feature set, so waiting would + * not help. + */ + WrongType, + /** + * A machine of the right type exists, but every one of its slots is + * taken. Waiting may help. + */ + AllBusy, +}; + +/** + * Reserve a build slot on the least loaded machine that can run this + * build, under a lock on `currentLoadDir/main-lock`. The result points + * into `machines`. + */ +std::variant acquireMachineSlot( + const std::filesystem::path & currentLoadDir, + Machines & machines, + const std::string & neededSystem, + const StringSet & requiredFeatures); + +/** + * Take the lock that serialises uploads to `storeUri`, so that + * concurrent builds do not each copy the same closure to the same + * machine. + */ +AutoCloseFD openUploadLock(const std::filesystem::path & currentLoadDir, std::string_view storeUri); + +} // namespace nix diff --git a/src/libstore/include/nix/store/meson.build b/src/libstore/include/nix/store/meson.build index 80468ce37458..24d254100455 100644 --- a/src/libstore/include/nix/store/meson.build +++ b/src/libstore/include/nix/store/meson.build @@ -63,6 +63,7 @@ headers = [ config_pub_h ] + files( 'local-settings.hh', 'local-store.hh', 'log-store.hh', + 'machine-selection.hh', 'machines.hh', 'make-content-addressed.hh', 'names.hh', diff --git a/src/libstore/include/nix/store/worker-settings.hh b/src/libstore/include/nix/store/worker-settings.hh index ab08e20176d3..67561f9c69c6 100644 --- a/src/libstore/include/nix/store/worker-settings.hh +++ b/src/libstore/include/nix/store/worker-settings.hh @@ -122,10 +122,9 @@ public: {"nix", "__build-remote"}, "build-hook", R"( - The path to the helper program that executes remote builds. + The program that performs remote builds, speaking the build-hook protocol over `stdio` with the Nix that requested them. - Nix communicates with the build hook over `stdio` using a custom protocol to request builds that cannot be performed directly by the Nix daemon. - The default value is the internal Nix binary that implements remote building. + With the default value Nix runs its own implementation in a forked child of the building process (on platforms other than Linux and macOS, as `nix __build-remote`); an explicit `build-hook = nix __build-remote` selects the same. Any other value is run as a separate program. > **Important** > diff --git a/src/libstore/machine-selection.cc b/src/libstore/machine-selection.cc new file mode 100644 index 000000000000..dbf52d112276 --- /dev/null +++ b/src/libstore/machine-selection.cc @@ -0,0 +1,136 @@ +#include + +#ifndef _WIN32 +# ifdef __APPLE__ +# include +# else +# include +# endif +#endif + +#include "nix/store/machine-selection.hh" +#include "nix/store/globals.hh" +#include "nix/store/local-fs-store.hh" +#include "nix/util/hash.hh" + +namespace nix { + +std::filesystem::path getCurrentLoadDir(Store & store) +{ + /* It would be more appropriate to use $XDG_RUNTIME_DIR, since + that gets cleared on reboot, but it wouldn't work on macOS. */ + if (auto * localStore = dynamic_cast(&store)) + return localStore->config.stateDir.get() / "current-load"; + else + return std::filesystem::path{settings.nixStateDir} / "current-load"; +} + +std::string escapeUri(std::string uri) +{ + std::replace(uri.begin(), uri.end(), '/', '_'); + return uri; +} + +static AutoCloseFD openSlotLock(const std::filesystem::path & currentLoadDir, const Machine & m, uint64_t slot) +{ + return openLockFile(currentLoadDir / fmt("%s-%d", escapeUri(m.storeUri.render()), slot), true); +} + +bool machineIsCandidate(const Machine & machine, const std::string & neededSystem, const StringSet & requiredFeatures) +{ + return machine.enabled && machine.systemSupported(neededSystem) && machine.allSupported(requiredFeatures) + && machine.mandatoryMet(requiredFeatures); +} + +bool machineIsBetter(const Machine & cand, uint64_t candLoad, const Machine & best, uint64_t bestLoad) +{ + if (candLoad / cand.speedFactor < bestLoad / best.speedFactor) + return true; + if (candLoad / cand.speedFactor == bestLoad / best.speedFactor) { + if (cand.speedFactor > best.speedFactor) + return true; + if (cand.speedFactor == best.speedFactor) + return candLoad < bestLoad; + } + return false; +} + +std::variant acquireMachineSlot( + const std::filesystem::path & currentLoadDir, + Machines & machines, + const std::string & neededSystem, + const StringSet & requiredFeatures) +{ + AutoCloseFD lock = openLockFile(currentLoadDir / "main-lock", true); + lockFile(lock.get(), ltWrite, true); + + bool rightType = false; + + Machine * bestMachine = nullptr; + uint64_t bestLoad = 0; + AutoCloseFD bestSlotLock; + + for (auto & m : machines) { + debug("considering building on remote machine '%s'", m.storeUri.render()); + + if (!machineIsCandidate(m, neededSystem, requiredFeatures)) + continue; + + rightType = true; + + AutoCloseFD free; + uint64_t load = 0; + for (uint64_t slot = 0; slot < m.maxJobs; ++slot) { + auto slotLock = openSlotLock(currentLoadDir, m, slot); + if (lockFile(slotLock.get(), ltWrite, false)) { + if (!free) { + free = std::move(slotLock); + } + } else { + ++load; + } + } + if (!free) { + continue; + } + if (!bestSlotLock || machineIsBetter(m, load, *bestMachine, bestLoad)) { + bestLoad = load; + bestSlotLock = std::move(free); + bestMachine = &m; + } + } + + if (!bestSlotLock) + return rightType ? NoMachine::AllBusy : NoMachine::WrongType; + +#ifndef _WIN32 +# ifdef __APPLE__ + futimes(bestSlotLock.get(), NULL); +# else + futimens(bestSlotLock.get(), NULL); +# endif +#endif + + return AcquiredMachine{ + .machine = bestMachine, + .slotLock = std::move(bestSlotLock), + }; +} + +AutoCloseFD openUploadLock(const std::filesystem::path & currentLoadDir, std::string_view storeUri) +{ + auto open = [&](std::string_view fileName) { + return openLockFile(currentLoadDir / (escapeUri(std::string{fileName}) + ".upload-lock"), true); + }; + try { + return open(storeUri); + } catch (SystemError & e) { + if (!e.is(std::errc::filename_too_long)) + throw; + /* Try again hashing the store URL so we have a shorter path. */ + auto h = hashString(HashAlgorithm::MD5, storeUri); + return open(h.to_string(HashFormat::Base64, false)); + } +} + +} // namespace nix diff --git a/src/libstore/meson.build b/src/libstore/meson.build index 092c96d7d4e5..b7a8c9a79813 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -301,6 +301,7 @@ sources = files( 'local-overlay-store.cc', 'local-store.cc', 'log-store.cc', + 'machine-selection.cc', 'machines.cc', 'make-content-addressed.cc', 'misc.cc', diff --git a/src/libstore/unix/build/build-remote.cc b/src/libstore/unix/build/build-remote.cc new file mode 100644 index 000000000000..6f3f5b7b1773 --- /dev/null +++ b/src/libstore/unix/build/build-remote.cc @@ -0,0 +1,309 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "nix/store/build/build-remote.hh" +#include "nix/store/build.hh" +#include "nix/store/build-result.hh" +#include "nix/store/derivations.hh" +#include "nix/store/derivation/full-inputs.hh" +#include "nix/store/globals.hh" +#include "nix/store/local-store.hh" +#include "nix/store/machine-selection.hh" +#include "nix/store/machines.hh" +#include "nix/util/experimental-features.hh" +#include "nix/util/strings.hh" + +namespace nix { + +static void handleAlarm(int sig) {} + +static bool allSupportedLocally(Store & store, const StringSet & requiredFeatures) +{ + for (auto & feature : requiredFeatures) + if (!store.config.systemFeatures.get().count(feature)) + return false; + return true; +} + +/* Unbuffered: the parent is waiting for it. */ +static void reply(Descriptor toParent, std::string_view line) +{ + writeFull(toParent, std::string{line} + "\n"); +} + +void serveBuildHook( + ref store, unsigned int maxBuildJobs, Source & from, Descriptor toParent, Descriptor sshErrorFd) +{ + auto currentLoad = getCurrentLoadDir(*store); + + std::shared_ptr sshStore; + AutoCloseFD bestSlotLock; + + auto machines = Machine::parseConfig({settings.thisSystem}, settings.getWorkerSettings().builders); + debug("got %d remote builders", machines.size()); + + std::optional drvPath; + std::string storeUri; + + while (true) { + + try { + auto s = readString(from); + if (s != "try") + return; + } catch (EndOfFile &) { + return; + } + + auto amWilling = readInt(from); + auto neededSystem = readString(from); + drvPath = store->parseStorePath(readString(from)); + auto requiredFeatures = readStrings(from); + + /* Read the request first, or the parent's write can fail with + EPIPE. */ + if (machines.empty()) { + reply(toParent, "# decline-permanently"); + return; + } + + /* It would be possible to build locally after some builds clear out, + so don't show the warning now: */ + bool couldBuildLocally = + maxBuildJobs > 0 + && (neededSystem == settings.thisSystem || settings.extraPlatforms.get().count(neededSystem) > 0) + && allSupportedLocally(*store, requiredFeatures); + /* It's possible to build this locally right now: */ + bool canBuildLocally = amWilling && couldBuildLocally; + + /* Error ignored here, will be caught later */ + mkdir(currentLoad.c_str(), 0777); + + while (true) { + bestSlotLock = -1; + + auto selection = acquireMachineSlot(currentLoad, machines, neededSystem, requiredFeatures); + + if (auto * noMachine = std::get_if(&selection)) { + if (*noMachine == NoMachine::AllBusy && !canBuildLocally) + reply(toParent, "# postpone"); + else { + // build the hint template. + std::string errorText = + "Failed to find a machine for remote build!\n" + "derivation: %s\nrequired (system, features): (%s, [%s])"; + errorText += "\n%s available machines:"; + errorText += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)"; + + for (unsigned int i = 0; i < machines.size(); ++i) + errorText += "\n([%s], %s, [%s], [%s])"; + + // add the template values. + std::string drvstr; + if (drvPath.has_value()) + drvstr = drvPath->to_string(); + else + drvstr = ""; + + auto error = HintFmt::fromFormatString(errorText); + error % drvstr % neededSystem % concatStringsSep(", ", requiredFeatures) + % machines.size(); + + for (auto & m : machines) + error % concatStringsSep(", ", m.systemTypes) % m.maxJobs + % concatStringsSep(", ", m.supportedFeatures) + % concatStringsSep(", ", m.mandatoryFeatures); + + printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str()); + + reply(toParent, "# decline"); + } + break; + } + + auto & acquired = std::get(selection); + auto * bestMachine = acquired.machine; + bestSlotLock = std::move(acquired.slotLock); + + try { + storeUri = bestMachine->storeUri.render(); + + Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", storeUri)); + + sshStore = bestMachine->openStore(); + sshStore->connect(); + } catch (std::exception & e) { + auto msg = chomp(drainFD(sshErrorFd, {.block = false})); + printError("cannot build on '%s': %s%s", storeUri, e.what(), msg.empty() ? "" : ": " + msg); + bestMachine->enabled = false; + continue; + } + + goto connected; + } + } + +connected: + close(sshErrorFd); + + assert(sshStore); + + reply(toParent, "# accept\n" + storeUri); + + /* The other side of the build hook protocol is Nix itself, so + these are always printed in canonical form. */ + auto inputs = [&] { + StorePathSet res; + for (auto & i : readStrings(from)) + res.insert(store->parseStorePathCanonical(i)); + return res; + }(); + + auto wantedOutputs = readStrings(from); + + AutoCloseFD uploadLock = openUploadLock(currentLoad, storeUri); + + { + Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri)); + + auto old = signal(SIGALRM, handleAlarm); + alarm(15 * 60); + if (!lockFile(uploadLock.get(), ltWrite, true)) + printError("somebody is hogging the upload lock for '%s', continuing..."); + alarm(0); + signal(SIGALRM, old); + } + + auto substitute = settings.getWorkerSettings().buildersUseSubstitutes ? Substitute : NoSubstitute; + + { + Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri)); + copyPaths(*store, *sshStore, inputs, NoRepair, NoCheckSigs, substitute); + } + + uploadLock = -1; + + auto drv = store->readDerivation(*drvPath); + + std::optional optResult; + + // If we don't know whether we are trusted (e.g. `ssh://` + // stores), we assume we are. This is necessary for backwards + // compat. + bool trustedOrLegacy = ({ + std::optional trusted = sshStore->isTrustedClient(); + !trusted || *trusted; + }); + + // See the very large comment in `case WorkerProto::Op::BuildDerivation:` in + // `src/libstore/daemon.cc` that explains the trust model here. + // + // This condition mirrors that: that code enforces the "rules" outlined there; + // we do the best we can given those "rules". + if (trustedOrLegacy || type(drv).isCA()) { + // Check if there are any derivation inputs + bool hasInputDrvs = std::ranges::any_of(drv.inputs, [](const auto & input) { + return std::holds_alternative(input.raw()); + }); + + BasicDerivation resolvedDrv{ + .outputs = drv.outputs, + // Hijack the inputs paths of the derivation to include + // all the paths that come from the `inputDrvs` set. We + // don't do that for the derivations whose `inputDrvs` + // is empty because: + // + // 1. It's not needed + // + // 2. Changing the `inputSrcs` set changes the + // associated output ids, which break CA derivations + .inputs = + hasInputDrvs ? inputs : [&] { + StorePathSet srcs; + for (auto & input : drv.inputs) + if (auto * op = std::get_if(&input.raw())) + srcs.insert(op->path); + return srcs; + }(), + .platform = drv.platform, + .builder = drv.builder, + .args = drv.args, + .env = drv.env, + .structuredAttrs = drv.structuredAttrs, + .name = drv.name, + }; + optResult = sshStore->getBuilder()->buildDerivation(*drvPath, resolvedDrv); + auto & result = *optResult; + if (auto * failureP = result.tryGetFailure()) { + if (settings.keepFailed) { + warn( + "The failed build directory was kept on the remote builder due to `--keep-failed`.%s", + (settings.thisSystem == drv.platform || settings.extraPlatforms.get().count(drv.platform) > 0) + ? " You can re-run the command with `--builders ''` to disable remote building for this invocation." + : ""); + } + throw Error( + "build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, failureP->message()); + } + } else { + copyClosure(*store, *sshStore, StorePathSet{*drvPath}, NoRepair, NoCheckSigs, substitute); + auto res = sshStore->getBuilder()->buildPathsWithResults({DerivedPath::Built{ + .drvPath = makeConstantStorePathRef(*drvPath), + .outputs = OutputsSpec::All{}, + }}); + // One path to build should produce exactly one build result + assert(res.size() == 1); + optResult = std::move(res[0]); + } + + std::set missingRealisations; + StorePathSet missingPaths; + if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !type(drv).hasKnownOutputPaths()) { + for (auto & outputName : wantedOutputs) { + auto thisOutputId = DrvOutput{*drvPath, outputName}; + if (!store->queryRealisation(thisOutputId)) { + debug("missing output %s", outputName); + assert(optResult); + auto & result = *optResult; + if (auto * successP = result.tryGetSuccess()) { + auto & success = *successP; + auto i = success.builtOutputs.find(outputName); + assert(i != success.builtOutputs.end()); + auto & newRealisation = i->second; + missingRealisations.insert({newRealisation, thisOutputId}); + missingPaths.insert(newRealisation.outPath); + } + } + } + } else { + auto outputPaths = outputsAndOptPaths(drv, *store); + for (auto & [outputName, hopefullyOutputPath] : outputPaths) { + assert(hopefullyOutputPath.second); + if (!store->isValidPath(*hopefullyOutputPath.second)) + missingPaths.insert(*hopefullyOutputPath.second); + } + } + + if (!missingPaths.empty()) { + Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri)); + if (auto localStore = store.dynamic_pointer_cast()) + for (auto & path : missingPaths) + localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */ + copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute); + } + // XXX: Should be done as part of `copyPaths` + for (auto & realisation : missingRealisations) { + // Should hold, because if the feature isn't enabled the set + // of missing realisations should be empty + experimentalFeatureSettings.require(Xp::CaDerivations); + store->registerDrvOutput(realisation, NoCheckSigs); + } +} + +} // namespace nix diff --git a/src/libstore/unix/build/hook-instance.cc b/src/libstore/unix/build/hook-instance.cc index b5812d728a30..94245c4e548e 100644 --- a/src/libstore/unix/build/hook-instance.cc +++ b/src/libstore/unix/build/hook-instance.cc @@ -1,15 +1,163 @@ #include "nix/util/config-global.hh" #include "nix/store/build/hook-instance.hh" +#include "nix/store/build/build-remote.hh" #include "nix/store/build/child.hh" +#include "nix/store/globals.hh" +#include "nix/store/store-api.hh" #include "nix/util/strings.hh" #include "nix/util/executable-path.hh" +#include "nix/util/signals.hh" +#include "nix/util/util.hh" +#ifdef __linux__ +# include "nix/util/linux-namespaces.hh" +#endif + +#include #include +#include +#include +#include +#include #include +#include + +#include +#include + +/* The fork keeps SQLite's descriptors, found by path. Where the path + is unavailable, run `nix __build-remote` instead. */ +#if defined(__linux__) || defined(F_GETPATH) +# define CAN_FORK_HOOK 1 +#endif namespace nix { -HookInstance::HookInstance(const Strings & _buildHook, std::chrono::milliseconds timeout) +void HookInstance::redirectChildFds() +{ + if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1) + throw SysError("cannot pipe standard error into log file"); + + commonChildInit(); + + if (chdir("/") == -1) + throw SysError("changing into /"); + + /* Dup the communication pipes. */ + if (dup2(toHook.readSide.get(), STDIN_FILENO) == -1) + throw SysError("dupping to-hook read side"); + + /* Use fd 4 for the builder's stdout/stderr. */ + if (dup2(builderOut.writeSide.get(), 4) == -1) + throw SysError("dupping builder's stdout/stderr"); + + /* Hack: pass the read side of that fd to allow the hook to read + SSH error messages. */ + if (dup2(builderOut.readSide.get(), 5) == -1) + throw SysError("dupping builder's stdout/stderr"); +} + +#ifdef CAN_FORK_HOOK +static bool isSQLiteFile(std::string_view path) +{ + for (auto suffix : {".sqlite", ".sqlite-wal", ".sqlite-shm", ".sqlite-journal"}) + if (hasSuffix(path, suffix)) + return true; + return false; +} + +# ifndef __linux__ +/* The path behind `fd`, where the platform can tell. */ +static std::optional fdPath(int fd) +{ + char path[PATH_MAX]; + if (fcntl(fd, F_GETPATH, path) == 0) + return std::string(path); + return std::nullopt; +} +# endif + +/* Whether `fd` is the `-shm` file of a store database. */ +static bool fdIsStoreWALIndex(int fd) +{ +# ifdef __linux__ + std::error_code ec; + auto target = std::filesystem::read_symlink(std::filesystem::path("/proc/self/fd") / std::to_string(fd), ec); + return !ec && hasSuffix(target.string(), "/db.sqlite-shm"); +# else + auto path = fdPath(fd); + return path && hasSuffix(*path, "/db.sqlite-shm"); +# endif +} + +/* Close inherited descriptors except the protocol's, SQLite's and + `extra`. Others would hold the parent's PathLocks; SQLite's + per-process lock state still refers to its own. */ +static void closeExtraHookFDs(const std::vector & extra) +{ + auto keep = [&](int fd) { + return fd <= 2 || fd == 4 || fd == 5 || std::find(extra.begin(), extra.end(), fd) != extra.end(); + }; + + std::vector toClose; + +# ifdef __linux__ + std::error_code ec; + std::filesystem::directory_iterator fds("/proc/self/fd", ec); + if (ec) + throw Error("cannot list /proc/self/fd: %s", ec.message()); + for (auto & entry : fds) { + int fd = std::stoi(entry.path().filename().string()); + if (keep(fd)) + continue; + auto target = std::filesystem::read_symlink(entry.path(), ec); + if (!ec && isSQLiteFile(target.string())) + continue; + toClose.push_back(fd); + } +# else + for (int fd = 3, maxFd = static_cast(sysconf(_SC_OPEN_MAX)); fd < maxFd; ++fd) { + if (keep(fd)) + continue; + if (auto path = fdPath(fd); path && isSQLiteFile(*path)) + continue; + toClose.push_back(fd); + } +# endif + + for (auto fd : toClose) + close(fd); +} +#endif + +HookInstance::HookInstance() +{ + /* Create a pipe to get the output of the child. */ + fromHook.create(); + + /* Create the communication pipes. */ + toHook.create(); + + /* Create a pipe to get the output of the builder. */ + builderOut.create(); +} + +void HookInstance::adopt(pid_t childPid, std::chrono::milliseconds timeout) +{ + pid = childPid; + + /* Give custom build hooks the chance to cleanup. */ + pid.setKillSignal(SIGTERM); + pid.setKillTimeout(timeout); + + pid.setSeparatePG(true); + fromHook.writeSide = -1; + toHook.readSide = -1; + + sink = FdSink(toHook.writeSide.get()); +} + +std::unique_ptr HookInstance::external(const Strings & _buildHook, std::chrono::milliseconds timeout) { debug("starting build hook '%s'", concatStringsSep(" ", _buildHook)); @@ -36,59 +184,97 @@ HookInstance::HookInstance(const Strings & _buildHook, std::chrono::milliseconds args.push_back(std::to_string(std::to_underlying(verbosity))); - /* Create a pipe to get the output of the child. */ - fromHook.create(); + auto hook = std::unique_ptr(new HookInstance()); - /* Create the communication pipes. */ - toHook.create(); + /* Fork the hook. */ + auto childPid = startProcess([&]() { + hook->redirectChildFds(); - /* Create a pipe to get the output of the builder. */ - builderOut.create(); + execv(requireCString(buildHook.native()), stringsToCharPtrs(args).data()); - /* Fork the hook. */ - pid = startProcess([&]() { - if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1) - throw SysError("cannot pipe standard error into log file"); + throw SysError("executing %s", PathFmt(buildHook)); + }); + + hook->adopt(childPid, timeout); - commonChildInit(); + /* The hook is a fresh process with its own configuration, so tell + it ours. */ + std::map settingsToSend; + globalConfig.getSettings(settingsToSend); + for (auto & setting : settingsToSend) + hook->sink << 1 << setting.first << setting.second.value; + hook->sink << 0; - if (chdir("/") == -1) - throw SysError("changing into /"); + return hook; +} - /* Dup the communication pipes. */ - if (dup2(toHook.readSide.get(), STDIN_FILENO) == -1) - throw SysError("dupping to-hook read side"); +std::unique_ptr HookInstance::builtin(const StoreConfig & storeConfig, std::chrono::milliseconds timeout) +{ +#ifndef CAN_FORK_HOOK + /* Looked up in `PATH`. */ + (void) storeConfig; + return external({"nix", "__build-remote"}, timeout); +#else + debug("starting the built-in build hook"); - /* Use fd 4 for the builder's stdout/stderr. */ - if (dup2(builderOut.writeSide.get(), 4) == -1) - throw SysError("dupping builder's stdout/stderr"); + auto hook = std::unique_ptr(new HookInstance()); - /* Hack: pass the read side of that fd to allow build-remote - to read SSH error messages. */ - if (dup2(builderOut.readSide.get(), 5) == -1) - throw SysError("dupping builder's stdout/stderr"); + auto childPid = startProcess([&]() { + /* Out of the way of the dup2()s onto 4 and 5 below. */ + std::vector savedNsFds; +# ifdef __linux__ + savedNsFds = moveSavedMountNamespaceFds(6); +# endif - execv(requireCString(buildHook.native()), stringsToCharPtrs(args).data()); + /* Our SQLite connection reuses the parent's WAL index descriptor; + the dup2()s below must not overwrite it. */ + for (int fd : {4, 5}) + if (fdIsStoreWALIndex(fd)) + throw Error("descriptor %d is the store's SQLite WAL index; cannot fork the build hook", fd); - throw SysError("executing %s", PathFmt(buildHook)); - }); + hook->redirectChildFds(); - using namespace std::chrono_literals; + /* The parent parses our output, so it has to be JSON. */ + logger = makeJSONLogger(getStandardError()).release(); - /* Give custom build hooks the chance to cleanup. */ - pid.setKillSignal(SIGTERM); - pid.setKillTimeout(timeout); + try { + /* Inherited from the parent; this child has not been interrupted. */ + setInterrupted(false); - pid.setSeparatePG(true); - fromHook.writeSide = -1; - toHook.readSide = -1; + closeExtraHookFDs(savedNsFds); - sink = FdSink(toHook.writeSide.get()); - std::map settings; - globalConfig.getSettings(settings); - for (auto & setting : settings) - sink << 1 << setting.first << setting.second.value; - sink << 0; + /* As `initNix` does: SIGTERM becomes an interrupt. */ + unix::startSignalHandlerThread(); + + /* Ensure we don't get any SSH passphrase or host key popups. */ + unsetenv("DISPLAY"); + unsetenv("SSH_ASKPASS"); + + unsigned int maxBuildJobs = settings.getWorkerSettings().maxBuildJobs.get(); + settings.getWorkerSettings().maxBuildJobs.set("1"); // hack to make tests with local?root= work + + /* A SQLite connection cannot cross a fork. Open the worker's + store, not the ambient one. */ + auto store = storeConfig.openStore(); + store->init(); + + FdSource source(STDIN_FILENO); + serveBuildHook(store, maxBuildJobs, source, STDERR_FILENO, 5); + } catch (BaseError & e) { + logError(e.info()); + _exit(1); + } catch (std::exception & e) { + printError("error: %s", e.what()); + _exit(1); + } + + _exit(0); + }); + + hook->adopt(childPid, timeout); + + return hook; +#endif } HookInstance::~HookInstance() diff --git a/src/libstore/unix/include/nix/store/build/build-remote.hh b/src/libstore/unix/include/nix/store/build/build-remote.hh new file mode 100644 index 000000000000..42595f1e1ad3 --- /dev/null +++ b/src/libstore/unix/include/nix/store/build/build-remote.hh @@ -0,0 +1,21 @@ +#pragma once +///@file + +#include "nix/store/store-api.hh" +#include "nix/util/file-descriptor.hh" +#include "nix/util/serialise.hh" + +namespace nix { + +/** + * The build hook: answer requests from `from` on `toParent` and run + * an accepted build on a machine from `builders`. Returns at EOF or + * after one accepted build. + * + * @param maxBuildJobs the parent's `max-jobs`. + * @param sshErrorFd ssh's stderr, read when a connection fails. + */ +void serveBuildHook( + ref store, unsigned int maxBuildJobs, Source & from, Descriptor toParent, Descriptor sshErrorFd); + +} // namespace nix diff --git a/src/libstore/unix/include/nix/store/build/hook-instance.hh b/src/libstore/unix/include/nix/store/build/hook-instance.hh index 10d61b4d0b56..f5033f6be91f 100644 --- a/src/libstore/unix/include/nix/store/build/hook-instance.hh +++ b/src/libstore/unix/include/nix/store/build/hook-instance.hh @@ -7,9 +7,12 @@ #include #include +#include namespace nix { +struct StoreConfig; + /** * @note Sometimes this is owned by the `Worker`, and sometimes it is * owned by a `Goal`. This is for efficiency: rather than starting the @@ -50,9 +53,36 @@ struct HookInstance */ std::function onKillChild; - HookInstance(const Strings & buildHook, std::chrono::milliseconds timeout); + /** + * Run the program named by the `build-hook` setting, in a child + * process of this one. + */ + static std::unique_ptr external(const Strings & buildHook, std::chrono::milliseconds timeout); + + /** + * Run Nix's own build hook in a fork of this process, without exec. + * The child opens its own store from `storeConfig`. + */ + static std::unique_ptr builtin(const StoreConfig & storeConfig, std::chrono::milliseconds timeout); ~HookInstance(); + +private: + + /** + * Creates the pipes. + */ + HookInstance(); + + /** + * Take ownership of the started child and close its pipe ends. + */ + void adopt(pid_t childPid, std::chrono::milliseconds timeout); + + /** + * Put the pipes where the hook expects them. Runs in the child. + */ + void redirectChildFds(); }; } // namespace nix diff --git a/src/libstore/unix/include/nix/store/meson.build b/src/libstore/unix/include/nix/store/meson.build index bdc4b2f20236..028637e5fa05 100644 --- a/src/libstore/unix/include/nix/store/meson.build +++ b/src/libstore/unix/include/nix/store/meson.build @@ -1,6 +1,7 @@ include_dirs += include_directories('../..') headers += files( + 'build/build-remote.hh', 'build/child.hh', 'build/hook-instance.hh', 'user-lock.hh', diff --git a/src/libstore/unix/meson.build b/src/libstore/unix/meson.build index fc115a1932e2..e0253d109203 100644 --- a/src/libstore/unix/meson.build +++ b/src/libstore/unix/meson.build @@ -1,4 +1,5 @@ sources += files( + 'build/build-remote.cc', 'build/child.cc', 'build/chroot.cc', 'build/external-derivation-builder.cc', diff --git a/src/libutil/include/nix/util/configuration.hh b/src/libutil/include/nix/util/configuration.hh index 1bf12bd897b9..aa40b3eb1a94 100644 --- a/src/libutil/include/nix/util/configuration.hh +++ b/src/libutil/include/nix/util/configuration.hh @@ -408,6 +408,15 @@ public: return value != v2; } + /** + * Whether `value` equals the constructor's default. Unlike + * `isOverridden()`, not reset by `Config::resetOverridden()`. + */ + bool isDefault() const + { + return value == defaultValue; + } + template void operator=(const U & v) { diff --git a/src/libutil/linux/include/nix/util/linux-namespaces.hh b/src/libutil/linux/include/nix/util/linux-namespaces.hh index 0b6bef7bcabb..7e6e8352339c 100644 --- a/src/libutil/linux/include/nix/util/linux-namespaces.hh +++ b/src/libutil/linux/include/nix/util/linux-namespaces.hh @@ -2,6 +2,7 @@ ///@file #include +#include namespace nix { @@ -25,6 +26,12 @@ void remountReadOnlyWritable(const std::filesystem::path & path); */ void restoreMountNamespace(); +/** + * Move the descriptors used to return to the parent mount namespace to + * `minFd` or above, and return them. Empty if we never left it. + */ +std::vector moveSavedMountNamespaceFds(int minFd); + /** * Cause this thread to try to not share any FS attributes with the main * thread, because this causes setns() in restoreMountNamespace() to diff --git a/src/libutil/linux/linux-namespaces.cc b/src/libutil/linux/linux-namespaces.cc index 34e6aed616c7..128d98614f2f 100644 --- a/src/libutil/linux/linux-namespaces.cc +++ b/src/libutil/linux/linux-namespaces.cc @@ -5,6 +5,7 @@ #include "linux-namespaces-private.hh" +#include #include #include @@ -95,6 +96,25 @@ AutoCloseFD fdSavedMountNamespace; AutoCloseFD fdSavedRoot; bool havePrivateMountNs = false; +std::vector moveSavedMountNamespaceFds(int minFd) +{ + std::vector fds; + if (!havePrivateMountNs) + return fds; + for (auto * fd : {&fdSavedMountNamespace, &fdSavedRoot}) { + if (!*fd) + continue; + if (fd->get() < minFd) { + int moved = fcntl(fd->get(), F_DUPFD_CLOEXEC, minFd); + if (moved == -1) + throw SysError("moving saved mount namespace descriptor"); + *fd = AutoCloseFD(moved); + } + fds.push_back(fd->get()); + } + return fds; +} + /* Save the current mount namespace so restoreMountNamespace() can return to it later. Ignored if called more than once. */ static void saveMountNamespace() diff --git a/src/nix/build-remote/build-remote.cc b/src/nix/build-remote/build-remote.cc index d48f7dcd67f5..6795ae485552 100644 --- a/src/nix/build-remote/build-remote.cc +++ b/src/nix/build-remote/build-remote.cc @@ -1,58 +1,16 @@ #include -#include -#include -#include -#include -#include -#include -#ifdef __APPLE__ -# include -#endif - -#include "nix/store/machines.hh" +#include "nix/store/build/build-remote.hh" #include "nix/main/shared.hh" #include "nix/main/plugin.hh" -#include "nix/store/pathlocks.hh" #include "nix/store/globals.hh" -#include "nix/util/serialise.hh" -#include "nix/store/build-result.hh" #include "nix/store/store-open.hh" -#include "nix/util/strings.hh" +#include "nix/util/serialise.hh" #include "nix/util/util.hh" -#include "nix/store/derivations.hh" -#include "nix/store/derivation/full-inputs.hh" -#include "nix/store/local-store.hh" -#include "nix/store/build.hh" #include "nix/cmd/legacy.hh" -#include "nix/util/experimental-features.hh" -#include "nix/store/globals.hh" namespace nix { -static void handleAlarm(int sig) {} - -std::string escapeUri(std::string uri) -{ - std::replace(uri.begin(), uri.end(), '/', '_'); - return uri; -} - -static std::filesystem::path currentLoad; - -static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot) -{ - return openLockFile(currentLoad / fmt("%s-%d", escapeUri(m.storeUri.render()), slot), true); -} - -static bool allSupportedLocally(Store & store, const StringSet & requiredFeatures) -{ - for (auto & feature : requiredFeatures) - if (!store.config.systemFeatures.get().count(feature)) - return false; - return true; -} - static int main_build_remote(int argc, char ** argv) { { @@ -99,345 +57,14 @@ static int main_build_remote(int argc, char ** argv) settings.set(name, value); } - auto maxBuildJobs = settings.getWorkerSettings().maxBuildJobs; + unsigned int maxBuildJobs = settings.getWorkerSettings().maxBuildJobs.get(); settings.getWorkerSettings().maxBuildJobs.set("1"); // hack to make tests with local?root= work initPlugins(); auto store = openStore(); - /* It would be more appropriate to use $XDG_RUNTIME_DIR, since - that gets cleared on reboot, but it wouldn't work on macOS. */ - if (auto localStore = store.dynamic_pointer_cast()) - currentLoad = localStore->config.stateDir.get() / "current-load"; - else - currentLoad = std::filesystem::path{settings.nixStateDir} / "current-load"; - - std::shared_ptr sshStore; - AutoCloseFD bestSlotLock; - - auto machines = Machine::parseConfig({settings.thisSystem}, settings.getWorkerSettings().builders); - debug("got %d remote builders", machines.size()); - - if (machines.empty()) { - std::cerr << "# decline-permanently\n"; - return 0; - } - - std::optional drvPath; - std::string storeUri; - - while (true) { - - try { - auto s = readString(source); - if (s != "try") - return 0; - } catch (EndOfFile &) { - return 0; - } - - auto amWilling = readInt(source); - auto neededSystem = readString(source); - drvPath = store->parseStorePath(readString(source)); - auto requiredFeatures = readStrings(source); - - /* It would be possible to build locally after some builds clear out, - so don't show the warning now: */ - bool couldBuildLocally = - maxBuildJobs > 0 - && (neededSystem == settings.thisSystem || settings.extraPlatforms.get().count(neededSystem) > 0) - && allSupportedLocally(*store, requiredFeatures); - /* It's possible to build this locally right now: */ - bool canBuildLocally = amWilling && couldBuildLocally; - - /* Error ignored here, will be caught later */ - mkdir(currentLoad.c_str(), 0777); - - while (true) { - bestSlotLock = -1; - AutoCloseFD lock = openLockFile(currentLoad / "main-lock", true); - lockFile(lock.get(), ltWrite, true); - - bool rightType = false; - - Machine * bestMachine = nullptr; - uint64_t bestLoad = 0; - for (auto & m : machines) { - debug("considering building on remote machine '%s'", m.storeUri.render()); - - if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures) - && m.mandatoryMet(requiredFeatures)) { - rightType = true; - AutoCloseFD free; - uint64_t load = 0; - for (uint64_t slot = 0; slot < m.maxJobs; ++slot) { - auto slotLock = openSlotLock(m, slot); - if (lockFile(slotLock.get(), ltWrite, false)) { - if (!free) { - free = std::move(slotLock); - } - } else { - ++load; - } - } - if (!free) { - continue; - } - bool best = false; - if (!bestSlotLock) { - best = true; - } else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) { - best = true; - } else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) { - if (m.speedFactor > bestMachine->speedFactor) { - best = true; - } else if (m.speedFactor == bestMachine->speedFactor) { - if (load < bestLoad) { - best = true; - } - } - } - if (best) { - bestLoad = load; - bestSlotLock = std::move(free); - bestMachine = &m; - } - } - } - - if (!bestSlotLock) { - if (rightType && !canBuildLocally) - std::cerr << "# postpone\n"; - else { - // build the hint template. - std::string errorText = - "Failed to find a machine for remote build!\n" - "derivation: %s\nrequired (system, features): (%s, [%s])"; - errorText += "\n%s available machines:"; - errorText += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)"; - - for (unsigned int i = 0; i < machines.size(); ++i) - errorText += "\n([%s], %s, [%s], [%s])"; - - // add the template values. - std::string drvstr; - if (drvPath.has_value()) - drvstr = drvPath->to_string(); - else - drvstr = ""; - - auto error = HintFmt::fromFormatString(errorText); - error % drvstr % neededSystem % concatStringsSep(", ", requiredFeatures) - % machines.size(); - - for (auto & m : machines) - error % concatStringsSep(", ", m.systemTypes) % m.maxJobs - % concatStringsSep(", ", m.supportedFeatures) - % concatStringsSep(", ", m.mandatoryFeatures); - - printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str()); - - std::cerr << "# decline\n"; - } - break; - } - -#ifdef __APPLE__ - futimes(bestSlotLock.get(), NULL); -#else - futimens(bestSlotLock.get(), NULL); -#endif - - lock = -1; - - try { - storeUri = bestMachine->storeUri.render(); - - Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", storeUri)); - - sshStore = bestMachine->openStore(); - sshStore->connect(); - } catch (std::exception & e) { - auto msg = chomp(drainFD(5, {.block = false})); - printError("cannot build on '%s': %s%s", storeUri, e.what(), msg.empty() ? "" : ": " + msg); - bestMachine->enabled = false; - continue; - } - - goto connected; - } - } - - connected: - close(5); - - assert(sshStore); - - std::cerr << "# accept\n" << storeUri << "\n"; - - /* The other side of the build hook protocol is Nix itself, so - these are always printed in canonical form. */ - auto inputs = [&] { - StorePathSet res; - for (auto & i : readStrings(source)) - res.insert(store->parseStorePathCanonical(i)); - return res; - }(); - - auto wantedOutputs = readStrings(source); - - AutoCloseFD uploadLock; - { - auto setUpdateLock = [&](auto && fileName) { - uploadLock = openLockFile(currentLoad / (escapeUri(fileName) + ".upload-lock"), true); - }; - try { - setUpdateLock(storeUri); - } catch (SystemError & e) { - if (!e.is(std::errc::filename_too_long)) - throw; - // Try again hashing the store URL so we have a shorter path - auto h = hashString(HashAlgorithm::MD5, storeUri); - setUpdateLock(h.to_string(HashFormat::Base64, false)); - } - } - - { - Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri)); - - auto old = signal(SIGALRM, handleAlarm); - alarm(15 * 60); - if (!lockFile(uploadLock.get(), ltWrite, true)) - printError("somebody is hogging the upload lock for '%s', continuing..."); - alarm(0); - signal(SIGALRM, old); - } - - auto substitute = settings.getWorkerSettings().buildersUseSubstitutes ? Substitute : NoSubstitute; - - { - Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri)); - copyPaths(*store, *sshStore, inputs, NoRepair, NoCheckSigs, substitute); - } - - uploadLock = -1; - - auto drv = store->readDerivation(*drvPath); - - std::optional optResult; - - // If we don't know whether we are trusted (e.g. `ssh://` - // stores), we assume we are. This is necessary for backwards - // compat. - bool trustedOrLegacy = ({ - std::optional trusted = sshStore->isTrustedClient(); - !trusted || *trusted; - }); - - // See the very large comment in `case WorkerProto::Op::BuildDerivation:` in - // `src/libstore/daemon.cc` that explains the trust model here. - // - // This condition mirrors that: that code enforces the "rules" outlined there; - // we do the best we can given those "rules". - if (trustedOrLegacy || type(drv).isCA()) { - // Check if there are any derivation inputs - bool hasInputDrvs = std::ranges::any_of(drv.inputs, [](const auto & input) { - return std::holds_alternative(input.raw()); - }); - - BasicDerivation resolvedDrv{ - .outputs = drv.outputs, - // Hijack the inputs paths of the derivation to include - // all the paths that come from the `inputDrvs` set. We - // don't do that for the derivations whose `inputDrvs` - // is empty because: - // - // 1. It's not needed - // - // 2. Changing the `inputSrcs` set changes the - // associated output ids, which break CA derivations - .inputs = - hasInputDrvs ? inputs : [&] { - StorePathSet srcs; - for (auto & input : drv.inputs) - if (auto * op = std::get_if(&input.raw())) - srcs.insert(op->path); - return srcs; - }(), - .platform = drv.platform, - .builder = drv.builder, - .args = drv.args, - .env = drv.env, - .structuredAttrs = drv.structuredAttrs, - .name = drv.name, - }; - optResult = sshStore->getBuilder()->buildDerivation(*drvPath, resolvedDrv); - auto & result = *optResult; - if (auto * failureP = result.tryGetFailure()) { - if (settings.keepFailed) { - warn( - "The failed build directory was kept on the remote builder due to `--keep-failed`.%s", - (settings.thisSystem == drv.platform || settings.extraPlatforms.get().count(drv.platform) > 0) - ? " You can re-run the command with `--builders ''` to disable remote building for this invocation." - : ""); - } - throw Error( - "build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, failureP->message()); - } - } else { - copyClosure(*store, *sshStore, StorePathSet{*drvPath}, NoRepair, NoCheckSigs, substitute); - auto res = sshStore->getBuilder()->buildPathsWithResults({DerivedPath::Built{ - .drvPath = makeConstantStorePathRef(*drvPath), - .outputs = OutputsSpec::All{}, - }}); - // One path to build should produce exactly one build result - assert(res.size() == 1); - optResult = std::move(res[0]); - } - - std::set missingRealisations; - StorePathSet missingPaths; - if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !type(drv).hasKnownOutputPaths()) { - for (auto & outputName : wantedOutputs) { - auto thisOutputId = DrvOutput{*drvPath, outputName}; - if (!store->queryRealisation(thisOutputId)) { - debug("missing output %s", outputName); - assert(optResult); - auto & result = *optResult; - if (auto * successP = result.tryGetSuccess()) { - auto & success = *successP; - auto i = success.builtOutputs.find(outputName); - assert(i != success.builtOutputs.end()); - auto & newRealisation = i->second; - missingRealisations.insert({newRealisation, thisOutputId}); - missingPaths.insert(newRealisation.outPath); - } - } - } - } else { - auto outputPaths = outputsAndOptPaths(drv, *store); - for (auto & [outputName, hopefullyOutputPath] : outputPaths) { - assert(hopefullyOutputPath.second); - if (!store->isValidPath(*hopefullyOutputPath.second)) - missingPaths.insert(*hopefullyOutputPath.second); - } - } - - if (!missingPaths.empty()) { - Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri)); - if (auto localStore = store.dynamic_pointer_cast()) - for (auto & path : missingPaths) - localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */ - copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute); - } - // XXX: Should be done as part of `copyPaths` - for (auto & realisation : missingRealisations) { - // Should hold, because if the feature isn't enabled the set - // of missing realisations should be empty - experimentalFeatureSettings.require(Xp::CaDerivations); - store->registerDrvOutput(realisation, NoCheckSigs); - } + serveBuildHook(store, maxBuildJobs, source, STDERR_FILENO, 5); return 0; } diff --git a/src/nix/main.cc b/src/nix/main.cc index 4b0bf4cc2ee8..2f5c684adfdc 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -24,7 +24,6 @@ #include "nix/flake/flake.hh" #include "nix/flake/settings.hh" -#include "self-exe.hh" #include "crash-handler.hh" #include "cli-config-private.hh" @@ -387,18 +386,6 @@ void mainWrapped(int argc, char ** argv) /* This must be called before Sentry since both initialize OpenSSL. */ initLibUtil(); - /* Set the build hook location - - For builds we perform a self-invocation, so Nix has to be - self-aware. That is, it has to know where it is installed. We - don't think it's sentient. - */ - settings.getWorkerSettings().buildHook.setDefault( - Strings{ - getNixBin({}).string(), - "__build-remote", - }); - initNix(); initGC(); flakeSettings.configureEvalSettings(evalSettings);