aboutsummaryrefslogtreecommitdiff
path: root/src/legacy/nix-channel.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/legacy/nix-channel.cc')
-rw-r--r--src/legacy/nix-channel.cc272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/legacy/nix-channel.cc b/src/legacy/nix-channel.cc
new file mode 100644
index 000000000..2f79919dd
--- /dev/null
+++ b/src/legacy/nix-channel.cc
@@ -0,0 +1,272 @@
+#include "profiles.hh"
+#include "shared.hh"
+#include "globals.hh"
+#include "filetransfer.hh"
+#include "store-api.hh"
+#include "legacy.hh"
+#include "fetchers.hh"
+#include "eval-settings.hh" // for defexpr
+#include "users.hh"
+#include "nix-channel.hh"
+
+#include <fcntl.h>
+#include <regex>
+#include <pwd.h>
+
+namespace nix {
+
+typedef std::map<std::string, std::string> Channels;
+
+static Channels channels;
+static Path channelsList;
+
+// Reads the list of channels.
+static void readChannels()
+{
+ if (!pathExists(channelsList)) return;
+ auto channelsFile = readFile(channelsList);
+
+ for (const auto & line : tokenizeString<std::vector<std::string>>(channelsFile, "\n")) {
+ chomp(line);
+ if (std::regex_search(line, std::regex("^\\s*\\#")))
+ continue;
+ auto split = tokenizeString<std::vector<std::string>>(line, " ");
+ auto url = std::regex_replace(split[0], std::regex("/*$"), "");
+ auto name = split.size() > 1 ? split[1] : std::string(baseNameOf(url));
+ channels[name] = url;
+ }
+}
+
+// Writes the list of channels.
+static void writeChannels()
+{
+ auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)};
+ if (!channelsFD)
+ throw SysError("opening '%1%' for writing", channelsList);
+ for (const auto & channel : channels)
+ writeFull(channelsFD.get(), channel.second + " " + channel.first + "\n");
+}
+
+// Adds a channel.
+static void addChannel(const std::string & url, const std::string & name)
+{
+ if (!regex_search(url, std::regex("^(file|http|https)://")))
+ throw Error("invalid channel URL '%1%'", url);
+ if (!regex_search(name, std::regex("^[a-zA-Z0-9_][a-zA-Z0-9_\\.-]*$")))
+ throw Error("invalid channel identifier '%1%'", name);
+ readChannels();
+ channels[name] = url;
+ writeChannels();
+}
+
+static Path profile;
+
+// Remove a channel.
+static void removeChannel(const std::string & name)
+{
+ readChannels();
+ channels.erase(name);
+ writeChannels();
+
+ runProgram(settings.nixBinDir + "/nix-env", true, { "--profile", profile, "--uninstall", name });
+}
+
+static Path nixDefExpr;
+
+// Fetch Nix expressions and binary cache URLs from the subscribed channels.
+static void update(const StringSet & channelNames)
+{
+ readChannels();
+
+ auto store = openStore();
+
+ auto [fd, unpackChannelPath] = createTempFile();
+ writeFull(fd.get(),
+ #include "unpack-channel.nix.gen.hh"
+ );
+ fd.reset();
+ AutoDelete del(unpackChannelPath, false);
+
+ // Download each channel.
+ Strings exprs;
+ for (const auto & channel : channels) {
+ auto name = channel.first;
+ auto url = channel.second;
+
+ // If the URL contains a version number, append it to the name
+ // attribute (so that "nix-env -q" on the channels profile
+ // shows something useful).
+ auto cname = name;
+ std::smatch match;
+ auto urlBase = std::string(baseNameOf(url));
+ if (std::regex_search(urlBase, match, std::regex("(-\\d.*)$")))
+ cname = cname + match.str(1);
+
+ std::string extraAttrs;
+
+ if (!(channelNames.empty() || channelNames.count(name))) {
+ // no need to update this channel, reuse the existing store path
+ Path symlink = profile + "/" + name;
+ Path storepath = dirOf(readLink(symlink));
+ exprs.push_back("f: rec { name = \"" + cname + "\"; type = \"derivation\"; outputs = [\"out\"]; system = \"builtin\"; outPath = builtins.storePath \"" + storepath + "\"; out = { inherit outPath; };}");
+ } else {
+ // We want to download the url to a file to see if it's a tarball while also checking if we
+ // got redirected in the process, so that we can grab the various parts of a nix channel
+ // definition from a consistent location if the redirect changes mid-download.
+ auto result = fetchers::downloadFile(store, url, std::string(baseNameOf(url)), false);
+ auto filename = store->toRealPath(result.storePath);
+ url = result.effectiveUrl;
+
+ bool unpacked = false;
+ if (std::regex_search(filename, std::regex("\\.tar\\.(gz|bz2|xz)$"))) {
+ runProgram(settings.nixBinDir + "/nix-build", false, { "--no-out-link", "--expr", "import " + unpackChannelPath +
+ "{ name = \"" + cname + "\"; channelName = \"" + name + "\"; src = builtins.storePath \"" + filename + "\"; }" });
+ unpacked = true;
+ }
+
+ if (!unpacked) {
+ // Download the channel tarball.
+ try {
+ filename = store->toRealPath(fetchers::downloadFile(store, url + "/nixexprs.tar.xz", "nixexprs.tar.xz", false).storePath);
+ } catch (FileTransferError & e) {
+ filename = store->toRealPath(fetchers::downloadFile(store, url + "/nixexprs.tar.bz2", "nixexprs.tar.bz2", false).storePath);
+ }
+ }
+ // Regardless of where it came from, add the expression representing this channel to accumulated expression
+ exprs.push_back("f: f { name = \"" + cname + "\"; channelName = \"" + name + "\"; src = builtins.storePath \"" + filename + "\"; " + extraAttrs + " }");
+ }
+ }
+
+ // Unpack the channel tarballs into the Nix store and install them
+ // into the channels profile.
+ std::cerr << "unpacking " << exprs.size() << " channels...\n";
+ Strings envArgs{ "--profile", profile, "--file", unpackChannelPath, "--install", "--remove-all", "--from-expression" };
+ for (auto & expr : exprs)
+ envArgs.push_back(std::move(expr));
+ envArgs.push_back("--quiet");
+ runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
+
+ // Make the channels appear in nix-env.
+ struct stat st;
+ if (lstat(nixDefExpr.c_str(), &st) == 0) {
+ if (S_ISLNK(st.st_mode))
+ // old-skool ~/.nix-defexpr
+ if (unlink(nixDefExpr.c_str()) == -1)
+ throw SysError("unlinking %1%", nixDefExpr);
+ } else if (errno != ENOENT) {
+ throw SysError("getting status of %1%", nixDefExpr);
+ }
+ createDirs(nixDefExpr);
+ auto channelLink = nixDefExpr + "/channels";
+ replaceSymlink(profile, channelLink);
+}
+
+static int main_nix_channel(int argc, char ** argv)
+{
+ {
+ // Figure out the name of the `.nix-channels' file to use
+ auto home = getHome();
+ channelsList = settings.useXDGBaseDirectories ? createNixStateDir() + "/channels" : home + "/.nix-channels";
+ nixDefExpr = getNixDefExpr();
+
+ // Figure out the name of the channels profile.
+ profile = profilesDir() + "/channels";
+ createDirs(dirOf(profile));
+
+ enum {
+ cNone,
+ cAdd,
+ cRemove,
+ cList,
+ cUpdate,
+ cListGenerations,
+ cRollback
+ } cmd = cNone;
+ std::vector<std::string> args;
+ parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+ if (*arg == "--help") {
+ showManPage("nix-channel");
+ } else if (*arg == "--version") {
+ printVersion("nix-channel");
+ } else if (*arg == "--add") {
+ cmd = cAdd;
+ } else if (*arg == "--remove") {
+ cmd = cRemove;
+ } else if (*arg == "--list") {
+ cmd = cList;
+ } else if (*arg == "--update") {
+ cmd = cUpdate;
+ } else if (*arg == "--list-generations") {
+ cmd = cListGenerations;
+ } else if (*arg == "--rollback") {
+ cmd = cRollback;
+ } else {
+ if ((*arg).starts_with("-"))
+ throw UsageError("unsupported argument '%s'", *arg);
+ args.push_back(std::move(*arg));
+ }
+ return true;
+ });
+
+ switch (cmd) {
+ case cNone:
+ throw UsageError("no command specified");
+ case cAdd:
+ if (args.size() < 1 || args.size() > 2)
+ throw UsageError("'--add' requires one or two arguments");
+ {
+ auto url = args[0];
+ std::string name;
+ if (args.size() == 2) {
+ name = args[1];
+ } else {
+ name = baseNameOf(url);
+ name = std::regex_replace(name, std::regex("-unstable$"), "");
+ name = std::regex_replace(name, std::regex("-stable$"), "");
+ }
+ addChannel(url, name);
+ }
+ break;
+ case cRemove:
+ if (args.size() != 1)
+ throw UsageError("'--remove' requires one argument");
+ removeChannel(args[0]);
+ break;
+ case cList:
+ if (!args.empty())
+ throw UsageError("'--list' expects no arguments");
+ readChannels();
+ for (const auto & channel : channels)
+ std::cout << channel.first << ' ' << channel.second << '\n';
+ break;
+ case cUpdate:
+ update(StringSet(args.begin(), args.end()));
+ break;
+ case cListGenerations:
+ if (!args.empty())
+ throw UsageError("'--list-generations' expects no arguments");
+ std::cout << runProgram(settings.nixBinDir + "/nix-env", false, {"--profile", profile, "--list-generations"}) << std::flush;
+ break;
+ case cRollback:
+ if (args.size() > 1)
+ throw UsageError("'--rollback' has at most one argument");
+ Strings envArgs{"--profile", profile};
+ if (args.size() == 1) {
+ envArgs.push_back("--switch-generation");
+ envArgs.push_back(args[0]);
+ } else {
+ envArgs.push_back("--rollback");
+ }
+ runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
+ break;
+ }
+
+ return 0;
+ }
+}
+
+void registerNixChannel() {
+ LegacyCommands::add("nix-channel", main_nix_channel);
+}
+
+}