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