From 460b87cc488910661771ee93dc052fa1644c0523 Mon Sep 17 00:00:00 2001 From: Lisa Scheers Date: Thu, 16 Jul 2026 06:06:02 +0200 Subject: [PATCH] nushell: load session environment at runtime Source the OS and Home Manager POSIX environment scripts when Nushell starts so runtime HOME and USER expansions and profile paths match managed POSIX shells. --- docs/release-notes/rl-2611.md | 7 ++ modules/programs/nushell.nix | 100 +++++++++++++++++- tests/modules/programs/nushell/default.nix | 1 + .../modules/programs/nushell/env-expected.nu | 2 +- .../programs/nushell/example-settings.nix | 6 +- .../programs/nushell/session-variables.nix | 80 ++++++++++++++ 6 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 tests/modules/programs/nushell/session-variables.nix diff --git a/docs/release-notes/rl-2611.md b/docs/release-notes/rl-2611.md index a79d40d95992..68f35efcb7c5 100644 --- a/docs/release-notes/rl-2611.md +++ b/docs/release-notes/rl-2611.md @@ -20,6 +20,13 @@ This release has the following notable changes: domain to `user` for agents that should run without an active graphical login session. +- [](#opt-programs.nushell.enable) now loads [](#opt-home.sessionVariables) + when Nushell starts directly. When Home Manager is used as a NixOS or + nix-darwin module, it also loads the system environment. This makes its + environment consistent with managed Bash and Zsh sessions. If you manage + Nushell's `env.nu` independently, migrate its contents to + [](#opt-programs.nushell.envFile) or [](#opt-programs.nushell.extraEnv). + ## State Version Changes {#sec-release-26.11-state-version-changes} The state version in this release includes the changes below. These diff --git a/modules/programs/nushell.nix b/modules/programs/nushell.nix index 21e9045c51cb..b03ba106f299 100644 --- a/modules/programs/nushell.nix +++ b/modules/programs/nushell.nix @@ -1,6 +1,7 @@ { config, lib, + osConfig, pkgs, ... }: @@ -9,6 +10,102 @@ let inherit (lib.hm.nushell) isNushellInline toNushell; cfg = config.programs.nushell; + systemEnvironmentPath = [ + "system" + "build" + "setEnvironment" + ]; + effectiveOsConfig = if osConfig == null then { } else osConfig; + getSystemEnvironment = lib.attrByPath systemEnvironmentPath null; + systemEnvironmentFile = getSystemEnvironment effectiveOsConfig; + hasSystemEnvironment = systemEnvironmentFile != null; + sessionVariablesPackage = toString config.home.sessionVariablesPackage; + sessionVariablesFile = "/etc/profile.d/hm-session-vars.sh"; + homeSessionVariablesFile = sessionVariablesPackage + sessionVariablesFile; + hasSessionEnvironment = + systemEnvironmentFile != null + || config.home.sessionVariables != { } + || config.home.sessionSearchVariables != { } + || config.home.sessionVariablesExtra != ""; + hasUserEnvFile = cfg.envFile != null || cfg.extraEnv != ""; + writeEnvFile = hasSessionEnvironment || hasUserEnvFile; + + # Translate the POSIX session scripts at shell startup so references to + # runtime values such as HOME and USER are expanded in the user environment. + sessionVariablesLoader = + let + sourceSystemEnvironment = lib.optionalString hasSystemEnvironment '' + if [ -z "''${__NIXOS_SET_ENVIRONMENT_DONE-}" ] \ + && [ -z "''${__NIX_DARWIN_SET_ENVIRONMENT_DONE-}" ]; then + . ${lib.escapeShellArg systemEnvironmentFile} >/dev/null + fi + ''; + captureEnvironment = '' + ${lib.getExe' pkgs.coreutils "env"} -0 + printf '\0' + ${sourceSystemEnvironment} + . ${lib.escapeShellArg homeSessionVariablesFile} >/dev/null + ${lib.getExe' pkgs.coreutils "env"} -0 + ''; + shouldLoadSystemEnvironment = lib.optionalString hasSystemEnvironment '' + or ( + "__NIXOS_SET_ENVIRONMENT_DONE" not-in $env + and "__NIX_DARWIN_SET_ENVIRONMENT_DONE" not-in $env + ) + ''; + in + pkgs.writeText "hm-session-vars.nu" '' + if ( + "__HM_SESS_VARS_SOURCED" not-in $env + ${shouldLoadSystemEnvironment} + ) { + let captured = ( + ^${pkgs.runtimeShell} -c ${toNushell { } captureEnvironment} + | complete + ) + + if $captured.exit_code != 0 { + error make { + msg: "failed to load the Home Manager session environment" + help: $captured.stderr + } + } + + let separator = $"(char nul)(char nul)" + let sections = ($captured.stdout | split row $separator) + + if ($sections | length) != 2 { + error make { + msg: "failed to parse the Home Manager session environment" + } + } + + let before = ( + $sections + | first + | split row (char nul) + | compact --empty + ) + let changed = ( + $sections + | last + | split row (char nul) + | compact --empty + | where { |entry| $entry not-in $before } + | parse --regex '(?s)^(?[^=]+)=(?.*)$' + | where { |entry| $entry.name not-in ["_" "_AST_FEATURES" "SHLVL"] } + | transpose --header-row --as-record + ) + let changed = if "PATH" in $changed { + $changed | update PATH ($changed.PATH | split row (char esep)) + } else { + $changed + } + + load-env $changed + } + ''; + linesOrSource = name: types.submodule ( @@ -279,8 +376,9 @@ in } ) - (lib.mkIf (cfg.envFile != null || cfg.extraEnv != "") { + (lib.mkIf writeEnvFile { "${cfg.configDir}/env.nu".text = lib.mkMerge [ + (lib.mkIf hasSessionEnvironment "source ${sessionVariablesLoader}") (lib.mkIf (cfg.envFile != null) cfg.envFile.text) cfg.extraEnv ]; diff --git a/tests/modules/programs/nushell/default.nix b/tests/modules/programs/nushell/default.nix index 101006533054..7640d4d16cf1 100644 --- a/tests/modules/programs/nushell/default.nix +++ b/tests/modules/programs/nushell/default.nix @@ -1,4 +1,5 @@ { nushell-example-settings = ./example-settings.nix; nushell-config-dir = ./config-dir.nix; + nushell-session-variables = ./session-variables.nix; } diff --git a/tests/modules/programs/nushell/env-expected.nu b/tests/modules/programs/nushell/env-expected.nu index aacc379259aa..0760e0234bb5 100644 --- a/tests/modules/programs/nushell/env-expected.nu +++ b/tests/modules/programs/nushell/env-expected.nu @@ -1,2 +1,2 @@ +source /nix/store/00000000000000000000000000000000-hm-session-vars.nu $env.FOO = 'BAR' - diff --git a/tests/modules/programs/nushell/example-settings.nix b/tests/modules/programs/nushell/example-settings.nix index 1c8f3209ec79..d1c1f5dce992 100644 --- a/tests/modules/programs/nushell/example-settings.nix +++ b/tests/modules/programs/nushell/example-settings.nix @@ -19,9 +19,7 @@ } ''; - envFile.text = '' - $env.FOO = 'BAR' - ''; + envFile.text = "$env.FOO = 'BAR'"; loginFile.text = '' # Prints "Hello, World" upon logging into tty1 @@ -75,7 +73,7 @@ "${configDir}/config.nu" \ ${./config-expected.nu} assertFileContent \ - "${configDir}/env.nu" \ + "$(normalizeStorePaths "${configDir}/env.nu")" \ ${./env-expected.nu} assertFileContent \ "${configDir}/login.nu" \ diff --git a/tests/modules/programs/nushell/session-variables.nix b/tests/modules/programs/nushell/session-variables.nix new file mode 100644 index 000000000000..e9824c41e009 --- /dev/null +++ b/tests/modules/programs/nushell/session-variables.nix @@ -0,0 +1,80 @@ +{ + config, + lib, + pkgs, + realPkgs, + ... +}: + +let + systemEnvironment = realPkgs.writeText "set-environment" '' + export __NIXOS_SET_ENVIRONMENT_DONE=1 + export SYSTEM_VALUE="$HOME/$USER" + export PATH="/system/bin:$PATH" + ''; + expected = pkgs.writeText "expected-session-variables" '' + /runtime/home/runtime-user + /runtime/home/.config + fallback + home-is-set + /runtime/home/.local/bin:/system/bin + env-file-loaded + ''; +in +{ + _module.args.osConfig.system.build.setEnvironment = systemEnvironment; + + nixpkgs.overlays = [ + (_final: _prev: { runtimeShell = "/bin/sh"; }) + ]; + + home = { + sessionPath = [ "$HOME/.local/bin" ]; + sessionVariables = { + ALTERNATE_VALUE = "\${HOME:+home-is-set}"; + DEFAULT_VALUE = "\${UNSET_VARIABLE:-fallback}"; + EXPANDED_HOME = "$HOME/.config"; + }; + }; + + programs.nushell = { + enable = true; + package = realPkgs.nushell; + envFile.text = '' + $env.USER_ENV_FILE = "env-file-loaded" + ''; + }; + + nmt.script = + let + homePrefix = "${config.home.homeDirectory}/"; + configDir = lib.removePrefix homePrefix config.programs.nushell.configDir; + envFile = "home-files/${configDir}/env.nu"; + in + '' + assertFileRegex ${envFile} \ + '^source /nix/store/[^/]*-hm-session-vars.nu$' + + actual="$PWD/actual" + env -i \ + HOME=/runtime/home \ + USER=runtime-user \ + PATH=/initial/bin \ + ${lib.getExe realPkgs.nushell} \ + --no-history \ + --config /dev/null \ + --env-config "$(_abs ${envFile})" \ + --commands ' + print ([ + $env.SYSTEM_VALUE + $env.EXPANDED_HOME + $env.DEFAULT_VALUE + $env.ALTERNATE_VALUE + ($env.PATH | first 2 | str join (char esep)) + $env.USER_ENV_FILE + ] | str join (char newline)) + ' > "$actual" + + assertFileContent "$actual" ${expected} + ''; +}