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