WIP: restore /nix/var/nix as source of truth for profiles

See-also: https://github.com/NixOS/nix/pull/5226
Change-Id: I35082c9a59148f7fb132232659c3a254d514d190
This commit is contained in:
Qyriad 2024-05-03 08:21:03 -06:00
parent 41b903c5fe
commit e63572f540
15 changed files with 241 additions and 35 deletions

View file

@ -269,7 +269,13 @@ void MixProfile::updateProfile(const BuiltPaths & buildables)
MixDefaultProfile::MixDefaultProfile() MixDefaultProfile::MixDefaultProfile()
{ {
profile = getDefaultProfile(); // `nix profile remove` at the least expects this. huh.
try {
this->profile = ensureDefaultProfile();
} catch (SysError const & e) {
// We get called in __dumpCli too, so we should not do fallable IO.
printTalkative("ignoring error initializing default profile %s", e.what());
}
} }
MixEnvironment::MixEnvironment() : ignoreEnvironment(false) MixEnvironment::MixEnvironment() : ignoreEnvironment(false)

View file

@ -217,6 +217,8 @@ LocalStore::LocalStore(const Params & params)
} }
} }
createUser(getUserName(), getuid());
/* Optionally, create directories and set permissions for a /* Optionally, create directories and set permissions for a
multi-user install. */ multi-user install. */
if (getuid() == 0 && settings.buildUsersGroup != "") { if (getuid() == 0 && settings.buildUsersGroup != "") {
@ -1793,6 +1795,21 @@ void LocalStore::signRealisation(Realisation & realisation)
} }
} }
void LocalStore::createUser(std::string_view userName, uid_t userId)
{
// XXX: previously created gcroots/per-user; should this too?
auto const perUserProfile = fmt("%s/profiles/per-user/%s", stateDir, userName);
createDirs(perUserProfile);
if (chmod(perUserProfile.c_str(), 0755) == -1) {
throw SysError(errno, "changing permissions of directory '%s'", perUserProfile);
}
if (chown(perUserProfile.c_str(), userId, getgid()) == -1) {
throw SysError(errno, "changing owner of directory '%s'", perUserProfile);
}
}
void LocalStore::signPathInfo(ValidPathInfo & info) void LocalStore::signPathInfo(ValidPathInfo & info)
{ {
// FIXME: keep secret keys in memory. // FIXME: keep secret keys in memory.

View file

@ -366,6 +366,8 @@ private:
void signPathInfo(ValidPathInfo & info); void signPathInfo(ValidPathInfo & info);
void signRealisation(Realisation &); void signRealisation(Realisation &);
void createUser(std::string_view userName, uid_t userId) override;
// XXX: Make a generic `Store` method // XXX: Make a generic `Store` method
ContentAddress hashCAPath( ContentAddress hashCAPath(
const ContentAddressMethod & method, const ContentAddressMethod & method,

View file

@ -307,12 +307,7 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir() Path profilesDir()
{ {
auto profileRoot = return fmt("%s/profiles/per-user/%s", settings.nixStateDir, getUserName());
(getuid() == 0)
? rootProfilesDir()
: createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
} }
Path rootProfilesDir() Path rootProfilesDir()
@ -320,25 +315,52 @@ Path rootProfilesDir()
return settings.nixStateDir + "/profiles/per-user/root"; return settings.nixStateDir + "/profiles/per-user/root";
} }
Path getDefaultProfileLink()
{
if (getuid() == 0) {
return settings.nixStateDir + "/profiles/default";
}
if (settings.useXDGBaseDirectories) {
return createNixStateDir() + "/profile";
}
return getHome() + "/.nix-profile";
}
Path ensureDefaultProfile()
{
Path const profileLink = getDefaultProfileLink();
Path const defaultProfile = profilesDir() + "/profile";
if (!pathExists(profileLink)) {
replaceSymlink(defaultProfile, profileLink);
}
// Backwards compatibiliy measure: Make root's profile available as
// `.../default` as it's what NixOS and most of the init scripts expect
Path const globalProfileLink = settings.nixStateDir + "/profiles/default";
if (getuid() == 0 && !pathExists(globalProfileLink)) {
replaceSymlink(defaultProfile, globalProfileLink);
}
return absPath(readLink(profileLink), dirOf(profileLink));
}
Path getDefaultProfile() Path getDefaultProfile()
{ {
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; Path const profileLink = getDefaultProfileLink();
try { try {
auto profile = profilesDir() + "/profile"; if (pathExists(profileLink)) {
if (!pathExists(profileLink)) { return absPath(readLink(profileLink), dirOf(profileLink));
replaceSymlink(profile, profileLink);
} }
// Backwards compatibiliy measure: Make root's profile available as } catch (SysError const & e) {
// `.../default` as it's what NixOS and most of the init scripts expect printTalkative("ignoring error resolving default profile '%s': %s", profileLink, e.what());
Path globalProfileLink = settings.nixStateDir + "/profiles/default"; } catch (Error const & e) {
if (getuid() == 0 && !pathExists(globalProfileLink)) { printError("ignoring error resolving default profile '%s': %s", profileLink, e.what());
replaceSymlink(profile, globalProfileLink);
}
return absPath(readLink(profileLink), dirOf(profileLink));
} catch (Error &) {
return profileLink;
} }
return profileLink;
} }
Path defaultChannelsDir() Path defaultChannelsDir()

View file

@ -234,4 +234,6 @@ Path rootChannelsDir();
*/ */
Path getDefaultProfile(); Path getDefaultProfile();
Path ensureDefaultProfile();
} }

View file

@ -859,6 +859,11 @@ public:
return toRealPath(printStorePath(storePath)); return toRealPath(printStorePath(storePath));
} }
virtual void createUser(std::string_view userName, uid_t userId)
{
warn("base class Store called unimplemented createUser()");
}
/** /**
* Synchronises the options of the client with those of the daemon * Synchronises the options of the client with those of the daemon
* (a no-op when theres no daemon) * (a no-op when theres no daemon)

View file

@ -169,7 +169,7 @@ static int main_nix_channel(int argc, char ** argv)
nixDefExpr = getNixDefExpr(); nixDefExpr = getNixDefExpr();
// Figure out the name of the channels profile. // Figure out the name of the channels profile.
profile = profilesDir() + "/channels"; profile = defaultChannelsDir();
createDirs(dirOf(profile)); createDirs(dirOf(profile));
enum { enum {

View file

@ -81,7 +81,13 @@ static int main_nix_collect_garbage(int argc, char * * argv)
if (removeOld) { if (removeOld) {
std::set<Path> dirsToClean = { std::set<Path> dirsToClean = {
profilesDir(), settings.nixStateDir + "/profiles", dirOf(getDefaultProfile())}; // XXX: check
profilesDir(),
// XXX: refactor
settings.nixStateDir + "/profiles",
/// XXX: check
dirOf(getDefaultProfile()),
};
for (auto & dir : dirsToClean) for (auto & dir : dirsToClean)
removeOldGenerations(dir); removeOldGenerations(dir);
} }

View file

@ -503,7 +503,7 @@ static bool keep(DrvInfo & drv)
static void installDerivations(Globals & globals, static void installDerivations(Globals & globals,
const Strings & args, const Path & profile) const Strings & args, const Path & profile)
{ {
debug("installing derivations"); debug("installing derivations into profile %s", profile);
/* Get the set of user environment elements to be installed. */ /* Get the set of user environment elements to be installed. */
DrvInfos newElems, newElemsTmp; DrvInfos newElems, newElemsTmp;
@ -554,8 +554,17 @@ static void installDerivations(Globals & globals,
if (globals.dryRun) return; if (globals.dryRun) return;
if (createUserEnv(*globals.state, allElems, bool success = createUserEnv(
profile, settings.envKeepDerivations, lockToken)) break; *globals.state,
allElems,
profile,
settings.envKeepDerivations,
lockToken
);
if (success) {
break;
}
} }
} }
@ -1296,6 +1305,7 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
if (opArgs.size() != 1) if (opArgs.size() != 1)
throw UsageError("exactly one argument expected"); throw UsageError("exactly one argument expected");
// XXX: refactor
Path profile = absPath(opArgs.front()); Path profile = absPath(opArgs.front());
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
@ -1463,8 +1473,10 @@ static int main_nix_env(int argc, char * * argv)
op = opQuery; op = opQuery;
opName = "-query"; opName = "-query";
} }
else if (*arg == "--profile" || *arg == "-p") else if (*arg == "--profile" || *arg == "-p") {
// XXX: check
globals.profile = absPath(getArg(*arg, arg, end)); globals.profile = absPath(getArg(*arg, arg, end));
}
else if (*arg == "--file" || *arg == "-f") else if (*arg == "--file" || *arg == "-f")
file = getArg(*arg, arg, end); file = getArg(*arg, arg, end);
else if (*arg == "--switch-profile" || *arg == "-S") { else if (*arg == "--switch-profile" || *arg == "-S") {
@ -1528,11 +1540,15 @@ static int main_nix_env(int argc, char * * argv)
globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state); globals.instSource.autoArgs = myArgs.getAutoArgs(*globals.state);
if (globals.profile == "") if (globals.profile == "") {
// XXX: refactor?
globals.profile = getEnv("NIX_PROFILE").value_or(""); globals.profile = getEnv("NIX_PROFILE").value_or("");
}
if (globals.profile == "") if (globals.profile == "") {
globals.profile = getDefaultProfile(); // XXX: check
globals.profile = ensureDefaultProfile();
}
op(globals, std::move(opFlags), std::move(opArgs)); op(globals, std::move(opFlags), std::move(opArgs));

View file

@ -20,6 +20,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
const Path & profile, bool keepDerivations, const Path & profile, bool keepDerivations,
const std::string & lockToken) const std::string & lockToken)
{ {
debug("asked to create a user env %s for %u drvs", profile, elems.size());
/* Build the components in the user environment, if they don't /* Build the components in the user environment, if they don't
exist already. */ exist already. */
std::vector<StorePathWithOutputs> drvsToBuild; std::vector<StorePathWithOutputs> drvsToBuild;
@ -131,10 +132,11 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
state.repair ? bmRepair : bmNormal); state.repair ? bmRepair : bmNormal);
/* Switch the current user environment to the output path. */ /* Switch the current user environment to the output path. */
auto store2 = state.store.dynamic_pointer_cast<LocalFSStore>(); auto localStore = state.store.dynamic_pointer_cast<LocalFSStore>();
if (store2) { if (localStore) {
PathLocks lock; PathLocks lock;
debug("locking profile %s", profile);
lockProfile(lock, profile); lockProfile(lock, profile);
Path lockTokenCur = optimisticLockProfile(profile); Path lockTokenCur = optimisticLockProfile(profile);
@ -144,7 +146,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
} }
debug("switching to new user environment"); debug("switching to new user environment");
Path generation = createGeneration(*store2, profile, topLevelOut); Path generation = createGeneration(*localStore, profile, topLevelOut);
switchLink(profile, generation); switchLink(profile, generation);
} }

View file

@ -357,6 +357,13 @@ static void daemonLoop(std::optional<TrustedFlag> forceTrustClientOpt)
// Restore normal handling of SIGCHLD. // Restore normal handling of SIGCHLD.
setSigChldAction(false); setSigChldAction(false);
auto store = openUncachedStore();
try {
store->createUser(user, peer.uid);
} catch (SysError const & e) {
printError("ignoring error while creating store per-user state: %s", e.what());
}
// For debugging, stuff the pid into argv[1]. // For debugging, stuff the pid into argv[1].
if (peer.pidKnown && savedArgv[1]) { if (peer.pidKnown && savedArgv[1]) {
auto processName = std::to_string(peer.pid); auto processName = std::to_string(peer.pid);
@ -366,7 +373,7 @@ static void daemonLoop(std::optional<TrustedFlag> forceTrustClientOpt)
// Handle the connection. // Handle the connection.
FdSource from(remote.get()); FdSource from(remote.get());
FdSink to(remote.get()); FdSink to(remote.get());
processConnection(openUncachedStore(), from, to, trusted, NotRecursive); processConnection(store, from, to, trusted, NotRecursive);
exit(0); exit(0);
}, options); }, options);

View file

@ -66,7 +66,7 @@ readLink() {
} }
clearProfiles() { clearProfiles() {
profiles="$HOME"/.local/state/nix/profiles profiles="$NIX_STATE_DIR/profiles/per-user/$(whoami || echo -n nixbld)"
rm -rf "$profiles" rm -rf "$profiles"
} }

View file

@ -37,8 +37,10 @@ nix-env -qa '*' --description | grepQuiet silly
# Query the system. # Query the system.
nix-env -qa '*' --system | grepQuiet $system nix-env -qa '*' --system | grepQuiet $system
readlink $HOME/.nix-profile || true
eza --tree -la $HOME/.local/state/nix || true
# Install "foo-1.0". # Install "foo-1.0".
nix-env -i foo-1.0 nix-env --debug -i foo-1.0
# Query installed: should contain foo-1.0 now (which should be # Query installed: should contain foo-1.0 now (which should be
# executable). # executable).

View file

@ -143,6 +143,8 @@ in
nix-upgrade-nix = runNixOSTestFor "x86_64-linux" ./nix-upgrade-nix.nix; nix-upgrade-nix = runNixOSTestFor "x86_64-linux" ./nix-upgrade-nix.nix;
profiles = runNixOSTestFor "x86_64-linux" ./profiles.nix;
nssPreload = runNixOSTestFor "x86_64-linux" ./nss-preload.nix; nssPreload = runNixOSTestFor "x86_64-linux" ./nss-preload.nix;
githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix; githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix;

117
tests/nixos/profiles.nix Normal file
View file

@ -0,0 +1,117 @@
{ lib, config, ... }:
let
pkgs = config.nodes.machine.nixpkgs.pkgs;
eza = "${lib.getExe pkgs.eza} -laa --group-directories-first --classify --color=always";
ezaTree = "${lib.getExe pkgs.eza} -la --tree --color=always";
in {
name = "nix-profiles";
nodes = {
machine = { config, lib, pkgs, ... }: {
virtualisation.writableStore = true;
virtualisation.additionalPaths = [
pkgs.hello
pkgs.cowsay.out
pkgs.cowsay.man
pkgs.cowsay.drvPath
pkgs.eza
];
users.users.alice.isNormalUser = true;
#virtualisation.additionalPaths = [ pkgs.hello.drvPath ];
#nix.settings.substituters = lib.mkForce [ ];
#nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.settings = {
substituters = lib.mkForce [ ];
experimental-features = [ "nix-command" "flakes" ];
allowed-users = [ "alice" ];
use-xdg-base-directories = true;
};
nix.nixPath = [ "nixpkgs=${pkgs.path}" ];
#nix.package = pkgs.nixVersions.nix_2_18;
#nix.package = pkgs.nixVersions.nix_2_3;
#services.getty.autologinUser = "alice";
};
};
testScript = { nodes }: ''
# fmt: off
start_all()
machine.wait_for_unit("multi-user.target")
machine.succeed("nix -vv --version >&2")
print(machine.succeed("${eza} ~ >&2"))
print(machine.succeed("realpath ~ >&2"))
machine.succeed("""
set -x
#nix config show >&2
nix-env --version -vv >&2
""")
print(machine.succeed("systemctl cat nix-daemon.service"))
# Initial state.
machine.succeed("""
su --login alice -c '
set -euxo pipefail
${ezaTree} ~
${ezaTree} /nix/var/nix
:' >&2
""")
# This one is as root.
#machine.succeed("""
# nix-env --store local --file "<nixpkgs>" --install -A hello >&2
# ${ezaTree} ~ >&2
# ${ezaTree} /nix/var/nix/profiles >&2
#""")
machine.succeed("""
su --login alice -c '
set -euxo pipefail
nix-env --file "<nixpkgs>" --install -A hello
${ezaTree} ~
${ezaTree} /nix/var/nix/profiles
:' >&2
""")
machine.succeed("""
su --login alice -c '
set -euxo pipefail
nix-env --file "<nixpkgs>" --install -A cowsay
${ezaTree} ~
${ezaTree} /nix/var/nix/profiles
:' >&2
""")
machine.succeed("""
su --login alice -c '
set -euxo pipefail
PAGER=cat nix-env --option use-xdg-base-directories false --query '*'
${ezaTree} ~
${ezaTree} /nix/var/nix/profiles
:' >&2
""")
machine.succeed("""
su --login alice -c '
set -euxo pipefail
nix-env --uninstall cowsay
${ezaTree} ~
${ezaTree} /nix/var/nix/profiles
:' >&2
""")
machine.succeed("""
su --login alice -c '
set -euxo pipefail
nix profile install -f "<nixpkgs>" cowsay
${ezaTree} ~
${ezaTree} /nix/var/nix/profiles
:' >&2
""")
'';
}