I remember trying to follow through a few home-manager issues couple months ago, but I eventually gave up on trying to get nushell working as login shell… (hold up) However, the workaround I chose to go with is changing the shell startup command for terminal emulator so that nushell can inherit from my working zsh config (/run/current-system/sw/bin/zsh -l -c nu) ![]()
Also, as mentioned, while command-not-found doesn’t, nix-index does support nushell.
Since nushell doesn’t support global configuration it’s really difficult to do any declarative configuration without home-manager, but something like this would work:
# configuration.nix
{ pkgs, ... }: {
programs.command-not-found.enable = false;
environment.etc."nu/profile.d/command-not-found.nu".source =
"${pkgs.nix-index}/etc/profile.d/command-not-found.nu";
}
# config.nu
# Side note, you should not be using `env.nu`, use `config.nu`:
# https://www.nushell.sh/book/configuration.html#configuration-overview
$env.config.hooks.command_not_found = source /etc/nu/profile.d/command-not-found.nu
You’ll also have to generate the database, or use their pre-generated ones: GitHub - nix-community/nix-index: Quickly locate nix packages with specific files [maintainers=@bennofs @figsoda @raitobezarius]
It’s also currently broken, I’m unsure if this applies to stable or not: `command-not-found.nu`: unexpected argument `--top-level` found · Issue #275 · nix-community/nix-index · GitHub
See also here for a more generic version of that, and the post in general for an exploration of the alternative and why you probably want to use a surrogate shell even if you could theoretically use nu as a login shell.
@TLATER I agree that having a POSIX-compliant login shell is best even when using nushell for the reasons you describe—I use dash for this as follows:
users.defaultUserShell = pkgs.dash;
environment.sessionVariables.ENV =
pkgs.writeShellScript "dashInit"
''
if [ -z "$DISPLAY" ] && [ "$XDG_VTNR" = 1 ]; then
${pkgs.systemd}/bin/systemctl start --user hyprland;
elif ! [ "$TERM" = "dumb" ] && ! [ "$DASH_STARTED" = 1 ]; then
DASH_STARTED=1; export DASH_STARTED;
exec ${pkgs.nushell}/bin/nu;
fi
'';
(the first conditional is to automatically start my Wayland compositor on first login, so that I don’t need to use a greeter).
However, I think there is a slightly more robust way of setting environment variables in nu than the manual method you describe; I just do
home-manager.sharedModules = [{
programs.nushell.extraConfig = ''
if ($env.__NIXOS_SET_ENVIRONMENT_DONE? != "1") {
${pkgs.bash-env-json}/bin/bash-env-json /etc/set-environment | from json | get env | load-env;
}
'';
}];
Unfortunately, since this is parsing something into json it adds a significant amount of time to load (on my laptop nushell with this hack takes close to 100ms to load initially, whereas after __NIXOS_SET_ENVIRONMENT_DONE is set it can load in under 20ms).
That’s an interesting approach, but I don’t think it’s more robust.
If anything it’s less robust - the environment captured does not match the environment of a login shell perfectly, let alone one that passes through a display manager. If you intend for nushell to be a “login” shell it will be missing some variables if you use that approach exclusively. I think you’re being saved by the fact that you use a surrogate login shell, and that calling that script barely does anything for you.
I’m curious, honestly, try comparing your environment before and after you execute that script and see what the differences actually are. I’d expect there to be very few, and most of them would probably be wrong.
Although, the fact that you have indirection via systemd without something like uwsm to carefully manage the session probably means your environment is broken. Perhaps that’s why you think this adds robustness.
POSIX shells feature the export builtin precisely to make environment inheritance work correctly. The most robust way to make this work is to use the intended mechanism, which you can only do by using a POSIX shell as your login shell. Nushell will inherit the environment exactly as it should from there, there is no need to run another intermediate shell afterwards. If there is, that means you’ve made a mistake somewhere else and are taping around it with this hack.
As for performance, of course it’s slow - I don’t think the issue is the JSON parsing as much as it is the fact that you’re building a massive JSON object in pure bash. Bash isn’t very fast at operations like this.
It’d be pretty easy to implement a much more performant hack in an actual programming language (in fact, you can probably just parse the output of the POSIX printenv command), and then to invoke that with bash -lc or something - but this wouldn’t solve the fundamental problem that this is the wrong way to inherit session variables.
I’m curious, honestly, try comparing your environment before and after you execute that script and see what the differences actually are. I’d expect there to be very few, and most of them would probably be wrong.
It is actually significantly different. For example, before running that script /run/wrappers/bin is not added to my path so sudo doesn’t work. There are several other things as well: notably environment.variables does not work and NIX_XDG_DESKTOP_PORTAL_DIR and LOCALE_ARCHIVE are set differently.
Although, the fact that you have indirection via systemd without something like uwsm to carefully manage the session probably means your environment is broken. Perhaps that’s why you think this adds robustness.
Yes, this is a terrible hack and I am not proud of it (to be fair, I wrote it before uwsm was a thing). I agree that uwsm is a much better idea, and I plan to switch over when I have the time.
POSIX shells feature the
exportbuiltin precisely to make environment inheritance work correctly. The most robust way to make this work is to use the intended mechanism, which you can only do by using a POSIX shell as your login shell. Nushell will inherit the environment exactly as it should from there, there is no need to run another intermediate shell afterwards. If there is, that means you’ve made a mistake somewhere else and are taping around it with this hack.
My hack doesn’t seem to be necessary with bash and zsh, so I think my problem here might be that dash isn’t wired up to set any of the nixos-managed environment variables.
EDIT: adding the following to my environment.sessionVariables.ENV script fixed the problem and allowed me to remove the nushell hack:
if [ -z "$__ETC_PROFILE_SOURCED" ]; then
. /etc/profile
fi
Yeah, this would be unnecessary if you didn’t have that indirection from systemd. From the dash man page:
When first starting, the shell inspects argument 0, and if it begins with a dash ‘-’, the shell is also considered a login shell. This is normally done automatically by the system when the user first logs in. A login shell first reads commands from the files /etc/profile and .profile if they exist.
I.e., your problem is that you’re interrupting the environment inheritance between your actual login shell and your graphical session, which will cascade into all kinds of little bugs. You should get around to fixing that.
This is an excellent demonstration of what you’d get when using nushell as a login shell without an intermediate POSIX shell, or all that maintenance I was talking about earlier in the thread, by the way ![]()
Your bash/zsh rcfiles presumably end up sourcing /etc/profile for one reason or another down the line, which is why they don’t need that treatment, but that is arguably also a mistake as the profile scripts aren’t guaranteed to be idempotent, so it will execute some things twice (or more…) that should only run once, as well as make your init take longer than it needs to on every shell invocation. It may also be entirely missing in non-interactive shells!
Anyway, messy init script setup is well outside the scope of this thread, feel free to open another if you need help untangling this ![]()