diff --git a/src/nix/system-activate.md b/src/nix/system-activate.md new file mode 100644 index 000000000..cdf842854 --- /dev/null +++ b/src/nix/system-activate.md @@ -0,0 +1,8 @@ +R""( + +# Description + +This command activates NixOS configurations. + +)"" + diff --git a/src/nix/system-build.md b/src/nix/system-build.md new file mode 100644 index 000000000..2040dcf20 --- /dev/null +++ b/src/nix/system-build.md @@ -0,0 +1,7 @@ +R""( + +# Description + +This command builds NixOS configurations. The default attribute name is the current system hostname. + +)"" diff --git a/src/nix/system.cc b/src/nix/system.cc new file mode 100644 index 000000000..e381a3a11 --- /dev/null +++ b/src/nix/system.cc @@ -0,0 +1,254 @@ +#include "args.hh" +#include "command.hh" +#include "shared.hh" +#include "common-args.hh" +#include "error.hh" +#include "installables.hh" +#include "logging.hh" +#include "shared.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "flake/flake.hh" +#include "get-drvs.hh" +#include "store-api.hh" +#include "derivations.hh" +#include "path-with-outputs.hh" +#include "attr-path.hh" +#include "fetchers.hh" +#include "registry.hh" +#include "eval-cache.hh" +#include "markdown.hh" +#include "local-fs-store.hh" +#include "progress-bar.hh" +#include "util.hh" + +#include +#include +#include +#include +#include +#include + +using namespace nix; + +void executePrivileged(std::string program, Strings args) { + args.push_front(program); + auto exe = program; + if(getuid() != 0) { + args.push_front("doas"); + exe = "doas"; + } + execvp(exe.c_str(), stringsToCharPtrs(args).data()); + + throw SysError("unable to execute '%s'", exe); +} + +struct SystemCommand : InstallableCommand +{ + Strings getDefaultFlakeAttrPaths() override + { + char hostname[1024]; + hostname[1023] = '\0'; + gethostname(hostname, 1024); + Strings res{ + "nixosConfigurations." + std::string(hostname), + }; + return res; + } + + Strings getDefaultFlakeAttrPathPrefixes() override + { + Strings res{"nixosConfigurations."}; + return res; + } + + std::shared_ptr transformInstallable(ref state, std::shared_ptr installable) { + auto installableFlake = std::dynamic_pointer_cast(installable); + if (installableFlake) { + auto fragment = *installableFlake->attrPaths.begin(); + return std::make_shared( + this, + state, + std::move(installableFlake->flakeRef), + fragment + ".config.system.build.toplevel", + installableFlake->outputsSpec, + getDefaultFlakeAttrPaths(), + getDefaultFlakeAttrPathPrefixes(), + installableFlake->lockFlags + ); + } else { + return installable; + } + } +}; + +struct CmdSystemBuild : SystemCommand, MixDryRun +{ + Path outLink = "result"; + bool printOutputPaths = false; + + CmdSystemBuild() + { + addFlag({ + .longName = "out-link", + .shortName = 'o', + .description = "Use *path* as prefix for the symlinks to the build results. It defaults to `result`.", + .labels = {"path"}, + .handler = {&outLink}, + .completer = completePath + }); + + addFlag({ + .longName = "no-link", + .description = "Do not create symlinks to the build results.", + .handler = {&outLink, Path("")}, + }); + + addFlag({ + .longName = "print-out-paths", + .description = "Print the resulting output paths", + .handler = {&printOutputPaths, true}, + }); + } + + std::string description() override + { + return "build a NixOS system configuration"; + } + + std::string doc() override + { + return + #include "system-build.md" + ; + } + + void run(nix::ref store) override + { + std::vector> installableContext; + auto state = getEvalState(); + installableContext.emplace_back(transformInstallable(state, installable)); + if (dryRun) { + std::vector pathsToBuild; + + for (auto & i : installableContext) { + auto b = i->toDerivedPaths(); + pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end()); + } + printMissing(store, pathsToBuild, lvlError); + return; + } + + auto buildables = Installable::build( + getEvalStore(), store, + Realise::Outputs, + installableContext, bmNormal); + + if (outLink != "") + if (auto store2 = store.dynamic_pointer_cast()) + for (const auto & [_i, buildable] : enumerate(buildables)) { + auto i = _i; + std::visit(overloaded { + [&](const BuiltPath::Opaque & bo) { + std::string symlink = outLink; + if (i) symlink += fmt("-%d", i); + store2->addPermRoot(bo.path, absPath(symlink)); + }, + [&](const BuiltPath::Built & bfd) { + for (auto & output : bfd.outputs) { + std::string symlink = outLink; + if (i) symlink += fmt("-%d", i); + if (output.first != "out") symlink += fmt("-%s", output.first); + store2->addPermRoot(output.second, absPath(symlink)); + } + }, + }, buildable.path.raw()); + } + + if (printOutputPaths) { + stopProgressBar(); + for (auto & buildable : buildables) { + std::visit(overloaded { + [&](const BuiltPath::Opaque & bo) { + std::cout << store->printStorePath(bo.path) << std::endl; + }, + [&](const BuiltPath::Built & bfd) { + for (auto & output : bfd.outputs) { + std::cout << store->printStorePath(output.second) << std::endl; + } + }, + }, buildable.path.raw()); + } + } + } +}; + +struct CmdSystemActivate : SystemCommand, MixDryRun +{ + CmdSystemActivate() + { + } + + std::string description() override + { + return "activate a NixOS system configuration"; + } + + std::string doc() override + { + return + #include "system-activate.md" + ; + } + void run(nix::ref store) override + { + auto state = getEvalState(); + auto inst = transformInstallable(state, installable); + auto cursor = inst->getCursor(*state); + + auto drvPath = cursor->forceDerivation(); + auto outPath = cursor->getAttr(state->sOutPath)->getString(); + auto outputName = cursor->getAttr(state->sOutputName)->getString(); + + auto app = UnresolvedApp{App { + .context = { { drvPath, {outputName} } }, + .program = outPath + "/bin/switch-to-configuration", + }}.resolve(getEvalStore(), store); + + executePrivileged(app.program, Strings{dryRun ? "dry-activate" : "test"}); + } +}; + +struct CmdSystem : NixMultiCommand +{ + CmdSystem() + : MultiCommand({ + {"build", []() { return make_ref(); }}, + {"activate", []() { return make_ref(); }}, + }) + { + } + + std::string description() override + { + return "manage NixOS systems"; + } + + std::string doc() override + { + return + #include "system.md" + ; + } + + void run() override + { + if (!command) + throw UsageError("'nix system' requires a sub-command."); + settings.requireExperimentalFeature(Xp::NixCommand); + command->second->prepare(); + command->second->run(); + } +}; + +static auto rCmdSystem = registerCommand("system"); diff --git a/src/nix/system.md b/src/nix/system.md new file mode 100644 index 000000000..de2b64647 --- /dev/null +++ b/src/nix/system.md @@ -0,0 +1,7 @@ +R""( + +# Description + +This is a group of commands for managing NixOS system configurations. + +)""