1# This module defines a global environment configuration and
2# a common configuration for all shells.
3{
4 config,
5 lib,
6 utils,
7 pkgs,
8 ...
9}:
10let
11
12 cfg = config.environment;
13
14 exportedEnvVars =
15 let
16 absoluteVariables = lib.mapAttrs (n: lib.toList) cfg.variables;
17
18 suffixedVariables = lib.flip lib.mapAttrs cfg.profileRelativeEnvVars (
19 envVar: listSuffixes:
20 lib.concatMap (profile: map (suffix: "${profile}${suffix}") listSuffixes) cfg.profiles
21 );
22
23 allVariables = lib.zipAttrsWith (n: lib.concatLists) [
24 absoluteVariables
25 suffixedVariables
26 ];
27
28 exportVariables = lib.mapAttrsToList (
29 n: v: ''export ${n}="${lib.concatStringsSep ":" v}"''
30 ) allVariables;
31 in
32 lib.concatStringsSep "\n" exportVariables;
33in
34
35{
36
37 options = {
38
39 environment.variables = lib.mkOption {
40 default = { };
41 example = {
42 EDITOR = "nvim";
43 VISUAL = "nvim";
44 };
45 description = ''
46 A set of environment variables used in the global environment.
47 These variables will be set on shell initialisation (e.g. in /etc/profile).
48
49 The value of each variable can be either a string or a list of
50 strings. The latter is concatenated, interspersed with colon
51 characters.
52
53 Setting a variable to `null` does nothing. You can override a
54 variable set by another module to `null` to unset it.
55 '';
56 type =
57 with lib.types;
58 attrsOf (
59 nullOr (oneOf [
60 (listOf (oneOf [
61 int
62 str
63 path
64 ]))
65 int
66 str
67 path
68 ])
69 );
70 apply =
71 let
72 toStr = v: if lib.isPath v then "${v}" else toString v;
73 in
74 attrs:
75 lib.mapAttrs (n: v: if lib.isList v then lib.concatMapStringsSep ":" toStr v else toStr v) (
76 lib.filterAttrs (n: v: v != null) attrs
77 );
78 };
79
80 environment.profiles = lib.mkOption {
81 default = [ ];
82 description = ''
83 A list of profiles used to setup the global environment.
84 '';
85 type = lib.types.listOf lib.types.str;
86 };
87
88 environment.profileRelativeEnvVars = lib.mkOption {
89 type = lib.types.attrsOf (lib.types.listOf lib.types.str);
90 example = {
91 PATH = [ "/bin" ];
92 MANPATH = [
93 "/man"
94 "/share/man"
95 ];
96 };
97 description = ''
98 Attribute set of environment variable. Each attribute maps to a list
99 of relative paths. Each relative path is appended to the each profile
100 of {option}`environment.profiles` to form the content of the
101 corresponding environment variable.
102 '';
103 };
104
105 # !!! isn't there a better way?
106 environment.extraInit = lib.mkOption {
107 default = "";
108 description = ''
109 Shell script code called during global environment initialisation
110 after all variables and profileVariables have been set.
111 This code is assumed to be shell-independent, which means you should
112 stick to pure sh without sh word split.
113 '';
114 type = lib.types.lines;
115 };
116
117 environment.shellInit = lib.mkOption {
118 default = "";
119 description = ''
120 Shell script code called during shell initialisation.
121 This code is assumed to be shell-independent, which means you should
122 stick to pure sh without sh word split.
123 '';
124 type = lib.types.lines;
125 };
126
127 environment.loginShellInit = lib.mkOption {
128 default = "";
129 description = ''
130 Shell script code called during login shell initialisation.
131 This code is assumed to be shell-independent, which means you should
132 stick to pure sh without sh word split.
133 '';
134 type = lib.types.lines;
135 };
136
137 environment.interactiveShellInit = lib.mkOption {
138 default = "";
139 description = ''
140 Shell script code called during interactive shell initialisation.
141 This code is assumed to be shell-independent, which means you should
142 stick to pure sh without sh word split.
143 '';
144 type = lib.types.lines;
145 };
146
147 environment.shellAliases = lib.mkOption {
148 example = {
149 l = null;
150 ll = "ls -l";
151 };
152 description = ''
153 An attribute set that maps aliases (the top level attribute names in
154 this option) to command strings or directly to build outputs. The
155 aliases are added to all users' shells.
156 Aliases mapped to `null` are ignored.
157 '';
158 type = with lib.types; attrsOf (nullOr (either str path));
159 };
160
161 environment.homeBinInPath = lib.mkOption {
162 description = ''
163 Include ~/bin/ in $PATH.
164 '';
165 default = false;
166 type = lib.types.bool;
167 };
168
169 environment.localBinInPath = lib.mkOption {
170 description = ''
171 Add ~/.local/bin/ to $PATH
172 '';
173 default = false;
174 type = lib.types.bool;
175 };
176
177 environment.binsh = lib.mkOption {
178 default = "${config.system.build.binsh}/bin/sh";
179 defaultText = lib.literalExpression ''"''${config.system.build.binsh}/bin/sh"'';
180 example = lib.literalExpression ''"''${pkgs.dash}/bin/dash"'';
181 type = lib.types.path;
182 visible = false;
183 description = ''
184 The shell executable that is linked system-wide to
185 `/bin/sh`. Please note that NixOS assumes all
186 over the place that shell to be Bash, so override the default
187 setting only if you know exactly what you're doing.
188 '';
189 };
190
191 environment.shells = lib.mkOption {
192 default = [ ];
193 example = lib.literalExpression "[ pkgs.bashInteractive pkgs.zsh ]";
194 description = ''
195 A list of permissible login shells for user accounts.
196 No need to mention `/bin/sh`
197 here, it is placed into this list implicitly.
198 '';
199 type = lib.types.listOf (lib.types.either lib.types.shellPackage lib.types.path);
200 };
201
202 };
203
204 config = {
205
206 system.build.binsh = pkgs.bashInteractive;
207
208 # Set session variables in the shell as well. This is usually
209 # unnecessary, but it allows changes to session variables to take
210 # effect without restarting the session (e.g. by opening a new
211 # terminal instead of logging out of X11).
212 environment.variables = config.environment.sessionVariables;
213
214 environment.profileRelativeEnvVars = config.environment.profileRelativeSessionVariables;
215
216 environment.shellAliases = lib.mapAttrs (name: lib.mkDefault) {
217 ls = "ls --color=tty";
218 ll = "ls -l";
219 l = "ls -alh";
220 };
221
222 environment.etc.shells.text = ''
223 ${lib.concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
224 /bin/sh
225 '';
226
227 # For resetting environment with `. /etc/set-environment` when needed
228 # and discoverability (see motivation of #30418).
229 environment.etc.set-environment.source = config.system.build.setEnvironment;
230
231 system.build.setEnvironment = pkgs.writeText "set-environment" ''
232 # DO NOT EDIT -- this file has been generated automatically.
233
234 # Prevent this file from being sourced by child shells.
235 export __NIXOS_SET_ENVIRONMENT_DONE=1
236
237 ${exportedEnvVars}
238
239 ${cfg.extraInit}
240
241 ${lib.optionalString cfg.homeBinInPath ''
242 # ~/bin if it exists overrides other bin directories.
243 export PATH="$HOME/bin:$PATH"
244 ''}
245
246 ${lib.optionalString cfg.localBinInPath ''
247 export PATH="$HOME/.local/bin:$PATH"
248 ''}
249 '';
250
251 system.activationScripts.binsh = lib.stringAfter [ "stdio" ] ''
252 # Create the required /bin/sh symlink; otherwise lots of things
253 # (notably the system() function) won't work.
254 mkdir -p /bin
255 chmod 0755 /bin
256 ln -sfn "${cfg.binsh}" /bin/.sh.tmp
257 mv /bin/.sh.tmp /bin/sh # atomically replace /bin/sh
258 '';
259
260 };
261
262}