diff --git a/doc/manual/src/command-ref/files/channels.md b/doc/manual/src/command-ref/files/channels.md index 7b1f271280b4..599429cfd5a5 100644 --- a/doc/manual/src/command-ref/files/channels.md +++ b/doc/manual/src/command-ref/files/channels.md @@ -1,13 +1,25 @@ ## Channels -A directory containing symlinks to Nix channels, managed by [`nix-channel`]: +A directory containing symlinks to Nix channels, managed by [`nix-channel`]. -- `$XDG_STATE_HOME/nix/profiles/channels` for regular users -- `$NIX_STATE_DIR/profiles/per-user/root/channels` for `root` - -[`nix-channel`] uses a [profile](@docroot@/command-ref/files/profiles.md) to store channels. +The channels directory is a [profile](@docroot@/command-ref/files/profiles.md), so as to allow easy management of multiple versions and switching between them. This profile contains symlinks to the contents of those channels. +### User-specific and global channels + +Channels are managed either for a specific user, or for all users globally on the system to share. +This matches the [user-specific vs global conventions](@docroot@/command-ref/files/profiles.md#user-specific-and-global-profiles) of profiles themselves. + +- [User-specific channels]{#user-channels} are stored in: + ``` + $XDG_STATE_HOME/nix/profiles/channels + ``` + +- [Global channels]{#global-channels} are stored in + ``` + $NIX_STATE_DIR/profiles/per-user/root/channels + ``` + ## Subscribed channels The list of subscribed channels is stored in diff --git a/doc/manual/src/command-ref/files/default-nix-expression.md b/doc/manual/src/command-ref/files/default-nix-expression.md index 620f7035c581..d4a0c2f45806 100644 --- a/doc/manual/src/command-ref/files/default-nix-expression.md +++ b/doc/manual/src/command-ref/files/default-nix-expression.md @@ -42,8 +42,8 @@ A symlink that ensures that [`nix-env`] can find your channels: This symlink points to: -- `$XDG_STATE_HOME/profiles/channels` for regular users -- `$NIX_STATE_DIR/profiles/per-user/root/channels` for `root` +- The [user-specific channels](@docroot@/command-ref/files/profiles.md#user-channels) (`$XDG_STATE_HOME/profiles/channels`) for regular users +- The [global channels](@docroot@/command-ref/files/profiles.md#global-channels) (`$NIX_STATE_DIR/profiles/per-user/root/channels`) for `root` In a multi-user installation, you may also have `~/.nix-defexpr/channels_root`, which links to the channels of the root user.[`nix-env`]: ../nix-env.md diff --git a/doc/manual/src/command-ref/files/profiles.md b/doc/manual/src/command-ref/files/profiles.md index b5c7378800fd..31c467479ced 100644 --- a/doc/manual/src/command-ref/files/profiles.md +++ b/doc/manual/src/command-ref/files/profiles.md @@ -1,11 +1,32 @@ ## Profiles -A directory that contains links to profiles managed by [`nix-env`] and [`nix profile`]: +A directory that contains links to profiles managed by [`nix-env`] and [`nix profile`]. -- `$XDG_STATE_HOME/nix/profiles` for regular users -- `$NIX_STATE_DIR/profiles/per-user/root` if the user is `root` +### [User-specific and global profiles]{#user-specific-and-global-profiles} -A profile is a directory of symlinks to files in the Nix store. +Profiles can be placed in any directory, but by default, different locations are used depending on whether the profile is intended to be used by a single user, or shared globally by all users. + +- The default directory for user-specific profiles is: + ``` + $XDG_STATE_HOME/nix/profiles + ``` + + Within it, the [default user profile]{#default-user-profile} is: + ``` + $XDG_STATE_HOME/nix/profiles/profile + ``` + +- The default directory for global profiles is + ``` + $NIX_STATE_DIR/profiles + ``` + + Within it, the [default global profile]{#default-global-profile} is + ``` + $NIX_STATE_DIR/profiles/default + ``` + +Nix command will default to the default user profile for regular users, and the default global profiles for root, but often provide flags to override this behavior. ### Filesystem layout @@ -63,8 +84,8 @@ A symbolic link to the user's current profile: By default, this symlink points to: -- `$XDG_STATE_HOME/nix/profiles/profile` for regular users -- `$NIX_STATE_DIR/profiles/per-user/root/profile` for `root` +- The [default user profile](#default-user-profile) (`$XDG_STATE_HOME/nix/profiles/profile`) for regular users +- The [default global profile](#default-global-profile) (`$NIX_STATE_DIR/profiles/per-user/root/profile`) for `root` The `PATH` environment variable should include `/bin` subdirectory of the profile link (e.g. `~/.nix-profile/bin`) for the user environment to be visible to the user. The [installer](@docroot@/installation/installing-binary.md) sets this up by default, unless you enable [`use-xdg-base-directories`]. diff --git a/doc/manual/src/command-ref/nix-channel.md b/doc/manual/src/command-ref/nix-channel.md index 72d3e422b37c..63070f8dcaf1 100644 --- a/doc/manual/src/command-ref/nix-channel.md +++ b/doc/manual/src/command-ref/nix-channel.md @@ -46,6 +46,14 @@ This command has the following operations: Note that `--add` does not automatically perform an update. +It also has the following flags, which affect all operations: + + - `--global`\ + Operate on the [global channels](@docroot@/command-ref/files/channels.md#global-channels), i.e. the channels shared by all users. + + - `--user`\ + Operate on the [user channels](@docroot@/command-ref/files/channels.md#user-channels), i.e. the channels just for the current user. + The list of subscribed channels is stored in `~/.nix-channels`. {{#include ./opt-common.md}} diff --git a/doc/manual/src/command-ref/nix-env.md b/doc/manual/src/command-ref/nix-env.md index b4a3dce49f0a..4c22079dc0f4 100644 --- a/doc/manual/src/command-ref/nix-env.md +++ b/doc/manual/src/command-ref/nix-env.md @@ -9,7 +9,7 @@ [`--arg` *name* *value*] [`--argstr` *name* *value*] [{`--file` | `-f`} *path*] - [{`--profile` | `-p`} *path*] + [{`--profile` | `-p`} *path* | `--global` | `--user`] [`--system-filter` *system*] [`--dry-run`] diff --git a/doc/manual/src/command-ref/nix-env/opt-common.md b/doc/manual/src/command-ref/nix-env/opt-common.md index 636281b6d366..820a5d4f4b35 100644 --- a/doc/manual/src/command-ref/nix-env/opt-common.md +++ b/doc/manual/src/command-ref/nix-env/opt-common.md @@ -19,6 +19,16 @@ The following options are allowed for all `nix-env` operations, but may not alwa sequence of user environments called *generations*, one of which is the *current generation*. + - `--user`\ + Specifies the profile as the [default user profile](@docroot@/command-ref/files/profiles.md#default-user-profile) private to this user. + This is a shorthand for passing `--profile` and the path to that default profile. + This is default if the current user is not root. + + - `--global`\ + Specifies the profile as the [default global profile](@docroot@/command-ref/files/profiles.md#default-global-profile) shared between all users. + A shorthand instead of passing `--profile` and the path to that default profile. + This is default if the current user is root. + - `--dry-run`\ For the `--install`, `--upgrade`, `--uninstall`, `--switch-generation`, `--delete-generations` and `--rollback` diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 6c4648b34a83..767cfde6def4 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -271,7 +271,24 @@ void MixProfile::updateProfile(const BuiltPaths & buildables) MixDefaultProfile::MixDefaultProfile() { - profile = getDefaultProfile(); + + addFlag({ + .longName = "user-profile", + .description = "Act on the profile for this current user", + .handler = {[this]() { + profile = getDefaultProfile(DefaultProfileKind::User); + }} + }); + + addFlag({ + .longName = "global-profile", + .description = "Act on the global profile shared between all default users", + .handler = {[this]() { + profile = getDefaultProfile(DefaultProfileKind::Global); + }} + }); + + profile = getDefaultProfile(defaultDefaultProfileKind()); } MixEnvironment::MixEnvironment() : ignoreEnvironment(false) diff --git a/src/libexpr/def-expr.cc b/src/libexpr/def-expr.cc new file mode 100644 index 000000000000..6b4e41bdc27d --- /dev/null +++ b/src/libexpr/def-expr.cc @@ -0,0 +1,13 @@ +#include "def-expr.hh" +#include "globals.hh" + +namespace nix { + +Path getNixDefExpr() +{ + return settings.useXDGBaseDirectories ? + getStateDir() + "/nix/defexpr" + : getHome() + "/.nix-defexpr"; +} + +} diff --git a/src/libexpr/def-expr.hh b/src/libexpr/def-expr.hh new file mode 100644 index 000000000000..6b8a8a15968e --- /dev/null +++ b/src/libexpr/def-expr.hh @@ -0,0 +1,13 @@ +#pragma once +///@file + +#include "types.hh" + +namespace nix { + +/** + * Conventionally part of the default nix path in impure mode. + */ +Path getNixDefExpr(); + +} diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 740a5e677ce6..1de8abdb348c 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -10,6 +10,8 @@ #include "function-trace.hh" #include "profiles.hh" #include "print.hh" +#include "profiles/channels.hh" +#include "def-expr.hh" #include #include @@ -2629,9 +2631,9 @@ Strings EvalSettings::getDefaultNixPath() }; if (!evalSettings.restrictEval && !evalSettings.pureEval) { - add(settings.useXDGBaseDirectories ? getStateDir() + "/nix/defexpr/channels" : getHome() + "/.nix-defexpr/channels"); - add(rootChannelsDir() + "/nixpkgs", "nixpkgs"); - add(rootChannelsDir()); + add(getNixDefExpr() + "/channels"); + add(globalChannelsDir() + "/nixpkgs", "nixpkgs"); + add(globalChannelsDir()); } return res; diff --git a/src/libstore/profiles.cc b/src/libstore/profiles.cc index ba5c8583fae4..4e22a3182e8f 100644 --- a/src/libstore/profiles.cc +++ b/src/libstore/profiles.cc @@ -280,50 +280,106 @@ std::string optimisticLockProfile(const Path & profile) } -Path profilesDir() +Path userProfilesDir() { - auto profileRoot = - (getuid() == 0) - ? rootProfilesDir() - : createNixStateDir() + "/profiles"; + auto profileRoot = createNixStateDir() + "/profiles"; createDirs(profileRoot); return profileRoot; } -Path rootProfilesDir() +/** + * Return the path to the global profile directory (but don't try creating it) + * + * Just used for the global profile and the global channels, but those + * go in different subdirs, so we do not expose this. + */ +static Path globalProfilesDir() { - return settings.nixStateDir + "/profiles/per-user/root"; + return settings.nixStateDir + "/profiles"; } +/** + * The other directory used for global profiles + */ +static Path perUsersRootDir() +{ + return globalProfilesDir() + "/per-user/root/"; +} + +std::vector globalProfilesDirs() +{ + return { + globalProfilesDir(), + perUsersRootDir(), + }; +} + +Path getDefaultUserProfile() +{ + return userProfilesDir() + "/profile"; +} + +Path getDefaultGlobalProfile() +{ + return globalProfilesDir() + "/default"; +} -Path getDefaultProfile() +DefaultProfileKind defaultDefaultProfileKind() { - Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; + return getuid() == 0 + ? DefaultProfileKind::Global + : DefaultProfileKind::User; +} + +Path defaultProfileLink() +{ + return settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; +} + +std::optional tryGetDefaultProfile() +{ + Path profileLink = defaultProfileLink(); + if (pathExists(profileLink)) + return absPath(readLink(profileLink), dirOf(profileLink)); + else + return std::nullopt; +} + +Path getDefaultProfile(DefaultProfileKind profileKind) +{ + Path profileLink = defaultProfileLink(); try { - auto profile = profilesDir() + "/profile"; if (!pathExists(profileLink)) { + Path profile; + switch (profileKind) { + case DefaultProfileKind::Global: + profile = getDefaultGlobalProfile(); + break; + case DefaultProfileKind::User: + profile = getDefaultUserProfile(); + break; + default: + assert(false); + } replaceSymlink(profile, profileLink); } - // Backwards compatibiliy measure: Make root's profile available as - // `.../default` as it's what NixOS and most of the init scripts expect - Path globalProfileLink = settings.nixStateDir + "/profiles/default"; - if (getuid() == 0 && !pathExists(globalProfileLink)) { - replaceSymlink(profile, globalProfileLink); - } return absPath(readLink(profileLink), dirOf(profileLink)); } catch (Error &) { return profileLink; } } -Path defaultChannelsDir() +Path userChannelsDir() { - return profilesDir() + "/channels"; + return userProfilesDir() + "/channels"; } -Path rootChannelsDir() +Path globalChannelsDir() { - return rootProfilesDir() + "/channels"; + /* "per-user" might seem like a weird thing to include in the path + to the "global" channels dir; it is done this way for + back-compat. */ + return perUsersRootDir() + "/channels"; } } diff --git a/src/libstore/profiles.hh b/src/libstore/profiles.hh index 4e1f42e83db3..a13fb01732cb 100644 --- a/src/libstore/profiles.hh +++ b/src/libstore/profiles.hh @@ -81,28 +81,60 @@ std::string optimisticLockProfile(const Path & profile); * Create and return the path to a directory suitable for storing the user’s * profiles. */ -Path profilesDir(); +Path userProfilesDir(); /** - * Return the path to the profile directory for root (but don't try creating it) + * Create and return the paths to the directories use for storing the + * global profiles. There are multiple for backwards compatibility + * reasons. */ -Path rootProfilesDir(); +std::vector globalProfilesDirs(); /** - * Create and return the path to the file used for storing the users's channels + * We have two types of default profiles: + * + * - User, just for the current user (read/write). + * + * - Global, shared (read-only) by all users. */ -Path defaultChannelsDir(); +enum class DefaultProfileKind +{ + User, + Global, +}; + +/** + * Returns the default kind of default profile. + * + * For regular users, this is DefaultProfileKind::Global, but for root it is DefaultProfileKind::User. + */ +DefaultProfileKind defaultDefaultProfileKind(); + +/** + * The path to the link (itself, not its target) defining the default profile. + * + * It is `~/.nix-profile` by default, `$XDG_STATE_HOME/nix/profile` if + * XDG Base Directory Support is enabled). + */ +Path defaultProfileLink(); /** - * Return the path to the channel directory for root (but don't try creating it) + * Resolve the default profile (as indicated by `defaultProfileLink()`). + * + * If it doesn't exist, return `std::nullopt`. */ -Path rootChannelsDir(); +std::optional tryGetDefaultProfile(); /** - * Resolve the default profile (~/.nix-profile by default, - * $XDG_STATE_HOME/nix/profile if XDG Base Directory Support is enabled), - * and create if doesn't exist + * Resolve the default profile (just `tryGetDefaultProfile()`), + * but also create if doesn't exist. + * + * If something goes wrong, just return the path to the link itself. + * + * @todo Why do squelch failures this way? + * + * @param kind Just used if the profile doesn't exist. */ -Path getDefaultProfile(); +Path getDefaultProfile(DefaultProfileKind kind); } diff --git a/src/libstore/profiles/channels.hh b/src/libstore/profiles/channels.hh new file mode 100644 index 000000000000..b01c5decefa7 --- /dev/null +++ b/src/libstore/profiles/channels.hh @@ -0,0 +1,17 @@ +#pragma once + +#include "types.hh" + +namespace nix { + +/** + * Create and return the path to the file used for storing the user's channels + */ +Path userChannelsDir(); + +/** + * Return the path to the global channel directory (but don't try creating it) + */ +Path globalChannelsDir(); + +} diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc index 740737ffe330..8abec7851f7c 100755 --- a/src/nix-channel/nix-channel.cc +++ b/src/nix-channel/nix-channel.cc @@ -1,10 +1,12 @@ #include "profiles.hh" +#include "profiles/channels.hh" #include "shared.hh" #include "globals.hh" #include "filetransfer.hh" #include "store-api.hh" #include "legacy.hh" #include "fetchers.hh" +#include "def-expr.hh" #include "util.hh" #include @@ -165,11 +167,7 @@ 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 = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : home + "/.nix-defexpr"; - - // Figure out the name of the channels profile. - profile = profilesDir() + "/channels"; - createDirs(dirOf(profile)); + nixDefExpr = getNixDefExpr(); enum { cNone, @@ -179,6 +177,9 @@ static int main_nix_channel(int argc, char ** argv) cUpdate, cRollback } cmd = cNone; + + DefaultProfileKind profileKind = defaultDefaultProfileKind(); + std::vector args; parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { if (*arg == "--help") { @@ -195,6 +196,10 @@ static int main_nix_channel(int argc, char ** argv) cmd = cUpdate; } else if (*arg == "--rollback") { cmd = cRollback; + } else if (*arg == "--user") { + profileKind = DefaultProfileKind::User; + } else if (*arg == "--global") { + profileKind = DefaultProfileKind::Global; } else { if (hasPrefix(*arg, "-")) throw UsageError("unsupported argument '%s'", *arg); @@ -203,6 +208,19 @@ static int main_nix_channel(int argc, char ** argv) return true; }); + // Figure out the name of the channels profile. + switch (profileKind) { + case DefaultProfileKind::User: + profile = userChannelsDir(); + break; + case DefaultProfileKind::Global: + profile = globalChannelsDir(); + break; + default: + assert(false); + }; + createDirs(dirOf(profile)); + switch (cmd) { case cNone: throw UsageError("no command specified"); diff --git a/src/nix-collect-garbage/nix-collect-garbage.cc b/src/nix-collect-garbage/nix-collect-garbage.cc index cb1f42e35ae3..0ee9fa12a81d 100644 --- a/src/nix-collect-garbage/nix-collect-garbage.cc +++ b/src/nix-collect-garbage/nix-collect-garbage.cc @@ -79,7 +79,12 @@ static int main_nix_collect_garbage(int argc, char * * argv) if (removeOld) { std::set dirsToClean = { - profilesDir(), settings.nixStateDir + "/profiles", dirOf(getDefaultProfile())}; + userProfilesDir(), + }; + if (auto optProfile = tryGetDefaultProfile()) + dirsToClean.insert(dirOf(*optProfile)); + for (auto && dir : globalProfilesDirs()) + dirsToClean.insert(std::move(dir)); for (auto & dir : dirsToClean) removeOldGenerations(dir); } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 5e94f2d14d4e..ee29520d58d6 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -6,6 +6,7 @@ #include "globals.hh" #include "names.hh" #include "profiles.hh" +#include "profiles/channels.hh" #include "path-with-outputs.hh" #include "shared.hh" #include "store-api.hh" @@ -15,6 +16,7 @@ #include "value-to-json.hh" #include "xml-writer.hh" #include "legacy.hh" +#include "def-expr.hh" #include #include @@ -54,6 +56,7 @@ struct InstallSourceInfo struct Globals { InstallSourceInfo instSource; + std::optional profileKind; Path profile; std::shared_ptr state; bool dryRun; @@ -1291,7 +1294,7 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs) throw UsageError("exactly one argument expected"); Path profile = absPath(opArgs.front()); - Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; + Path profileLink = defaultProfileLink(); switchLink(profileLink, profile); } @@ -1395,29 +1398,13 @@ static int main_nix_env(int argc, char * * argv) Globals globals; - globals.instSource.type = srcUnknown; - globals.instSource.systemFilter = "*"; - - Path nixExprPath = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : getHome() + "/.nix-defexpr"; - - if (!pathExists(nixExprPath)) { - try { - createDirs(nixExprPath); - replaceSymlink( - defaultChannelsDir(), - nixExprPath + "/channels"); - if (getuid() != 0) - replaceSymlink( - rootChannelsDir(), - nixExprPath + "/channels_root"); - } catch (Error &) { } - } - globals.dryRun = false; globals.preserveInstalled = false; globals.removeAll = false; globals.prebuiltOnly = false; + globals.profileKind = { defaultDefaultProfileKind() }; + struct MyArgs : LegacyArgs, MixEvalArgs { using LegacyArgs::LegacyArgs; @@ -1456,8 +1443,16 @@ static int main_nix_env(int argc, char * * argv) op = opQuery; opName = "-query"; } - else if (*arg == "--profile" || *arg == "-p") + else if (*arg == "--user") { + globals.profileKind = DefaultProfileKind::User; + } + else if (*arg == "--global") { + globals.profileKind = DefaultProfileKind::Global; + } + else if (*arg == "--profile" || *arg == "-p") { + globals.profileKind = std::nullopt; globals.profile = absPath(getArg(*arg, arg, end)); + } else if (*arg == "--file" || *arg == "-f") file = getArg(*arg, arg, end); else if (*arg == "--switch-profile" || *arg == "-S") { @@ -1509,8 +1504,39 @@ static int main_nix_env(int argc, char * * argv) if (showHelp) showManPage("nix-env" + opName); if (!op) throw UsageError("no operation specified"); + // Store layer concerns + auto store = openStore(); + // Profiles layer concerns + + if (globals.profile == "") + globals.profile = getEnv("NIX_PROFILE").value_or(""); + + if (globals.profile == "") { + assert(globals.profileKind); + globals.profile = getDefaultProfile(*globals.profileKind); + } + + // Eval layer concerns + + globals.instSource.type = srcUnknown; + globals.instSource.systemFilter = "*"; + + Path nixExprPath = getNixDefExpr(); + + if (!pathExists(nixExprPath)) { + try { + createDirs(nixExprPath); + replaceSymlink( + userChannelsDir(), + nixExprPath + "/channels"); + replaceSymlink( + globalChannelsDir(), + nixExprPath + "/channels_root"); + } catch (Error &) { } + } + globals.state = std::shared_ptr(new EvalState(myArgs.searchPath, store)); globals.state->repair = myArgs.repair; @@ -1521,11 +1547,7 @@ static int main_nix_env(int argc, char * * argv) globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state); - if (globals.profile == "") - globals.profile = getEnv("NIX_PROFILE").value_or(""); - - if (globals.profile == "") - globals.profile = getDefaultProfile(); + // Do the operation op(globals, std::move(opFlags), std::move(opArgs));