at 23.11-pre 8.4 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 # Prevent the current configuration from being garbage-collected. 59 mkdir -p /nix/var/nix/gcroots 60 ln -sfn /run/current-system /nix/var/nix/gcroots/current-system 61 62 exit $_status 63 ''; 64 65 path = with pkgs; map getBin 66 [ coreutils 67 gnugrep 68 findutils 69 getent 70 stdenv.cc.libc # nscd in update-users-groups.pl 71 shadow 72 nettools # needed for hostname 73 util-linux # needed for mount and mountpoint 74 ]; 75 76 scriptType = withDry: with types; 77 let scriptOptions = 78 { deps = mkOption 79 { type = types.listOf types.str; 80 default = [ ]; 81 description = lib.mdDoc "List of dependencies. The script will run after these."; 82 }; 83 text = mkOption 84 { type = types.lines; 85 description = lib.mdDoc "The content of the script."; 86 }; 87 } // optionalAttrs withDry { 88 supportsDryActivation = mkOption 89 { type = types.bool; 90 default = false; 91 description = lib.mdDoc '' 92 Whether this activation script supports being dry-activated. 93 These activation scripts will also be executed on dry-activate 94 activations with the environment variable 95 `NIXOS_ACTION` being set to `dry-activate`. 96 it's important that these activation scripts don't 97 modify anything about the system when the variable is set. 98 ''; 99 }; 100 }; 101 in either str (submodule { options = scriptOptions; }); 102 103in 104 105{ 106 107 ###### interface 108 109 options = { 110 111 system.activationScripts = mkOption { 112 default = {}; 113 114 example = literalExpression '' 115 { stdio.text = 116 ''' 117 # Needed by some programs. 118 ln -sfn /proc/self/fd /dev/fd 119 ln -sfn /proc/self/fd/0 /dev/stdin 120 ln -sfn /proc/self/fd/1 /dev/stdout 121 ln -sfn /proc/self/fd/2 /dev/stderr 122 '''; 123 } 124 ''; 125 126 description = lib.mdDoc '' 127 A set of shell script fragments that are executed when a NixOS 128 system configuration is activated. Examples are updating 129 /etc, creating accounts, and so on. Since these are executed 130 every time you boot the system or run 131 {command}`nixos-rebuild`, it's important that they are 132 idempotent and fast. 133 ''; 134 135 type = types.attrsOf (scriptType true); 136 apply = set: set // { 137 script = systemActivationScript set false; 138 }; 139 }; 140 141 system.dryActivationScript = mkOption { 142 description = lib.mdDoc "The shell script that is to be run when dry-activating a system."; 143 readOnly = true; 144 internal = true; 145 default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true; 146 defaultText = literalMD "generated activation script"; 147 }; 148 149 system.userActivationScripts = mkOption { 150 default = {}; 151 152 example = literalExpression '' 153 { plasmaSetup = { 154 text = ''' 155 ''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5" 156 '''; 157 deps = []; 158 }; 159 } 160 ''; 161 162 description = lib.mdDoc '' 163 A set of shell script fragments that are executed by a systemd user 164 service when a NixOS system configuration is activated. Examples are 165 rebuilding the .desktop file cache for showing applications in the menu. 166 Since these are executed every time you run 167 {command}`nixos-rebuild`, it's important that they are 168 idempotent and fast. 169 ''; 170 171 type = with types; attrsOf (scriptType false); 172 173 apply = set: { 174 script = '' 175 unset PATH 176 for i in ${toString path}; do 177 PATH=$PATH:$i/bin:$i/sbin 178 done 179 180 _status=0 181 trap "_status=1 _localstatus=\$?" ERR 182 183 ${ 184 let 185 set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set; 186 withHeadlines = addAttributeName set'; 187 in textClosureMap id (withHeadlines) (attrNames withHeadlines) 188 } 189 190 exit $_status 191 ''; 192 }; 193 194 }; 195 196 environment.usrbinenv = mkOption { 197 default = "${pkgs.coreutils}/bin/env"; 198 defaultText = literalExpression ''"''${pkgs.coreutils}/bin/env"''; 199 example = literalExpression ''"''${pkgs.busybox}/bin/env"''; 200 type = types.nullOr types.path; 201 visible = false; 202 description = lib.mdDoc '' 203 The env(1) executable that is linked system-wide to 204 `/usr/bin/env`. 205 ''; 206 }; 207 }; 208 209 210 ###### implementation 211 212 config = { 213 214 system.activationScripts.stdio = ""; # obsolete 215 216 system.activationScripts.var = 217 '' 218 # Various log/runtime directories. 219 220 mkdir -m 1777 -p /var/tmp 221 222 # Empty, immutable home directory of many system accounts. 223 mkdir -p /var/empty 224 # Make sure it's really empty 225 ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true 226 find /var/empty -mindepth 1 -delete 227 chmod 0555 /var/empty 228 chown root:root /var/empty 229 ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true 230 ''; 231 232 system.activationScripts.usrbinenv = if config.environment.usrbinenv != null 233 then '' 234 mkdir -m 0755 -p /usr/bin 235 ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp 236 mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env 237 '' 238 else '' 239 rm -f /usr/bin/env 240 rmdir --ignore-fail-on-non-empty /usr/bin /usr 241 ''; 242 243 system.activationScripts.specialfs = 244 '' 245 specialMount() { 246 local device="$1" 247 local mountPoint="$2" 248 local options="$3" 249 local fsType="$4" 250 251 if mountpoint -q "$mountPoint"; then 252 local options="remount,$options" 253 else 254 mkdir -m 0755 -p "$mountPoint" 255 fi 256 mount -t "$fsType" -o "$options" "$device" "$mountPoint" 257 } 258 source ${config.system.build.earlyMountScript} 259 ''; 260 261 systemd.user = { 262 services.nixos-activation = { 263 description = "Run user-specific NixOS activation"; 264 script = config.system.userActivationScripts.script; 265 unitConfig.ConditionUser = "!@system"; 266 serviceConfig.Type = "oneshot"; 267 wantedBy = [ "default.target" ]; 268 }; 269 }; 270 }; 271 272}