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 19645a4a64
commit 3784db0eaf
6 changed files with 125 additions and 24 deletions

View file

@ -217,6 +217,8 @@ LocalStore::LocalStore(const Params & params)
}
}
createUser(getUserName(), getuid());
/* Optionally, create directories and set permissions for a
multi-user install. */
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)
{
// FIXME: keep secret keys in memory.

View file

@ -366,6 +366,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

@ -304,15 +304,95 @@ std::string optimisticLockProfile(const Path & profile)
return pathExists(profile) ? readLink(profile) : "";
}
/**
* Migrates a set of profiles from one directory to another.
*
* Intended for migration from ~/.local/state/nix/profiles
* to /nix/var/nix/profiles/per-user/foo
*/
void migrateProfiles(Path const & profilesFrom, Path const & profilesTo)
{
auto const oldProfiles = readDirectory(profilesFrom);
for (DirEntry const & oldProfileEnt : oldProfiles) {
auto const oldGenPath = profilesFrom + "/" + oldProfileEnt.name;
if (!isLink(oldGenPath)) {
// This is not a profile and we should not touch it.
continue;
}
auto const newGenPath = fmt("%s/%s", profilesTo, oldProfileEnt.name);
auto const genTarget = readLink(oldGenPath);
printTalkative("migrating XDG profile '%s' to '%s'", oldGenPath, newGenPath);
replaceSymlink(genTarget, newGenPath);
deletePath(oldGenPath);
}
// Now that we've deleted all the profile symlinks in ~/.local/state/nix/profiles,
// it's time to make ~/.local/state/nix/profiles itself a symlink.
if (readDirectory(profilesFrom).empty()) {
removeFile(profilesFrom);
replaceSymlink(profilesTo, profilesFrom);
} else {
// But if that directory had stuff other than profiles in it, we shouldn't delete that.
// Back them up for the user.
warn("non-profiles found in '%s'; backing up instead of deleting", profilesFrom);
Path backupName = profilesFrom + ".bak";
if (pathExists(backupName)) {
unsigned suffix = 0;
do {
assert(suffix < UINT_MAX);
suffix += 1;
backupName = fmt("%s.bak-%u", profilesFrom, suffix);
} while (pathExists(backupName));
}
printTalkative("backed up as '%s'", backupName);
renameFile(profilesFrom, backupName);
}
}
Path ensureProfilesDir()
{
// root is special.
if (getuid() == 0) {
auto profileRoot = rootProfilesDir();
createDirs(profileRoot);
return profileRoot;
}
auto const localNixStateDir = createNixStateDir();
auto const homeStateProfiles = localNixStateDir + "/profiles";
auto const nixVarProfiles = fmt(
"%s/profiles/per-user/%s",
settings.nixStateDir,
getUserName()
);
if (pathExists(homeStateProfiles)) {
if (!isLink(homeStateProfiles)) {
migrateProfiles(homeStateProfiles, nixVarProfiles);
// The XDG-style profiles put the default profile *outside* of the directory
// with the rest of the profile symlinks.
replaceSymlink(nixVarProfiles + "/profile", localNixStateDir + "/profile");
}
} else {
replaceSymlink(nixVarProfiles, homeStateProfiles);
}
return nixVarProfiles;
}
Path profilesDir()
{
auto profileRoot =
(getuid() == 0)
? rootProfilesDir()
: createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
try {
return ensureProfilesDir();
} catch (Error const & e) {
printInfo("ignoring error initializing user profiles: %s", e.what());
return fmt("%s/profiles/per-user/%s", settings.nixStateDir, getUserName());
}
}
Path rootProfilesDir()
@ -320,25 +400,10 @@ Path rootProfilesDir()
return settings.nixStateDir + "/profiles/per-user/root";
}
Path getDefaultProfile()
{
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try {
auto profile = profilesDir() + "/profile";
if (!pathExists(profileLink)) {
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;
}
return profilesDir() + "/profile";
Path userProfiles = profilesDir();
}
Path defaultChannelsDir()

View file

@ -859,6 +859,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

@ -240,11 +240,15 @@ std::optional<Path> getSelfExe();
/**
* @return $XDG_STATE_HOME or $HOME/.local/state.
*
* @note Completely unrelated to settings.nixStateDir.
*/
Path getStateDir();
/**
* Create the Nix state directory and return the path to it.
*
* @note Completely unrelated to settings.nixStateDir
*/
Path createNixStateDir();

View file

@ -357,6 +357,14 @@ 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 +374,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);