diff --git a/src/libcmd/activatables.cc b/src/libcmd/activatables.cc new file mode 100644 index 000000000..490dba75d --- /dev/null +++ b/src/libcmd/activatables.cc @@ -0,0 +1,46 @@ +#include "command.hh" +#include "progress-bar.hh" + +using namespace nix; + +ActivatableCommand::ActivatableCommand(std::string activationPackageAttrPath) + : activationPackageAttrPath(activationPackageAttrPath) +{ } + +void ActivatableCommand::prepare() { + InstallableCommand::prepare(); + auto installableFlake = std::dynamic_pointer_cast(installable); + if (installableFlake) { + auto fragment = *installableFlake->attrPaths.begin(); + installable = std::make_shared( + this, + getEvalState(), + std::move(installableFlake->flakeRef), + activationPackageAttrPath == "" ? fragment : fragment + "." + activationPackageAttrPath, + installableFlake->outputsSpec, + getDefaultFlakeAttrPaths(), + getDefaultFlakeAttrPathPrefixes(), + installableFlake->lockFlags + ); + } +} + +StorePath ActivatableCommand::buildActivatable(nix::ref store, bool dryRun) { + auto installableFlake = std::dynamic_pointer_cast(installable); + std::vector> installableContext; + installableContext.emplace_back(installable); + auto buildables = Installable::build( + getEvalStore(), store, + Realise::Outputs, + installableContext, bmNormal); + + BuiltPaths buildables2; + for (auto & b : buildables) + buildables2.push_back(b.path); + + auto buildable = buildables2.front(); + if (buildables2.size() != 1 || buildable.outPaths().size() != 1) { + throw UsageError("this command requires that the argument produces a single store path"); + } + return *buildable.outPaths().begin(); +} diff --git a/src/libcmd/activatables.hh b/src/libcmd/activatables.hh new file mode 100644 index 000000000..7b564a55b --- /dev/null +++ b/src/libcmd/activatables.hh @@ -0,0 +1,70 @@ +#include "command.hh" +#include "local-fs-store.hh" +#include "progress-bar.hh" +#include "shared.hh" + +using namespace nix; + + +template +ActivatableBuildCommand::ActivatableBuildCommand() +{ + 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}, + }); +}; + +template +void ActivatableBuildCommand::run(nix::ref store) +{ + auto installableFlake = std::dynamic_pointer_cast(ActivatableCommand::installable); + if (dryRun) { + std::vector> installableContext; + installableContext.emplace_back(ActivatableCommand::installable); + 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 out = ActivatableCommand::buildActivatable(store); + + if (outLink != "") { + if (auto store2 = store.dynamic_pointer_cast()) { + std::string symlink = outLink; + store2->addPermRoot(out, absPath(symlink)); + } + } + + if (printOutputPaths) { + stopProgressBar(); + std::cout << store->printStorePath(out) << std::endl; + } +} + +template +ActivationCommand::ActivationCommand(std::string activationType, std::string selfCommandName) + : activationType(activationType), + selfCommandName(selfCommandName) +{ } diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 60c575d56..1ee855af0 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -1,5 +1,6 @@ #pragma once +#include "common-args.hh" #include "installables.hh" #include "args.hh" #include "common-eval-args.hh" @@ -272,6 +273,34 @@ struct MixEnvironment : virtual Args { void setEnviron(); }; +/* A command that operates on exactly one "activatable" */ +struct ActivatableCommand : public InstallableCommand +{ + ActivatableCommand(std::string activationPackageAttrPath); + std::string activationPackageAttrPath; + void prepare() override; + StorePath buildActivatable(nix::ref store, bool dryRun = false); +}; + +template +struct ActivatableBuildCommand : public ActivatableCommand, public MixDryRun +{ + ActivatableBuildCommand(); + Path outLink = "result"; + bool printOutputPaths = false; + void run(nix::ref) override; +}; + +/* A command that manages the activation of exactly one "activatable" */ +template +struct ActivationCommand : public ActivatableCommand +{ +public: + ActivationCommand(std::string activationType, std::string selfCommandName); + const std::string activationType; + const std::string selfCommandName; +}; + void completeFlakeRef(ref store, std::string_view prefix); void completeFlakeRefWithFragment( diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 226025ad8..c3a2dd7ba 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -354,6 +354,8 @@ void printVersion(const std::string & programName) cfg.push_back("gc"); #endif cfg.push_back("signed-caches"); + cfg.push_back("nixos"); + cfg.push_back("home"); std::cout << "System type: " << settings.thisSystem << "\n"; std::cout << "Additional system types: " << concatStringsSep(", ", settings.extraPlatforms.get()) << "\n"; std::cout << "Features: " << concatStringsSep(", ", cfg) << "\n"; diff --git a/src/nix/home-apply.md b/src/nix/home-apply.md new file mode 100644 index 000000000..d3a39b803 --- /dev/null +++ b/src/nix/home-apply.md @@ -0,0 +1,9 @@ +R""( + +# Description + +This command activates a home-manager configuration. + +)"" + + diff --git a/src/nix/home-build.md b/src/nix/home-build.md new file mode 100644 index 000000000..2040dcf20 --- /dev/null +++ b/src/nix/home-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/home.cc b/src/nix/home.cc new file mode 100644 index 000000000..ab97d255e --- /dev/null +++ b/src/nix/home.cc @@ -0,0 +1,123 @@ +#include "command.hh" +#include "activatables.hh" +#include "progress-bar.hh" + +using namespace nix; + +void execute(std::string program, Strings args) { + stopProgressBar(); + restoreProcessContext(); + args.push_front(program); + execvp(program.c_str(), stringsToCharPtrs(args).data()); + + throw SysError("unable to execute '%s'", program); +} + +struct HomeCommand : ActivatableCommand +{ + HomeCommand() + : ActivatableCommand("activationPackage") + {} + + Strings getDefaultFlakeAttrPaths() override + { + auto username = getUserName(); + char hostname[1024]; + hostname[1023] = '\0'; + gethostname(hostname, 1024); + + Strings res{ + "homeConfigurations." + username, + "homeConfigurations." + username + "@" + std::string(hostname), + }; + return res; + } + + Strings getDefaultFlakeAttrPathPrefixes() override + { + Strings res{"homeConfigurations."}; + return res; + } + +}; + +struct HomeActivationCommand : ActivationCommand +{ + HomeActivationCommand(std::string activationType, std::string selfCommandName) + : ActivationCommand(activationType, selfCommandName) + { } + + void run(nix::ref store) override + { + auto out = buildActivatable(store); + execute(store->printStorePath(out) + "/activate", Strings{}); + } +}; + +struct CmdHomeBuild : ActivatableBuildCommand +{ + std::string description() override + { + return "build a home-manager configuration"; + } + + std::string doc() override + { + return + #include "home-build.md" + ; + } +}; + +struct CmdHomeApply : HomeActivationCommand +{ + CmdHomeApply() : HomeActivationCommand("switch", "apply") + { + } + + std::string description() override + { + return "activate a home-manager configuration"; + } + + std::string doc() override + { + return + #include "home-apply.md" + ; + } +}; + +struct CmdHome : NixMultiCommand +{ + CmdHome() + : MultiCommand({ + {"build", []() { return make_ref(); }}, + {"apply", []() { return make_ref(); }}, + }) + { + } + + std::string description() override + { + return "manage home-manager configurations"; + } + + std::string doc() override + { + return + #include "home.md" + ; + } + + void run() override + { + if (!command) + throw UsageError("'nix home' requires a sub-command."); + settings.requireExperimentalFeature(Xp::NixCommand); + command->second->prepare(); + command->second->run(); + } +}; + +static auto rCmdHome = registerCommand("home"); diff --git a/src/nix/home.md b/src/nix/home.md new file mode 100644 index 000000000..e2042aeb5 --- /dev/null +++ b/src/nix/home.md @@ -0,0 +1,7 @@ +R""( + +# Description + +This is a group of commands for managing home-manager configurations. + +)"" 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-apply.md b/src/nix/system-apply.md new file mode 100644 index 000000000..df6c82167 --- /dev/null +++ b/src/nix/system-apply.md @@ -0,0 +1,15 @@ +R""( + +# Description + +This command activates NixOS configurations and sets it as the default boot configuration. + +The `profile` parameter is used to specify the system profile to use. + +The default system profile is `/nix/var/nix/profiles/system`. + +A system profile named `hello` would be located at `/nix/var/nix/profiles/system-profiles/hello`. + +)"" + + diff --git a/src/nix/system-boot.md b/src/nix/system-boot.md new file mode 100644 index 000000000..3214baa9f --- /dev/null +++ b/src/nix/system-boot.md @@ -0,0 +1,16 @@ +R""( + +# Description + +This command sets the given NixOS configuration as the default boot configuration. It will be activated on the next boot. + +The `profile` parameter is used to specify the system profile to use. + +The default system profile is `/nix/var/nix/profiles/system`. + +A system profile named `hello` would be located at `/nix/var/nix/profiles/system-profiles/hello`. + +)"" + + + 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..421cc9071 --- /dev/null +++ b/src/nix/system.cc @@ -0,0 +1,203 @@ +#include "command.hh" +#include "activatables.hh" +#include "progress-bar.hh" + +using namespace nix; + +void executePrivileged(std::string program, Strings args) { + stopProgressBar(); + restoreProcessContext(); + args.push_front(program); + auto exe = program; + auto privCmds = Strings { + "doas", + "sudo" + }; + bool isRoot = getuid() == 0; + for (auto privCmd : privCmds) { + if(!isRoot) { + args.push_front(privCmd); + exe = privCmd; + } + execvp(exe.c_str(), stringsToCharPtrs(args).data()); + if(!isRoot) + args.pop_front(); + } + + throw SysError("unable to execute privilege elevation helper (tried %s)", concatStringsSep(", ", privCmds)); +} + +struct SystemCommand : ActivatableCommand +{ + SystemCommand() + : ActivatableCommand("config.system.build.toplevel") + {} + + 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; + } + +}; + +struct SystemActivationCommand : ActivationCommand, MixProfile +{ + SystemActivationCommand(std::string activationType, std::string selfCommandName) + : ActivationCommand(activationType, selfCommandName) + { } + + void run(nix::ref store) override + { + auto out = buildActivatable(store); + + if (getuid() != 0) { + Strings args { + "system", selfCommandName, + store->printStorePath(out) + }; + if (profile) { + args.push_back("--profile"); + args.push_back(profile.value()); + } + executePrivileged(getSelfExe().value_or("nix"), args); + } else { + if (!profile) { + profile = settings.nixStateDir + "/profiles/system"; + } else { + auto systemProfileBase = settings.nixStateDir + "/profiles/system-profiles"; + if (!pathExists(systemProfileBase)) { + createDirs(systemProfileBase); + } + profile = systemProfileBase + "/" + profile.value(); + } + updateProfile(out); + executePrivileged(profile.value() + "/bin/switch-to-configuration", Strings{activationType}); + } + } +}; + +struct CmdSystemBuild : ActivatableBuildCommand +{ + std::string description() override + { + return "build a NixOS system configuration"; + } + + std::string doc() override + { + return + #include "system-build.md" + ; + } +}; + +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 out = buildActivatable(store); + + executePrivileged(store->printStorePath(out) + "/bin/switch-to-configuration", Strings{dryRun ? "dry-activate" : "test"}); + } +}; + +struct CmdSystemApply : SystemActivationCommand +{ + CmdSystemApply() : SystemActivationCommand("switch", "apply") + { + } + + std::string description() override + { + return "activate a NixOS system configuration and make it the default boot configuration"; + } + + std::string doc() override + { + return + #include "system-apply.md" + ; + } +}; + +struct CmdSystemBoot : SystemActivationCommand +{ + CmdSystemBoot() : SystemActivationCommand("boot","boot") + { + } + + std::string description() override + { + return "make a NixOS configuration the default boot configuration"; + } + + std::string doc() override + { + return + #include "system-boot.md" + ; + } +}; + +struct CmdSystem : NixMultiCommand +{ + CmdSystem() + : MultiCommand({ + {"build", []() { return make_ref(); }}, + {"activate", []() { return make_ref(); }}, + {"apply", []() { return make_ref(); }}, + {"boot", []() { 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. + +)""