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-11 18:05:14 -06:00
parent 04a87f34cb
commit 2280c53fcc
10 changed files with 217 additions and 24 deletions

View file

@ -269,7 +269,15 @@ void MixProfile::updateProfile(const BuiltPaths & buildables)
MixDefaultProfile::MixDefaultProfile()
{
profile = getDefaultProfile();
static bool haveWarned = false;
try {
// `nix profile remove` at the least seems to expect this.
this->profile = ensureDefaultProfile();
} catch (SysError const & e) {
// However we get called in __dumpCli too, so we should not do fatal IO.
warnOnce(haveWarned, "ignoring error initializing default profile %s", e.what());
this->profile = getDefaultProfile();
}
}
MixEnvironment::MixEnvironment() : ignoreEnvironment(false)

View file

@ -216,6 +216,10 @@ LocalStore::LocalStore(const Params & params)
}
}
if (!readOnly) {
createUser(getUserName(), getuid());
}
/* Optionally, create directories and set permissions for a
multi-user install. */
if (getuid() == 0 && settings.buildUsersGroup != "") {
@ -1788,6 +1792,27 @@ void LocalStore::signRealisation(Realisation & realisation)
}
}
void LocalStore::createUser(std::string_view userName, uid_t userId)
{
using namespace std::literals::string_view_literals;
// FIXME(Qyriad): is /nix/var/nix/gcroots/per-user used anywhere...?
for (auto const & dirPart : {"profiles"sv, "gcroots"sv}) {
auto const fullPerUserDir = fmt("%s/%s/per-user/%s", stateDir, dirPart, userName);
createDirs(fullPerUserDir);
if (chmod(fullPerUserDir.c_str(), 0755) == -1) {
throw SysError(errno, "changing permissions of directory '%s'", fullPerUserDir);
}
if (chown(fullPerUserDir.c_str(), userId, getgid()) == -1) {
throw SysError(errno, "changing owner of directory '%s'", fullPerUserDir);
}
}
}
void LocalStore::signPathInfo(ValidPathInfo & info)
{
// FIXME: keep secret keys in memory.

View file

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

View file

@ -307,12 +307,7 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir()
{
auto profileRoot =
(getuid() == 0)
? rootProfilesDir()
: createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
return fmt("%s/profiles/per-user/%s", settings.nixStateDir, getUserName());
}
Path rootProfilesDir()
@ -332,24 +327,40 @@ Path getDefaultProfileLink()
return getHome() + "/.nix-profile";
}
Path getDefaultProfile()
Path ensureDefaultProfile()
{
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try {
auto profile = profilesDir() + "/profile";
Path const profileLink = getDefaultProfileLink();
Path const defaultProfile = profilesDir() + "/profile";
if (!pathExists(profileLink)) {
replaceSymlink(profile, 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 globalProfileLink = settings.nixStateDir + "/profiles/default";
Path const globalProfileLink = settings.nixStateDir + "/profiles/default";
if (getuid() == 0 && !pathExists(globalProfileLink)) {
replaceSymlink(profile, globalProfileLink);
replaceSymlink(defaultProfile, globalProfileLink);
}
return absPath(readLink(profileLink), dirOf(profileLink));
} catch (Error &) {
return profileLink;
}
Path getDefaultProfile()
{
Path const profileLink = getDefaultProfileLink();
try {
if (pathExists(profileLink)) {
return absPath(readLink(profileLink), dirOf(profileLink));
}
} catch (SysError const & e) {
printTalkative("ignoring error resolving default profile '%s': %s", profileLink, e.what());
} catch (Error const & e) {
printError("ignoring error resolving default profile '%s': %s", profileLink, e.what());
}
return profileLink;
}
Path defaultChannelsDir()

View file

@ -235,9 +235,14 @@ Path getDefaultProfileLink();
/**
* 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
* $XDG_STATE_HOME/nix/profile if XDG Base Directory Support is enabled).
*/
Path getDefaultProfile();
/**
* Resolve the default profile (see getDefaultProfile()),
* and create it if it doesn't exist.
*/
Path ensureDefaultProfile();
}

View file

@ -845,6 +845,11 @@ public:
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
* (a no-op when theres no daemon)

View file

@ -357,6 +357,13 @@ static void daemonLoop(std::optional<TrustedFlag> forceTrustClientOpt)
// Restore normal handling of SIGCHLD.
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].
if (peer.pidKnown && savedArgv[1]) {
auto processName = std::to_string(peer.pid);
@ -366,7 +373,7 @@ static void daemonLoop(std::optional<TrustedFlag> forceTrustClientOpt)
// Handle the connection.
FdSource from(remote.get());
FdSink to(remote.get());
processConnection(openUncachedStore(), from, to, trusted, NotRecursive);
processConnection(store, from, to, trusted, NotRecursive);
exit(0);
}, options);

View file

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

View file

@ -143,6 +143,8 @@ in
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;
githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix;

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

@ -0,0 +1,128 @@
{ 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";
oldNix = pkgs.nixVersions.nix_2_18;
nix23-daemon = lib.getExe' oldNix "nix-daemon";
nix23-env = lib.getExe' oldNix "nix-env";
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
(lib.getBin pkgs.nixVersions.nix_2_3)
];
users.users.alice.isNormalUser = true;
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;
};
};
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("nix --version -vv >&2")
# Initial state.
#machine.succeed("""
# su --login alice -c '
# set -euxo pipefail
# ${ezaTree} ~
# ${ezaTree} /nix/var/nix
# :' >&2
#""")
# Create some client-side profiles for us to worry about.
machine.succeed("""
export NIX_DAEMON_SOCKET_PATH=/tmp/nix23-socket
${nix23-daemon} >&2 &
export _NIX_DAEMON_PID=$!
su --login alice -c '
export NIX_DAEMON_SOCKET_PATH=/tmp/nix23-socket
${nix23-env} --version -vv
${nix23-env} --file "<nixpkgs>" --install -A hello
${ezaTree} ~
${ezaTree} /nix/var/nix/profiles
:' >&2
kill "$_NIX_DAEMON_PID" >&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
""")
'';
}