1# generate the script used to activate the configuration.
2{
3 config,
4 lib,
5 pkgs,
6 ...
7}:
8
9with lib;
10
11let
12
13 addAttributeName = mapAttrs (
14 a: v:
15 v
16 // {
17 text = ''
18 #### Activation script snippet ${a}:
19 _localstatus=0
20 ${v.text}
21
22 if (( _localstatus > 0 )); then
23 printf "Activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus"
24 fi
25 '';
26 }
27 );
28
29 systemActivationScript =
30 set: onlyDry:
31 let
32 set' = mapAttrs (
33 _: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v
34 ) set;
35 withHeadlines = addAttributeName set';
36 # When building a dry activation script, this replaces all activation scripts
37 # that do not support dry mode with a comment that does nothing. Filtering these
38 # activation scripts out so they don't get generated into the dry activation script
39 # does not work because when an activation script that supports dry mode depends on
40 # an activation script that does not, the dependency cannot be resolved and the eval
41 # fails.
42 withDrySnippets = mapAttrs (
43 a: v:
44 if onlyDry && !v.supportsDryActivation then
45 v
46 // {
47 text = "#### Activation script snippet ${a} does not support dry activation.";
48 }
49 else
50 v
51 ) withHeadlines;
52 in
53 ''
54 #!${pkgs.runtimeShell}
55
56 source ${./lib/lib.sh}
57
58 systemConfig='@out@'
59
60 export PATH=/empty
61 for i in ${toString path}; do
62 PATH=$PATH:$i/bin:$i/sbin
63 done
64
65 _status=0
66 trap "_status=1 _localstatus=\$?" ERR
67
68 # Ensure a consistent umask.
69 umask 0022
70
71 ${textClosureMap id (withDrySnippets) (attrNames withDrySnippets)}
72
73 ''
74 + optionalString (!onlyDry) ''
75 # Make this configuration the current configuration.
76 # The readlink is there to ensure that when $systemConfig = /system
77 # (which is a symlink to the store), /run/current-system is still
78 # used as a garbage collection root.
79 ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
80
81 exit $_status
82 '';
83
84 path =
85 with pkgs;
86 map getBin [
87 coreutils
88 gnugrep
89 findutils
90 getent
91 stdenv.cc.libc # nscd in update-users-groups.pl
92 shadow
93 nettools # needed for hostname
94 util-linux # needed for mount and mountpoint
95 ];
96
97 scriptType =
98 withDry:
99 with types;
100 let
101 scriptOptions =
102 {
103 deps = mkOption {
104 type = types.listOf types.str;
105 default = [ ];
106 description = "List of dependencies. The script will run after these.";
107 };
108 text = mkOption {
109 type = types.lines;
110 description = "The content of the script.";
111 };
112 }
113 // optionalAttrs withDry {
114 supportsDryActivation = mkOption {
115 type = types.bool;
116 default = false;
117 description = ''
118 Whether this activation script supports being dry-activated.
119 These activation scripts will also be executed on dry-activate
120 activations with the environment variable
121 `NIXOS_ACTION` being set to `dry-activate`.
122 it's important that these activation scripts don't
123 modify anything about the system when the variable is set.
124 '';
125 };
126 };
127 in
128 either str (submodule {
129 options = scriptOptions;
130 });
131
132in
133
134{
135
136 ###### interface
137
138 options = {
139
140 system.activationScripts = mkOption {
141 default = { };
142
143 example = literalExpression ''
144 {
145 stdio = {
146 # Run after /dev has been mounted
147 deps = [ "specialfs" ];
148 text =
149 '''
150 # Needed by some programs.
151 ln -sfn /proc/self/fd /dev/fd
152 ln -sfn /proc/self/fd/0 /dev/stdin
153 ln -sfn /proc/self/fd/1 /dev/stdout
154 ln -sfn /proc/self/fd/2 /dev/stderr
155 ''';
156 };
157 }
158 '';
159
160 description = ''
161 A set of shell script fragments that are executed when a NixOS
162 system configuration is activated. Examples are updating
163 /etc, creating accounts, and so on. Since these are executed
164 every time you boot the system or run
165 {command}`nixos-rebuild`, it's important that they are
166 idempotent and fast.
167 '';
168
169 type = types.attrsOf (scriptType true);
170 apply =
171 set:
172 set
173 // {
174 script = systemActivationScript set false;
175 };
176 };
177
178 system.dryActivationScript = mkOption {
179 description = "The shell script that is to be run when dry-activating a system.";
180 readOnly = true;
181 internal = true;
182 default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
183 defaultText = literalMD "generated activation script";
184 };
185
186 system.userActivationScripts = mkOption {
187 default = { };
188
189 example = literalExpression ''
190 { plasmaSetup = {
191 text = '''
192 ''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
193 ''';
194 deps = [];
195 };
196 }
197 '';
198
199 description = ''
200 A set of shell script fragments that are executed by a systemd user
201 service when a NixOS system configuration is activated. Examples are
202 rebuilding the .desktop file cache for showing applications in the menu.
203 Since these are executed every time you run
204 {command}`nixos-rebuild`, it's important that they are
205 idempotent and fast.
206 '';
207
208 type = with types; attrsOf (scriptType false);
209
210 apply = set: {
211 script = ''
212 export PATH=
213 for i in ${toString path}; do
214 PATH=$PATH:$i/bin:$i/sbin
215 done
216
217 _status=0
218 trap "_status=1 _localstatus=\$?" ERR
219
220 ${
221 let
222 set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
223 withHeadlines = addAttributeName set';
224 in
225 textClosureMap id (withHeadlines) (attrNames withHeadlines)
226 }
227
228 exit $_status
229 '';
230 };
231
232 };
233
234 environment.usrbinenv = mkOption {
235 default = "${pkgs.coreutils}/bin/env";
236 defaultText = literalExpression ''"''${pkgs.coreutils}/bin/env"'';
237 example = literalExpression ''"''${pkgs.busybox}/bin/env"'';
238 type = types.nullOr types.path;
239 visible = false;
240 description = ''
241 The {manpage}`env(1)` executable that is linked system-wide to
242 `/usr/bin/env`.
243 '';
244 };
245
246 system.build.installBootLoader = mkOption {
247 internal = true;
248 default = pkgs.writeShellScript "no-bootloader" ''
249 echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2
250 '';
251 defaultText = lib.literalExpression ''
252 pkgs.writeShellScript "no-bootloader" '''
253 echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2
254 '''
255 '';
256 description = ''
257 A program that writes a bootloader installation script to the path passed in the first command line argument.
258
259 See `nixos/modules/system/activation/switch-to-configuration.pl`.
260 '';
261 type = types.unique {
262 message = ''
263 Only one bootloader can be enabled at a time. This requirement has not
264 been checked until NixOS 22.05. Earlier versions defaulted to the last
265 definition. Change your configuration to enable only one bootloader.
266 '';
267 } (types.either types.str types.package);
268 };
269
270 };
271
272 ###### implementation
273
274 config = {
275
276 system.activationScripts.stdio = ""; # obsolete
277 system.activationScripts.var = ""; # obsolete
278
279 systemd.tmpfiles.rules =
280 [
281 "D /var/empty 0555 root root -"
282 "h /var/empty - - - - +i"
283 ]
284 ++ lib.optionals config.nix.enable [
285 # Prevent the current configuration from being garbage-collected.
286 "d /nix/var/nix/gcroots -"
287 "L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system"
288 ];
289
290 system.activationScripts.usrbinenv =
291 if config.environment.usrbinenv != null then
292 ''
293 mkdir -p /usr/bin
294 chmod 0755 /usr/bin
295 ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
296 mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
297 ''
298 else
299 ''
300 rm -f /usr/bin/env
301 if test -d /usr/bin; then rmdir --ignore-fail-on-non-empty /usr/bin; fi
302 if test -d /usr; then rmdir --ignore-fail-on-non-empty /usr; fi
303 '';
304
305 system.activationScripts.specialfs = ''
306 specialMount() {
307 local device="$1"
308 local mountPoint="$2"
309 local options="$3"
310 local fsType="$4"
311
312 if mountpoint -q "$mountPoint"; then
313 local options="remount,$options"
314 else
315 mkdir -p "$mountPoint"
316 chmod 0755 "$mountPoint"
317 fi
318 mount -t "$fsType" -o "$options" "$device" "$mountPoint"
319 }
320 source ${config.system.build.earlyMountScript}
321 '';
322
323 systemd.user = {
324 services.nixos-activation = {
325 description = "Run user-specific NixOS activation";
326 script = config.system.userActivationScripts.script;
327 unitConfig.ConditionUser = "!@system";
328 serviceConfig.Type = "oneshot";
329 wantedBy = [ "default.target" ];
330 };
331 };
332 };
333
334}