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