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 util-linux # needed for mount and mountpoint
94 ];
95
96 scriptType =
97 withDry:
98 with types;
99 let
100 scriptOptions = {
101 deps = mkOption {
102 type = types.listOf types.str;
103 default = [ ];
104 description = "List of dependencies. The script will run after these.";
105 };
106 text = mkOption {
107 type = types.lines;
108 description = "The content of the script.";
109 };
110 }
111 // optionalAttrs withDry {
112 supportsDryActivation = mkOption {
113 type = types.bool;
114 default = false;
115 description = ''
116 Whether this activation script supports being dry-activated.
117 These activation scripts will also be executed on dry-activate
118 activations with the environment variable
119 `NIXOS_ACTION` being set to `dry-activate`.
120 it's important that these activation scripts don't
121 modify anything about the system when the variable is set.
122 '';
123 };
124 };
125 in
126 either str (submodule {
127 options = scriptOptions;
128 });
129
130in
131
132{
133
134 ###### interface
135
136 options = {
137
138 system.activationScripts = mkOption {
139 default = { };
140
141 example = literalExpression ''
142 {
143 stdio = {
144 # Run after /dev has been mounted
145 deps = [ "specialfs" ];
146 text =
147 '''
148 # Needed by some programs.
149 ln -sfn /proc/self/fd /dev/fd
150 ln -sfn /proc/self/fd/0 /dev/stdin
151 ln -sfn /proc/self/fd/1 /dev/stdout
152 ln -sfn /proc/self/fd/2 /dev/stderr
153 ''';
154 };
155 }
156 '';
157
158 description = ''
159 A set of shell script fragments that are executed when a NixOS
160 system configuration is activated. Examples are updating
161 /etc, creating accounts, and so on. Since these are executed
162 every time you boot the system or run
163 {command}`nixos-rebuild`, it's important that they are
164 idempotent and fast.
165 '';
166
167 type = types.attrsOf (scriptType true);
168 apply =
169 set:
170 set
171 // {
172 script = systemActivationScript set false;
173 };
174 };
175
176 system.dryActivationScript = mkOption {
177 description = "The shell script that is to be run when dry-activating a system.";
178 readOnly = true;
179 internal = true;
180 default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
181 defaultText = literalMD "generated activation script";
182 };
183
184 system.userActivationScripts = mkOption {
185 default = { };
186
187 example = literalExpression ''
188 { plasmaSetup = {
189 text = '''
190 ''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
191 ''';
192 deps = [];
193 };
194 }
195 '';
196
197 description = ''
198 A set of shell script fragments that are executed by a systemd user
199 service when a NixOS system configuration is activated. Examples are
200 rebuilding the .desktop file cache for showing applications in the menu.
201 Since these are executed every time you run
202 {command}`nixos-rebuild`, it's important that they are
203 idempotent and fast.
204 '';
205
206 type = with types; attrsOf (scriptType false);
207
208 apply = set: {
209 script = ''
210 export PATH=
211 for i in ${toString path}; do
212 PATH=$PATH:$i/bin:$i/sbin
213 done
214
215 _status=0
216 trap "_status=1 _localstatus=\$?" ERR
217
218 ${
219 let
220 set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
221 withHeadlines = addAttributeName set';
222 in
223 textClosureMap id (withHeadlines) (attrNames withHeadlines)
224 }
225
226 exit $_status
227 '';
228 };
229
230 };
231
232 environment.usrbinenv = mkOption {
233 default = "${pkgs.coreutils}/bin/env";
234 defaultText = literalExpression ''"''${pkgs.coreutils}/bin/env"'';
235 example = literalExpression ''"''${pkgs.busybox}/bin/env"'';
236 type = types.nullOr types.path;
237 visible = false;
238 description = ''
239 The {manpage}`env(1)` executable that is linked system-wide to
240 `/usr/bin/env`.
241 '';
242 };
243
244 system.build.installBootLoader = mkOption {
245 internal = true;
246 default = pkgs.writeShellScript "no-bootloader" ''
247 echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2
248 '';
249 defaultText = lib.literalExpression ''
250 pkgs.writeShellScript "no-bootloader" '''
251 echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2
252 '''
253 '';
254 description = ''
255 A program that writes a bootloader installation script to the path passed in the first command line argument.
256
257 See `pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs`.
258 '';
259 type = types.unique {
260 message = ''
261 Only one bootloader can be enabled at a time. This requirement has not
262 been checked until NixOS 22.05. Earlier versions defaulted to the last
263 definition. Change your configuration to enable only one bootloader.
264 '';
265 } (types.either types.str types.package);
266 };
267
268 };
269
270 ###### implementation
271
272 config = {
273
274 system.activationScripts.stdio = ""; # obsolete
275 system.activationScripts.var = ""; # obsolete
276
277 systemd.tmpfiles.rules = [
278 "D /var/empty 0555 root root -"
279 "h /var/empty - - - - +i"
280 ]
281 ++ lib.optionals config.nix.enable [
282 # Prevent the current configuration from being garbage-collected.
283 "d /nix/var/nix/gcroots -"
284 "L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system"
285 ];
286
287 system.activationScripts.usrbinenv =
288 if config.environment.usrbinenv != null then
289 ''
290 mkdir -p /usr/bin
291 chmod 0755 /usr/bin
292 ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp
293 mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
294 ''
295 else
296 ''
297 rm -f /usr/bin/env
298 if test -d /usr/bin; then rmdir --ignore-fail-on-non-empty /usr/bin; fi
299 if test -d /usr; then rmdir --ignore-fail-on-non-empty /usr; fi
300 '';
301
302 system.activationScripts.specialfs = ''
303 specialMount() {
304 local device="$1"
305 local mountPoint="$2"
306 local options="$3"
307 local fsType="$4"
308
309 if mountpoint -q "$mountPoint"; then
310 local options="remount,$options"
311 else
312 mkdir -p "$mountPoint"
313 chmod 0755 "$mountPoint"
314 fi
315 mount -t "$fsType" -o "$options" "$device" "$mountPoint"
316 }
317 source ${config.system.build.earlyMountScript}
318 '';
319
320 systemd.user = lib.mkIf config.system.activatable {
321 services.nixos-activation = {
322 description = "Run user-specific NixOS activation";
323 script = config.system.userActivationScripts.script;
324 unitConfig.ConditionUser = "!@system";
325 serviceConfig.Type = "oneshot";
326 wantedBy = [ "default.target" ];
327 };
328 };
329 };
330
331}