feat: enable use-xdg-base-directories by default #796

Open
opened 2025-04-09 16:31:40 +00:00 by kfearsoff · 4 comments
Member

XDG Base Directory support has been stalled in CppNix for years. Let's see what is left to do, and fix it.

The setting is currently used in a few places: nix-channel, nix-env, eval settings, profiles. Eval setting is also used in default NIX_PATH, nix-channel and nix-env.

This internal API is error-prone: it expects the user to handle the migration manually, which can come with many caveats. We need to carefully handle this in a centralized manner.

Centralizing User Files

First, we need to move getNixDefExpr from eval settings to libutil/users.cc, which is already used by getNixDefExpr. Similar getNixChannels and getNixProfile functions should be created there, and should be used in previous places where useXDGBaseDirectory was mentioned explicitly.

Then, the functionality of getNixDefExpr and similar functions should be refactored. Right now, getNixDefExpr is a simple conditional on useXDGBaseDirectory. We need more:

  1. Check useXDGBaseDirectory; write relevant subpaths (depending on the function) to usedPath and possibleUnusedPath variables
  2. Check if usedPath exists on disk and write the result to a variable
  3. Check if possibleUnusedPath exists on disk and write the result to a variable
  4. Do a match on usedPath and possibleUnusedPath:
    5. If usedPath doesn't exist and possibleUnusedPath also doesn't exist: call createNixStateDir with relevant subpath
    6. If usedPath doesn't exist and possibleUnusedPath exists: move possibleUnusedPath to usedPath. This is the migration path
    7. If usedPath exists and possibleUnusedPath doesn't exist: do nothing. This is backwards compat flow
    8. If usedPath exists and possibleUnusedPath also exists: TBD, also backwards compat flow
  5. Return usedPath

Migration Path and Backwards Compat

There are a few options here. What's described above is remove old, use new option.

A potentially better flow is symlink old to new option. This would write a symlink to old location that points to new location. It needs testing to see if this option works for the ecosystem. I suggest starting with this one and testing the ecosystem. The major benefit, aside from potentially better backwards compat, is that it might support Lix downgrade operations. This, too, needs to be tested.

The least preferred flow is keep both. This will potentially break UX in awful ways when doing downgrades or using some of the ecosystem options.

Known Issues

There are several things that are already reported to be broken with use-xdg-base-directories.

nix-build is busted. More accurately, this seems to be a NIX_PATH issue, seems to originate here.

fish completions are busted - seems to be resolved.

nix-collect-garbage issues - not actually related.

HM issues: the HM logic is faulty in those lines. First logic branch applies when HM is a NixOS/nix-darwin module, second if Nix impl is managed with HM. If Nix is not managed with HM, then HM defaults to legacy path and doesn't consider XDG paths at all, which is a bug.

Testing

This is a large change that needs extensive testing. There are several use cases that need to be checked on VMs. Like mentioned above, symlink old to new approach is ideal; so this approach should be taken first and discarded if related tests fail and it's a major issue:

  • Generic Linux, use-xdg-base-directories false -> true
  • Generic Linux, use-xdg-base-directories true -> false
  • Generic Linux, use-xdg-base-directories true on a patched Lix version -> downgrade to non-patched and verify that it works
  • Generic Linux, use-xdg-base-directories false on a patched Lix version -> downgrade to non-patched and verify that it works
  • (symlink old to new) Generic Linux, use-xdg-base-directories true on a patched Lix version -> downgrade to non-patched and verify that it works
  • (symlink old to new) Generic Linux, use-xdg-base-directories true -> see if HM standalone works
  • Generic Linux, use-xdg-base-directories true -> see if nix-build works
  • Generic Linux, use-xdg-base-directories true -> see if Hydra and lix-eval-jobs work
  • Generic Linux, use-xdg-base-directories true -> see if packages that use nixForLinking work
  • Generic Linux, use-xdg-base-directories true -> see if packages that can be built with Lix dependency work
  • NixOS, use-xdg-base-directories true -> see if commands like nixos-rebuild, nix-env, nix-channel work
  • (symlink old to new) NixOS, use-xdg-base-directories true -> downgrade to non-patched and see if commands like nixos-rebuild, nix-env, nix-channel work
XDG Base Directory support has been stalled in CppNix for years. Let's see what is left to do, and fix it. The setting is currently used in a few places: [nix-channel](https://git.lix.systems/lix-project/lix/src/branch/main/lix/legacy/nix-channel.cc#L182), [nix-env](https://git.lix.systems/lix-project/lix/src/branch/main/lix/legacy/nix-env.cc#L1335), [eval settings](https://git.lix.systems/lix-project/lix/src/branch/main/lix/libexpr/eval-settings.cc#L102), [profiles](https://git.lix.systems/lix-project/lix/src/branch/main/lix/libstore/profiles.cc#L330). Eval setting is also used in [default NIX_PATH](https://git.lix.systems/lix-project/lix/src/branch/main/lix/libexpr/eval-settings.cc#L66), [nix-channel](https://git.lix.systems/lix-project/lix/src/branch/main/lix/legacy/nix-channel.cc#L183) and [nix-env](https://git.lix.systems/lix-project/lix/src/branch/main/lix/legacy/nix-env.cc#L1442). This internal API is error-prone: it expects the user to handle the migration manually, which can come with many caveats. We need to carefully handle this in a centralized manner. ## Centralizing User Files First, we need to move `getNixDefExpr` from [eval settings](https://git.lix.systems/lix-project/lix/src/branch/main/lix/libexpr/eval-settings.cc#L102) to `libutil/users.cc`, which is [already used](https://git.lix.systems/lix-project/lix/src/commit/fb0ef6ca6bfea368e594e9ae2714858030f66bc6/lix/libutil/users.cc#L92-L96) by `getNixDefExpr`. Similar `getNixChannels` and `getNixProfile` functions should be created there, and should be used in previous places where `useXDGBaseDirectory` was mentioned explicitly. Then, the functionality of `getNixDefExpr` and similar functions should be refactored. Right now, `getNixDefExpr` is a simple conditional on `useXDGBaseDirectory`. We need more: 1. Check `useXDGBaseDirectory`; write relevant subpaths (depending on the function) to `usedPath` and `possibleUnusedPath` variables 2. Check if `usedPath` exists on disk and write the result to a variable 3. Check if `possibleUnusedPath` exists on disk and write the result to a variable 4. Do a match on `usedPath` and `possibleUnusedPath`: 5. If `usedPath` doesn't exist and `possibleUnusedPath` also doesn't exist: call `createNixStateDir` with relevant subpath 6. If `usedPath` doesn't exist and `possibleUnusedPath` exists: move `possibleUnusedPath` to `usedPath`. This is the *migration path* 7. If `usedPath` exists and `possibleUnusedPath` doesn't exist: do nothing. This is *backwards compat* flow 8. If `usedPath` exists and `possibleUnusedPath` also exists: TBD, also *backwards compat* flow 9. Return `usedPath` ## Migration Path and Backwards Compat There are a few options here. What's described above is *remove old, use new* option. A potentially better flow is *symlink old to new* option. This would write a symlink to old location that points to new location. It needs testing to see if this option works for the ecosystem. I suggest **starting with this one** and testing the ecosystem. The major benefit, aside from potentially better backwards compat, is that it *might* support Lix downgrade operations. This, too, needs to be tested. The least preferred flow is *keep both*. This will potentially break UX in awful ways when doing downgrades or using some of the ecosystem options. ## Known Issues There are several things that are already reported to be broken with `use-xdg-base-directories`. [nix-build is busted](https://github.com/NixOS/nix/issues/8580). More accurately, this seems to be a `NIX_PATH` issue, seems to originate [here](https://git.lix.systems/lix-project/lix/src/commit/9c2dba4ee2f54e54f3e8be89e06eeec54f62e31e/lix/libexpr/eval-settings.cc#L53-L71). [fish completions are busted](https://github.com/NixOS/nix/issues/8573) - seems to be resolved. [nix-collect-garbage issues](https://github.com/NixOS/nix/issues/8508) - not actually related. [HM issues](https://github.com/nix-community/home-manager/issues/2544#issuecomment-1507852884): the HM logic is faulty in [those lines](https://github.com/nix-community/home-manager/blob/542efdf2dfac351498f534eb71671525b9bd45ed/modules/home-environment.nix#L563-L569). First logic branch applies when HM is a NixOS/nix-darwin module, second if Nix impl is managed with HM. If Nix is not managed with HM, then HM defaults to legacy path and doesn't consider XDG paths at all, which is a bug. ## Testing This is a large change that needs extensive testing. There are several use cases that need to be checked on VMs. Like mentioned above, *symlink old to new* approach is ideal; so this approach should be taken first and discarded if related tests fail and it's a major issue: - Generic Linux, `use-xdg-base-directories` false -> true - Generic Linux, `use-xdg-base-directories` true -> false - Generic Linux, `use-xdg-base-directories` true on a patched Lix version -> downgrade to non-patched and verify that it works - Generic Linux, `use-xdg-base-directories` false on a patched Lix version -> downgrade to non-patched and verify that it works - (symlink old to new) Generic Linux, `use-xdg-base-directories` true on a patched Lix version -> downgrade to non-patched and verify that it works - (symlink old to new) Generic Linux, `use-xdg-base-directories` true -> see if HM standalone works - Generic Linux, `use-xdg-base-directories` true -> see if `nix-build` works - Generic Linux, `use-xdg-base-directories` true -> see if Hydra and lix-eval-jobs work - Generic Linux, `use-xdg-base-directories` true -> see if [packages that use nixForLinking](https://github.com/NixOS/nixpkgs/pull/384099/files) work - Generic Linux, `use-xdg-base-directories` true -> see if packages that can be built with Lix dependency work - NixOS, `use-xdg-base-directories` true -> see if commands like `nixos-rebuild`, `nix-env`, `nix-channel` work - (symlink old to new) NixOS, `use-xdg-base-directories` true -> downgrade to non-patched and see if commands like `nixos-rebuild`, `nix-env`, `nix-channel` work
Author
Member

Discussed in Matrix: supposedly, this flag also screws up profiles by changing symlink structure from ~/.nix-profile -> /nix/var/nix/profiles/per-user/foo/bar -> /nix/store/... to ~/.local/share/nix/something -> /nix/store/...

Also, while checking each other's setups, we might have found a lot of differences in when and how the setup is done...

Sigh, the list of things to do grows ever larger. Things to check:

  1. CppNix 2.3 installation
  2. CppNix 2.4 installation
  3. CppNix 2.18 installation
  4. CppNix latest installation
  5. DetSys installation of CppNix (non-DetSys version, latest)
  6. Lix installation

Additionally, the feature matrix between (nix-env or nix profile, non-XDG or XDG) symlink trees needs checking.

Need to also investigate on what this CL is about: https://gerrit.lix.systems/c/lix/+/1101

Discussed in Matrix: supposedly, this flag also screws up profiles by changing symlink structure from `~/.nix-profile -> /nix/var/nix/profiles/per-user/foo/bar -> /nix/store/...` to `~/.local/share/nix/something -> /nix/store/...` Also, while checking each other's setups, we might have found a lot of differences in when and how the setup is done... Sigh, the list of things to do grows ever larger. Things to check: 1. CppNix 2.3 installation 2. CppNix 2.4 installation 3. CppNix 2.18 installation 4. CppNix latest installation 5. DetSys installation of CppNix (non-DetSys version, latest) 6. Lix installation Additionally, the feature matrix between (`nix-env` or `nix profile`, non-XDG or XDG) symlink trees needs checking. Need to also investigate on what this CL is about: https://gerrit.lix.systems/c/lix/+/1101
Author
Member

Okay, totally unscientific research here is done. Methodology:

  1. Provision an Ubuntu-24.04 Server VM, install the OS, shut down
  2. Clone VM with disk to a new VM+disk with descriptive name
  3. Boot up a clone
  4. wget https://releases.nixos.org/nix/nix-<version>/install && chmod +x install && ./install --daemon for script installs
  5. Look at results
  6. Relog into user and look at results
  7. Restart the daemon and look at results

I used "vanilla releases" (ending on .0, like 2.3.0), because keeping up with minor patches is too much of a pain. Results:

  • CppNix 2.3: sets up /root/.nix-channels and /root/.nix-defexpr, /root/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/root/channels, nothing in user homedir. Relog sets up ~/.nix-defexpr/channels_root -> /nix/var/nix/profiles/per-user/root/channels and ~/.nix-profile -> /nix/var/nix/profiles/per-user/user/profile. Trying to run nix-env gives error error: opening lock file '/nix/var/nix/db/big-lock': Permission denied. This isn't fixed with trusted-users or reboot, lol
  • CppNix 2.3.13 (because plain 2.3 is broken): same, but /root/.nix-profile-> /nix/var/nix/profiles/default. Relog also sets up ~/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/user/channels, but homedir setup requires reboot, and running nix-env gives error error: could not set permissions on '/nix/var/nix/profiles/per-user' to 755: Operation not permitted. And it is actually 755 already, just owned by root:root. Gross.
  • CppNix 2.4 is exactly the same
  • CppNix 2.11 (arbitrary version to test 2.4-like before 2.14): same setup like before, but homedir is not set up with login scripts. Instead, running nix-env -iA nixpkgs.hello sets everything up. Channels are a bit weird: it sets up ~/.nix-defexpr/root_channel but ~/.nix-defexpr/channels exists and point to /nix/var/nix/profiles/per-user/user/channels (which doesn't exist). This should resolve itself when running nix-channel --add, though.
  • CppNix 2.18: same setup like before, but now ~/.nix-profile -> ~/.local/state/nix/profiles/profile -> ~/.local/state/nix/profiles/profile-1-link -> /nix/store/...-user-environment. Bizarrely enough, /root/.nix-profile -> /nix/var/nix/profiles/per-user/root/profile, with /nix/var/nix/profiles/default also pointing to it
  • CppNix latest: identical to CppNix 2.18
  • DetSys CppNix latest: identical to CppNix 2.18, except it doesn't set up root channels? sudo nix-channel gives "no command found"? wtf? Reboot doesn't fix it
  • Lix latest: identical to DetSys CppNix

I'll write a summary of what this means in practice later in a separate comment, because this drained life out of me.

Okay, totally unscientific research here is done. Methodology: 1. Provision an Ubuntu-24.04 Server VM, install the OS, shut down 2. Clone VM with disk to a new VM+disk with descriptive name 3. Boot up a clone 4. `wget https://releases.nixos.org/nix/nix-<version>/install && chmod +x install && ./install --daemon` for script installs 5. Look at results 6. Relog into user and look at results 7. Restart the daemon and look at results I used "vanilla releases" (ending on `.0`, like `2.3.0`), because keeping up with minor patches is too much of a pain. Results: - CppNix 2.3: sets up `/root/.nix-channels` and `/root/.nix-defexpr`, `/root/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/root/channels`, nothing in user homedir. Relog sets up `~/.nix-defexpr/channels_root -> /nix/var/nix/profiles/per-user/root/channels` and `~/.nix-profile -> /nix/var/nix/profiles/per-user/user/profile`. Trying to run `nix-env` gives error `error: opening lock file '/nix/var/nix/db/big-lock': Permission denied`. This isn't fixed with `trusted-users` or reboot, lol - CppNix 2.3.13 (because plain 2.3 is broken): same, but `/root/.nix-profile-> /nix/var/nix/profiles/default`. Relog also sets up `~/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/user/channels`, but homedir setup requires **reboot**, and running `nix-env` gives error `error: could not set permissions on '/nix/var/nix/profiles/per-user' to 755: Operation not permitted`. And it is actually 755 already, just owned by `root:root`. Gross. - CppNix 2.4 is exactly the same - CppNix 2.11 (arbitrary version to test 2.4-like before 2.14): same setup like before, but homedir is not set up with login scripts. Instead, running `nix-env -iA nixpkgs.hello` sets everything up. Channels are a bit weird: it sets up `~/.nix-defexpr/root_channel` but `~/.nix-defexpr/channels` exists and point to `/nix/var/nix/profiles/per-user/user/channels` (which doesn't exist). This should resolve itself when running `nix-channel --add`, though. - CppNix 2.18: same setup like before, but now `~/.nix-profile -> ~/.local/state/nix/profiles/profile -> ~/.local/state/nix/profiles/profile-1-link -> /nix/store/...-user-environment`. Bizarrely enough, `/root/.nix-profile` -> `/nix/var/nix/profiles/per-user/root/profile`, with `/nix/var/nix/profiles/default` also pointing to it - CppNix latest: identical to CppNix 2.18 - DetSys CppNix latest: identical to CppNix 2.18, except it doesn't set up root channels? `sudo nix-channel` gives "no command found"? wtf? Reboot doesn't fix it - Lix latest: identical to DetSys CppNix I'll write a summary of what this means in practice later in a separate comment, because this drained life out of me.
Author
Member

Pre-2.14 layout looks like this:

  • /root/.nix-channels
  • /root/.nix-defexpr (directory)
    • /root/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/root/channels
  • /root/.nix-profile -> /nix/var/nix/profiles/default -> /nix/var/nix/profiles/per-user/root/profile
  • ~/.nix-defexpr (directory)
    • ~/.nix-defexpr/channels_root -> /nix/var/nix/profiles/per-user/root/channels
    • ~/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/user/channels
  • ~/.nix-profile -> /nix/var/nix/profiles/per-user/user/profile -> /nix/var/nix/profiles/per-user/user/profile-1 -> /nix/store/...-user-environment

Post-2.14:

  • /root/.nix-channels
  • /root/.nix-defexpr (directory)
    • /root/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/root/channels
  • /root/.nix-profile -> /nix/var/nix/profiles/default -> /nix/var/nix/profiles/per-user/root/profile
  • ~/.nix-defexpr (directory)
    • ~/.nix-defexpr/channels_root -> /nix/var/nix/profiles/per-user/root/channels
    • ~/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/user/channels
  • ~/.nix-profile -> ~/.local/state/nix/profiles/profile -> ~/.local/state/nix/profiles/profile-1 -> /nix/store/...-user-environment

To revert 2.14 change (@qyriad CL at https://gerrit.lix.systems/c/lix/+/1101 , CppNix PR: https://github.com/NixOS/nix/pull/5226 ), we need to change daemon stuff, probably nix-collect-garbage, and we need to do something equivalent to mkdir -p /nix/var/nix/profiles/per-user/user && cp ~/.local/state/nix/profiles/* /nix/var/nix/profiles/per-user/user/ && ln -s /nix/var/nix/profiles/per-user/user/profile ~/.nix-profile when running commands.

Additional consideration: we need to figure out root channels, because modern installers don't even set them up. Some middle ground needs to be found, where it's impossible to footgun yourself with rootless/rootful nix-channel usage, yet other stuff doesn't break.

After that, we can actually get to doing this issue, which works as documented.

Pre-2.14 layout looks like this: - `/root/.nix-channels` - `/root/.nix-defexpr` (directory) - `/root/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/root/channels` - `/root/.nix-profile -> /nix/var/nix/profiles/default -> /nix/var/nix/profiles/per-user/root/profile` - `~/.nix-defexpr` (directory) - `~/.nix-defexpr/channels_root -> /nix/var/nix/profiles/per-user/root/channels` - `~/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/user/channels` - `~/.nix-profile -> /nix/var/nix/profiles/per-user/user/profile -> /nix/var/nix/profiles/per-user/user/profile-1 -> /nix/store/...-user-environment` Post-2.14: - `/root/.nix-channels` - `/root/.nix-defexpr` (directory) - `/root/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/root/channels` - `/root/.nix-profile -> /nix/var/nix/profiles/default -> /nix/var/nix/profiles/per-user/root/profile` - `~/.nix-defexpr` (directory) - `~/.nix-defexpr/channels_root -> /nix/var/nix/profiles/per-user/root/channels` - `~/.nix-defexpr/channels -> /nix/var/nix/profiles/per-user/user/channels` - `~/.nix-profile -> ~/.local/state/nix/profiles/profile -> ~/.local/state/nix/profiles/profile-1 -> /nix/store/...-user-environment` To revert 2.14 change (@qyriad CL at https://gerrit.lix.systems/c/lix/+/1101 , CppNix PR: https://github.com/NixOS/nix/pull/5226 ), we need to change daemon stuff, probably `nix-collect-garbage`, and we need to do something equivalent to `mkdir -p /nix/var/nix/profiles/per-user/user && cp ~/.local/state/nix/profiles/* /nix/var/nix/profiles/per-user/user/ && ln -s /nix/var/nix/profiles/per-user/user/profile ~/.nix-profile` when running commands. Additional consideration: we need to figure out root channels, because modern installers don't even set them up. Some middle ground needs to be found, where it's impossible to footgun yourself with rootless/rootful `nix-channel` usage, yet other stuff doesn't break. After that, we can actually get to doing this issue, which works as documented.
Author
Member

Related: #151 #215

Related: #151 #215
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lix-project/lix#796
No description provided.