Files
home-manager/modules/programs/nushell.nix
Lisa Scheers 6750249c69 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.
2026-10-22 16:05:41 -05:00

412 lines
13 KiB
Nix

{
config,
lib,
osConfig,
pkgs,
...
}:
let
inherit (lib) types;
inherit (lib.hm.nushell) isNushellInline toNushell;
cfg = config.programs.nushell;
systemEnvironmentFile = osConfig.system.build.setEnvironment or null;
hasSystemEnvironment = systemEnvironmentFile != null;
homeSessionVariablesFile = "${config.home.sessionVariablesPackage}/etc/profile.d/hm-session-vars.sh";
# 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
'';
# A script avoids Bash's SSH-specific bashrc loading for `bash -c`.
captureEnvironment = pkgs.writeShellScript "hm-session-vars-capture" ''
${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 (
($env.__NIXOS_SET_ENVIRONMENT_DONE? | is-empty)
and ($env.__NIX_DARWIN_SET_ENVIRONMENT_DONE? | is-empty)
)
'';
in
pkgs.writeText "hm-session-vars.nu" ''
if (
($env.__HM_SESS_VARS_SOURCED? | is-empty)
${shouldLoadSystemEnvironment}
) {
let sections = (
^${captureEnvironment}
| split row $"(char nul)(char nul)"
| each {
split row (char nul)
| parse --regex '(?s)^(?<name>[^=]+)=(?<value>.*)$'
| transpose --header-row --as-record
}
)
let before = $sections.0
let after = $sections.1
let ignored = ["_" "_AST_FEATURES" "SHLVL"]
let changed = ($after | items { |name, value|
if $name not-in $ignored and (
$name not-in $before or ($before | get $name) != $value
) {
[$name $value]
}
} | compact | into record)
load-env $changed
if "PATH" in $changed {
$env.PATH = $changed.PATH | split row (char esep)
}
for name in ($before | columns) {
if $name not-in $after and $name not-in $ignored {
hide-env --ignore-errors $name
}
}
}
'';
linesOrSource =
name:
types.submodule (
{ config, ... }:
{
options = {
text = lib.mkOption {
type = types.lines;
default = if config.source != null then builtins.readFile config.source else "";
defaultText = lib.literalExpression "if source is defined, the content of source, otherwise empty";
description = ''
Text of the nushell {file}`${name}` file.
If unset then the source option will be preferred.
'';
};
source = lib.mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path of the nushell {file}`${name}` file to use.
If the text option is set, it will be preferred.
'';
};
};
}
);
in
{
meta.maintainers = with lib.maintainers; [
joaquintrinanes
];
options.programs.nushell = {
enable = lib.mkEnableOption "nushell";
package = lib.mkPackageOption pkgs "nushell" { nullable = true; };
configDir = lib.mkOption {
type = types.either types.str types.path;
default =
if pkgs.stdenv.hostPlatform.isDarwin && !config.xdg.enable then
"Library/Application Support/nushell"
else
"${config.xdg.configHome}/nushell";
defaultText = lib.literalExpression ''
if pkgs.stdenv.hostPlatform.isDarwin && !config.xdg.enable then
"Library/Application Support/nushell"
else
"''${config.xdg.configHome}/nushell";
'';
description = ''
Location of the nushell config directory. This directory contains the {file}`config.nu`, {file}`env.nu`, and {file}`login.nu` files, as well as history and plugin files.
'';
};
configFile = lib.mkOption {
type = types.nullOr (linesOrSource "config.nu");
default = null;
example = lib.literalExpression ''
{
text = '''
const NU_LIB_DIRS = $NU_LIB_DIRS ++ ''${
lib.hm.nushell.toNushell (lib.concatStringsSep ":" [ ./scripts ])
}
$env.config.filesize_metric = false
$env.config.table_mode = 'rounded'
$env.config.use_ls_colors = true
''';
}
'';
description = ''
The configuration file to be used for nushell.
See <https://www.nushell.sh/book/configuration.html#configuration> for more information.
'';
};
envFile = lib.mkOption {
type = types.nullOr (linesOrSource "env.nu");
default = null;
example = ''
$env.FOO = 'BAR'
'';
description = ''
The environment variables file to be used for nushell.
See <https://www.nushell.sh/book/configuration.html#configuration> for more information.
'';
};
loginFile = lib.mkOption {
type = types.nullOr (linesOrSource "login.nu");
default = null;
example = ''
# Prints "Hello, World" upon logging into tty1
if (tty) == "/dev/tty1" {
echo "Hello, World"
}
'';
description = ''
The login file to be used for nushell upon logging in.
See <https://www.nushell.sh/book/configuration.html#configuring-nu-as-a-login-shell> for more information.
'';
};
extraConfig = lib.mkOption {
type = types.lines;
default = "";
description = ''
Additional configuration to add to the nushell configuration file.
'';
};
extraEnv = lib.mkOption {
type = types.lines;
default = "";
description = ''
Additional configuration to add to the nushell environment variables file.
'';
};
extraLogin = lib.mkOption {
type = types.lines;
default = "";
description = ''
Additional configuration to add to the nushell login file.
'';
};
plugins = lib.mkOption {
type = types.listOf types.package;
default = [ ];
example = lib.literalExpression "[ pkgs.nushellPlugins.formats ]";
description = ''
A list of nushell plugins to write to the plugin registry file.
'';
};
settings = lib.mkOption {
type = types.attrsOf lib.hm.types.nushellValue;
default = { };
example = {
show_banner = false;
history.format = "sqlite";
};
description = ''
Nushell settings. These will be flattened and assigned one by one to `$env.config` to avoid overwriting the default or existing options.
For example:
```nix
{
show_banner = false;
completions.external = {
enable = true;
max_results = 200;
};
}
```
becomes:
```nushell
$env.config.completions.external.enable = true
$env.config.completions.external.max_results = 200
$env.config.show_banner = false
```
'';
};
shellAliases = lib.mkOption {
type = types.attrsOf types.str;
default = { };
example = {
ll = "ls -l";
g = "git";
};
description = ''
An attribute set that maps aliases (the top level attribute names in
this option) to command strings or directly to build outputs.
'';
};
loadSessionVariables = lib.mkOption {
type = types.bool;
inherit
(lib.hm.deprecations.mkStateVersionOptionDefault {
inherit (config.home) stateVersion;
since = "26.11";
optionPath = [
"programs"
"nushell"
"loadSessionVariables"
];
legacy.value = false;
current.value = true;
})
default
defaultText
;
description = ''
Whether to load the Home Manager session environment, such as
{option}`home.sessionVariables` and {option}`home.sessionPath`, in
{file}`env.nu` when Nushell starts without it, for example as a login
shell. When Home Manager is used as a NixOS or nix-darwin module, the
system environment is loaded too.
Enabling this makes Home Manager manage {file}`env.nu`. Put other
environment configuration in {option}`programs.nushell.envFile` or
{option}`programs.nushell.extraEnv`.
'';
};
environmentVariables = lib.mkOption {
type = types.attrsOf lib.hm.types.nushellValue;
default = { };
example = lib.literalExpression ''
{
FOO = "BAR";
LIST_VALUE = [ "foo" "bar" ];
PROMPT_COMMAND = lib.hm.nushell.mkNushellInline '''{|| "> "}''';
ENV_CONVERSIONS.PATH = {
from_string = lib.hm.nushell.mkNushellInline "{|s| $s | split row (char esep) }";
to_string = lib.hm.nushell.mkNushellInline "{|v| $v | str join (char esep) }";
};
}
'';
description = ''
Environment variables to be set.
Inline values can be set with `lib.hm.nushell.mkNushellInline`.
'';
};
};
config = lib.mkIf cfg.enable {
warnings = lib.optional (cfg.package == null && cfg.plugins != [ ]) ''
You have configured `plugins` for `nushell` but have not set `package`.
The listed plugins will not be installed.
'';
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];
home.extraDependencies = cfg.plugins; # make sure the plugins are not garbage-collected
home.file = lib.mkMerge [
(
let
writeConfig =
cfg.configFile != null
|| cfg.extraConfig != ""
|| aliasesStr != ""
|| cfg.settings != { }
|| cfg.environmentVariables != { };
aliasesStr = lib.concatLines (
lib.mapAttrsToList (k: v: "alias ${toNushell { } k} = ${v}") cfg.shellAliases
);
in
lib.mkIf writeConfig {
"${cfg.configDir}/config.nu".text = lib.mkMerge [
(
let
hasEnvVars = cfg.environmentVariables != { };
envVarsStr = ''
load-env ${toNushell { } cfg.environmentVariables}
'';
in
lib.mkIf hasEnvVars envVarsStr
)
(
let
flattenSettings =
let
joinDot = a: b: "${if a == "" then "" else "${a}."}${b}";
unravel =
prefix: value:
if lib.isAttrs value && !isNushellInline value then
lib.concatMap (key: unravel (joinDot prefix key) value.${key}) (builtins.attrNames value)
else
[ (lib.nameValuePair prefix value) ];
in
unravel "";
mkLine =
{ name, value }:
''
$env.config.${name} = ${toNushell { } value}
'';
settingsLines = lib.concatMapStrings mkLine (flattenSettings cfg.settings);
in
lib.mkIf (cfg.settings != { }) settingsLines
)
(lib.mkIf (cfg.configFile != null) cfg.configFile.text)
cfg.extraConfig
aliasesStr
];
}
)
(lib.mkIf (cfg.loadSessionVariables || cfg.envFile != null || cfg.extraEnv != "") {
"${cfg.configDir}/env.nu".text = lib.mkMerge [
(lib.mkIf cfg.loadSessionVariables "source ${sessionVariablesLoader}")
(lib.mkIf (cfg.envFile != null) cfg.envFile.text)
cfg.extraEnv
];
})
(lib.mkIf (cfg.loginFile != null || cfg.extraLogin != "") {
"${cfg.configDir}/login.nu".text = lib.mkMerge [
(lib.mkIf (cfg.loginFile != null) cfg.loginFile.text)
cfg.extraLogin
];
})
(
let
msgPackz = pkgs.runCommand "nushellMsgPackz" { } ''
mkdir -p "$out"
touch "$out/plugin.msgpackz"
${lib.getExe cfg.package} \
--plugin-config "$out/plugin.msgpackz" \
--commands '${
lib.concatStringsSep "; " (map (plugin: "plugin add ${lib.getExe plugin}") cfg.plugins)
}'
'';
in
lib.mkIf ((cfg.package != null) && (cfg.plugins != [ ])) {
"${cfg.configDir}/plugin.msgpackz".source = "${msgPackz}/plugin.msgpackz";
}
)
];
};
}