summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2022-05-26 13:58:07 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2022-05-26 13:58:07 +0200
commitfc6cf6169868e60c189e4b243330c3717ff159f3 (patch)
tree3f6dea9c1420e23756257b5abea27ec9ed92d58a /modules
parent84f2affd66a0ff3947b91a30308cb8e6a8ff7594 (diff)
downloadnixos-fc6cf6169868e60c189e4b243330c3717ff159f3.tar
nixos-fc6cf6169868e60c189e4b243330c3717ff159f3.tar.gz
nixos-fc6cf6169868e60c189e4b243330c3717ff159f3.tar.bz2
nixos-fc6cf6169868e60c189e4b243330c3717ff159f3.tar.xz
nixos-fc6cf6169868e60c189e4b243330c3717ff159f3.zip
...
Diffstat (limited to 'modules')
-rw-r--r--modules/luksroot.nix1089
-rw-r--r--modules/stage-1/default.nix669
-rw-r--r--modules/stage-1/stage-1-init.sh638
3 files changed, 0 insertions, 2396 deletions
diff --git a/modules/luksroot.nix b/modules/luksroot.nix
deleted file mode 100644
index 52de2c40..00000000
--- a/modules/luksroot.nix
+++ /dev/null
@@ -1,1089 +0,0 @@
1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 luks = config.boot.initrd.luks;
7 kernelPackages = config.boot.kernelPackages;
8
9 commonFunctions = ''
10 die() {
11 echo "$@" >&2
12 exit 1
13 }
14
15 dev_exist() {
16 local target="$1"
17 if [ -e $target ]; then
18 return 0
19 else
20 local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g')
21 blkid --uuid $uuid >/dev/null
22 return $?
23 fi
24 }
25
26 wait_target() {
27 local name="$1"
28 local target="$2"
29 local secs="''${3:-10}"
30 local desc="''${4:-$name $target to appear}"
31
32 if ! dev_exist $target; then
33 echo -n "Waiting $secs seconds for $desc..."
34 local success=false;
35 for try in $(seq $secs); do
36 echo -n "."
37 sleep 1
38 if dev_exist $target; then
39 success=true
40 break
41 fi
42 done
43 if [ $success == true ]; then
44 echo " - success";
45 return 0
46 else
47 echo " - failure";
48 return 1
49 fi
50 fi
51 return 0
52 }
53
54 wait_yubikey() {
55 local secs="''${1:-10}"
56
57 ykinfo -v 1>/dev/null 2>&1
58 if [ $? != 0 ]; then
59 echo -n "Waiting $secs seconds for YubiKey to appear..."
60 local success=false
61 for try in $(seq $secs); do
62 echo -n .
63 sleep 1
64 ykinfo -v 1>/dev/null 2>&1
65 if [ $? == 0 ]; then
66 success=true
67 break
68 fi
69 done
70 if [ $success == true ]; then
71 echo " - success";
72 return 0
73 else
74 echo " - failure";
75 return 1
76 fi
77 fi
78 return 0
79 }
80
81 wait_gpgcard() {
82 local secs="''${1:-10}"
83
84 gpg --card-status > /dev/null 2> /dev/null
85 if [ $? != 0 ]; then
86 echo -n "Waiting $secs seconds for GPG Card to appear"
87 local success=false
88 for try in $(seq $secs); do
89 echo -n .
90 sleep 1
91 gpg --card-status > /dev/null 2> /dev/null
92 if [ $? == 0 ]; then
93 success=true
94 break
95 fi
96 done
97 if [ $success == true ]; then
98 echo " - success";
99 return 0
100 else
101 echo " - failure";
102 return 1
103 fi
104 fi
105 return 0
106 }
107 '';
108
109 preCommands = ''
110 # A place to store crypto things
111
112 # A ramfs is used here to ensure that the file used to update
113 # the key slot with cryptsetup will never get swapped out.
114 # Warning: Do NOT replace with tmpfs!
115 mkdir -p /crypt-ramfs
116 mount -t ramfs none /crypt-ramfs
117
118 # Cryptsetup locking directory
119 mkdir -p /run/cryptsetup
120
121 # For YubiKey salt storage
122 mkdir -p /crypt-storage
123
124 ${optionalString luks.gpgSupport ''
125 export GPG_TTY=$(tty)
126 export GNUPGHOME=/crypt-ramfs/.gnupg
127
128 gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
129 ''}
130
131 # Disable all input echo for the whole stage. We could use read -s
132 # instead but that would ocasionally leak characters between read
133 # invocations.
134 stty -echo
135 '';
136
137 postCommands = ''
138 stty echo
139 umount /crypt-storage 2>/dev/null
140 umount /crypt-ramfs 2>/dev/null
141 '';
142
143 openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, bypassWorkqueues, yubikey, gpgCard, fido2, clevis, dmi, fallbackToPassword, preOpenCommands, postOpenCommands, ... }: assert name' == name;
144 let
145 csopen = "cryptsetup luksOpen ${device} ${name}"
146 + optionalString allowDiscards " --allow-discards"
147 + optionalString bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue"
148 + optionalString (header != null) " --header=${header}";
149 cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
150 in ''
151 # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
152 # if on a USB drive.
153 wait_target "device" ${device} || die "${device} is unavailable"
154
155 ${optionalString (header != null) ''
156 wait_target "header" ${header} || die "${header} is unavailable"
157 ''}
158
159 do_open_passphrase() {
160 local passphrase
161
162 while true; do
163 echo -n "Passphrase for ${device}: "
164 passphrase=
165 while true; do
166 if [ -e /crypt-ramfs/passphrase ]; then
167 echo "reused"
168 passphrase=$(cat /crypt-ramfs/passphrase)
169 break
170 else
171 # ask cryptsetup-askpass
172 echo -n "${device}" > /crypt-ramfs/device
173
174 # and try reading it from /dev/console with a timeout
175 IFS= read -t 1 -r passphrase
176 if [ -n "$passphrase" ]; then
177 ${if luks.reusePassphrases then ''
178 # remember it for the next device
179 echo -n "$passphrase" > /crypt-ramfs/passphrase
180 '' else ''
181 # Don't save it to ramfs. We are very paranoid
182 ''}
183 echo
184 break
185 fi
186 fi
187 done
188 echo -n "Verifying passphrase for ${device}..."
189 echo -n "$passphrase" | ${csopen} --key-file=-
190 if [ $? == 0 ]; then
191 echo " - success"
192 ${if luks.reusePassphrases then ''
193 # we don't rm here because we might reuse it for the next device
194 '' else ''
195 rm -f /crypt-ramfs/passphrase
196 ''}
197 break
198 else
199 echo " - failure"
200 # ask for a different one
201 rm -f /crypt-ramfs/passphrase
202 fi
203 done
204 }
205
206 # LUKS
207 open_normally() {
208 ${if (keyFile != null) then ''
209 if wait_target "key file" ${keyFile}; then
210 ${csopen} --key-file=${keyFile} \
211 ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \
212 ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}
213 else
214 ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable"
215 echo " - failing back to interactive password prompt"
216 do_open_passphrase
217 fi
218 '' else ''
219 do_open_passphrase
220 ''}
221 }
222
223 ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
224 # YubiKey
225 rbtohex() {
226 ( od -An -vtx1 | tr -d ' \n' )
227 }
228
229 hextorb() {
230 ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
231 }
232
233 do_open_yubikey() {
234 # Make all of these local to this function
235 # to prevent their values being leaked
236 local salt
237 local iterations
238 local k_user
239 local challenge
240 local response
241 local k_luks
242 local opened
243 local new_salt
244 local new_iterations
245 local new_challenge
246 local new_response
247 local new_k_luks
248
249 mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \
250 die "Failed to mount YubiKey salt storage device"
251
252 salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
253 iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
254 challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
255 response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
256
257 for try in $(seq 3); do
258 ${optionalString yubikey.twoFactor ''
259 echo -n "Enter two-factor passphrase: "
260 k_user=
261 while true; do
262 if [ -e /crypt-ramfs/passphrase ]; then
263 echo "reused"
264 k_user=$(cat /crypt-ramfs/passphrase)
265 break
266 else
267 # Try reading it from /dev/console with a timeout
268 IFS= read -t 1 -r k_user
269 if [ -n "$k_user" ]; then
270 ${if luks.reusePassphrases then ''
271 # Remember it for the next device
272 echo -n "$k_user" > /crypt-ramfs/passphrase
273 '' else ''
274 # Don't save it to ramfs. We are very paranoid
275 ''}
276 echo
277 break
278 fi
279 fi
280 done
281 ''}
282
283 if [ ! -z "$k_user" ]; then
284 k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
285 else
286 k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
287 fi
288
289 echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
290
291 if [ $? == 0 ]; then
292 opened=true
293 ${if luks.reusePassphrases then ''
294 # We don't rm here because we might reuse it for the next device
295 '' else ''
296 rm -f /crypt-ramfs/passphrase
297 ''}
298 break
299 else
300 opened=false
301 echo "Authentication failed!"
302 fi
303 done
304
305 [ "$opened" == false ] && die "Maximum authentication errors reached"
306
307 echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
308 for i in $(seq ${toString yubikey.saltLength}); do
309 byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
310 new_salt="$new_salt$byte";
311 echo -n .
312 done;
313 echo "ok"
314
315 new_iterations="$iterations"
316 ${optionalString (yubikey.iterationStep > 0) ''
317 new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))"
318 ''}
319
320 new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
321
322 new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
323
324 if [ ! -z "$k_user" ]; then
325 new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
326 else
327 new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
328 fi
329
330 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
331 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
332
333 if [ $? == 0 ]; then
334 echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path}
335 else
336 echo "Warning: Could not update LUKS key, current challenge persists!"
337 fi
338
339 rm -f /crypt-ramfs/new_key
340 umount /crypt-storage
341 }
342
343 open_with_hardware() {
344 if wait_yubikey ${toString yubikey.gracePeriod}; then
345 do_open_yubikey
346 else
347 echo "No YubiKey found, falling back to non-YubiKey open procedure"
348 open_normally
349 fi
350 }
351 ''}
352
353 ${optionalString (luks.gpgSupport && (gpgCard != null)) ''
354
355 do_open_gpg_card() {
356 # Make all of these local to this function
357 # to prevent their values being leaked
358 local pin
359 local opened
360
361 gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null
362
363 gpg --card-status > /dev/null 2> /dev/null
364
365 for try in $(seq 3); do
366 echo -n "PIN for GPG Card associated with device ${device}: "
367 pin=
368 while true; do
369 if [ -e /crypt-ramfs/passphrase ]; then
370 echo "reused"
371 pin=$(cat /crypt-ramfs/passphrase)
372 break
373 else
374 # and try reading it from /dev/console with a timeout
375 IFS= read -t 1 -r pin
376 if [ -n "$pin" ]; then
377 ${if luks.reusePassphrases then ''
378 # remember it for the next device
379 echo -n "$pin" > /crypt-ramfs/passphrase
380 '' else ''
381 # Don't save it to ramfs. We are very paranoid
382 ''}
383 echo
384 break
385 fi
386 fi
387 done
388 echo -n "Verifying passphrase for ${device}..."
389 echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
390 if [ $? == 0 ]; then
391 echo " - success"
392 ${if luks.reusePassphrases then ''
393 # we don't rm here because we might reuse it for the next device
394 '' else ''
395 rm -f /crypt-ramfs/passphrase
396 ''}
397 break
398 else
399 echo " - failure"
400 # ask for a different one
401 rm -f /crypt-ramfs/passphrase
402 fi
403 done
404
405 [ "$opened" == false ] && die "Maximum authentication errors reached"
406 }
407
408 open_with_hardware() {
409 if wait_gpgcard ${toString gpgCard.gracePeriod}; then
410 do_open_gpg_card
411 else
412 echo "No GPG Card found, falling back to normal open procedure"
413 open_normally
414 fi
415 }
416 ''}
417
418 ${optionalString (luks.fido2Support && (fido2.credential != null)) ''
419
420 open_with_hardware() {
421 local passsphrase
422
423 ${if fido2.passwordLess then ''
424 export passphrase=""
425 '' else ''
426 read -rsp "FIDO2 salt for ${device}: " passphrase
427 echo
428 ''}
429 ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
430 echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest."
431 echo "Please move your mouse to create needed randomness."
432 ''}
433 echo "Waiting for your FIDO2 device..."
434 fido2luks open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase
435 if [ $? -ne 0 ]; then
436 echo "No FIDO2 key found, falling back to normal open procedure"
437 open_normally
438 fi
439 }
440 ''}
441
442 ${optionalString (luks.clevisSupport && clevis) ''
443
444 open_with_hardware() {
445 mkdir -p /crypt-ramfs/clevis
446
447 TMPDIR=/crypt-ramfs/clevis clevis luks unlock -d ${device} -n ${name}
448
449 if [ $? -ne 0 ]; then
450 echo "Unlocking with clevis failed, falling back to normal open procedure"
451 open_normally
452 fi
453 }
454
455 ''}
456
457 ${optionalString (luks.dmiSupport && dmi) ''
458
459 open_with_hardware() {
460 dmidecode -s system-uuid > /crypt-ramfs/passphrase
461
462 ${csopen} --key-file=- < /crypt-ramfs/passphrase > /dev/null 2> /dev/null
463
464 if [ $? -ne 0 ]; then
465 echo "Unlocking with system-uuid failed, falling back to normal open procedure"
466 rm -f /crypt-ramfs/passphrase
467 open_normally
468 ${optionalString (!luks.reusePassphrases) ''
469 else
470 rm -f /crypt-ramfs/passphrase
471 ''}
472 fi
473 }
474
475 ''}
476
477 # commands to run right before we mount our device
478 ${preOpenCommands}
479
480 ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) || (luks.clevisSupport && clevis) || (luks.dmiSupport && dmi) then ''
481 open_with_hardware
482 '' else ''
483 open_normally
484 ''}
485
486 # commands to run right after we mounted our device
487 ${postOpenCommands}
488 '';
489
490 askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
491 #!/bin/sh
492
493 ${commonFunctions}
494
495 while true; do
496 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
497 device=$(cat /crypt-ramfs/device)
498
499 echo -n "Passphrase for $device: "
500 IFS= read -rs passphrase
501 echo
502
503 rm /crypt-ramfs/device
504 echo -n "$passphrase" > /crypt-ramfs/passphrase
505 done
506 '';
507
508 preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
509 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
510
511in
512{
513 disabledModules = [ "system/boot/luksroot.nix" ];
514
515 imports = [
516 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
517 ];
518
519 options = {
520
521 boot.initrd.luks.mitigateDMAAttacks = mkOption {
522 type = types.bool;
523 default = true;
524 description = ''
525 Unless enabled, encryption keys can be easily recovered by an attacker with physical
526 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
527 More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>.
528
529 This option blacklists FireWire drivers, but doesn't remove them. You can manually
530 load the drivers if you need to use a FireWire device, but don't forget to unload them!
531 '';
532 };
533
534 boot.initrd.luks.cryptoModules = mkOption {
535 type = types.listOf types.str;
536 default =
537 [ "aes" "aes_generic" "blowfish" "twofish"
538 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
539 "af_alg" "algif_skcipher"
540 ];
541 description = ''
542 A list of cryptographic kernel modules needed to decrypt the root device(s).
543 The default includes all common modules.
544 '';
545 };
546
547 boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
548 type = types.bool;
549 default = false;
550 internal = true;
551 description = ''
552 Whether to configure luks support in the initrd, when no luks
553 devices are configured.
554 '';
555 };
556
557 boot.initrd.luks.reusePassphrases = mkOption {
558 type = types.bool;
559 default = true;
560 description = ''
561 When opening a new LUKS device try reusing last successful
562 passphrase.
563
564 Useful for mounting a number of devices that use the same
565 passphrase without retyping it several times.
566
567 Such setup can be useful if you use <command>cryptsetup
568 luksSuspend</command>. Different LUKS devices will still have
569 different master keys even when using the same passphrase.
570 '';
571 };
572
573 boot.initrd.luks.devices = mkOption {
574 default = { };
575 example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
576 description = ''
577 The encrypted disk that should be opened before the root
578 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
579 setups are supported. The unencrypted devices can be accessed as
580 <filename>/dev/mapper/<replaceable>name</replaceable></filename>.
581 '';
582
583 type = with types; attrsOf (submodule (
584 { name, ... }: { options = {
585
586 name = mkOption {
587 visible = false;
588 default = name;
589 example = "luksroot";
590 type = types.str;
591 description = "Name of the unencrypted device in <filename>/dev/mapper</filename>.";
592 };
593
594 device = mkOption {
595 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
596 type = types.str;
597 description = "Path of the underlying encrypted block device.";
598 };
599
600 header = mkOption {
601 default = null;
602 example = "/root/header.img";
603 type = types.nullOr types.str;
604 description = ''
605 The name of the file or block device that
606 should be used as header for the encrypted device.
607 '';
608 };
609
610 keyFile = mkOption {
611 default = null;
612 example = "/dev/sdb1";
613 type = types.nullOr types.str;
614 description = ''
615 The name of the file (can be a raw device or a partition) that
616 should be used as the decryption key for the encrypted device. If
617 not specified, you will be prompted for a passphrase instead.
618 '';
619 };
620
621 keyFileSize = mkOption {
622 default = null;
623 example = 4096;
624 type = types.nullOr types.int;
625 description = ''
626 The size of the key file. Use this if only the beginning of the
627 key file should be used as a key (often the case if a raw device
628 or partition is used as key file). If not specified, the whole
629 <literal>keyFile</literal> will be used decryption, instead of just
630 the first <literal>keyFileSize</literal> bytes.
631 '';
632 };
633
634 keyFileOffset = mkOption {
635 default = null;
636 example = 4096;
637 type = types.nullOr types.int;
638 description = ''
639 The offset of the key file. Use this in combination with
640 <literal>keyFileSize</literal> to use part of a file as key file
641 (often the case if a raw device or partition is used as a key file).
642 If not specified, the key begins at the first byte of
643 <literal>keyFile</literal>.
644 '';
645 };
646
647 # FIXME: get rid of this option.
648 preLVM = mkOption {
649 default = true;
650 type = types.bool;
651 description = "Whether the luksOpen will be attempted before LVM scan or after it.";
652 };
653
654 allowDiscards = mkOption {
655 default = false;
656 type = types.bool;
657 description = ''
658 Whether to allow TRIM requests to the underlying device. This option
659 has security implications; please read the LUKS documentation before
660 activating it.
661 '';
662 };
663
664 bypassWorkqueues = mkOption {
665 default = false;
666 type = types.bool;
667 description = ''
668 Whether to bypass dm-crypt's internal read and write workqueues.
669 Enabling this should improve performance on SSDs; see
670 <link xlink:href="https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance">here</link>
671 for more information. Needs Linux 5.9 or later.
672 '';
673 };
674
675 fallbackToPassword = mkOption {
676 default = false;
677 type = types.bool;
678 description = ''
679 Whether to fallback to interactive passphrase prompt if the keyfile
680 cannot be found. This will prevent unattended boot should the keyfile
681 go missing.
682 '';
683 };
684
685 gpgCard = mkOption {
686 default = null;
687 description = ''
688 The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
689 If null (the default), GPG-Smartcard will be disabled for this device.
690 '';
691
692 type = with types; nullOr (submodule {
693 options = {
694 gracePeriod = mkOption {
695 default = 10;
696 type = types.int;
697 description = "Time in seconds to wait for the GPG Smartcard.";
698 };
699
700 encryptedPass = mkOption {
701 default = "";
702 type = types.path;
703 description = "Path to the GPG encrypted passphrase.";
704 };
705
706 publicKey = mkOption {
707 default = "";
708 type = types.path;
709 description = "Path to the Public Key.";
710 };
711 };
712 });
713 };
714
715 fido2 = {
716 credential = mkOption {
717 default = null;
718 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
719 type = types.nullOr types.str;
720 description = "The FIDO2 credential ID.";
721 };
722
723 gracePeriod = mkOption {
724 default = 10;
725 type = types.int;
726 description = "Time in seconds to wait for the FIDO2 key.";
727 };
728
729 passwordLess = mkOption {
730 default = false;
731 type = types.bool;
732 description = ''
733 Defines whatever to use an empty string as a default salt.
734
735 Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>.
736 '';
737 };
738 };
739
740 yubikey = mkOption {
741 default = null;
742 description = ''
743 The options to use for this LUKS device in YubiKey-PBA.
744 If null (the default), YubiKey-PBA will be disabled for this device.
745 '';
746
747 type = with types; nullOr (submodule {
748 options = {
749 twoFactor = mkOption {
750 default = true;
751 type = types.bool;
752 description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
753 };
754
755 slot = mkOption {
756 default = 2;
757 type = types.int;
758 description = "Which slot on the YubiKey to challenge.";
759 };
760
761 saltLength = mkOption {
762 default = 16;
763 type = types.int;
764 description = "Length of the new salt in byte (64 is the effective maximum).";
765 };
766
767 keyLength = mkOption {
768 default = 64;
769 type = types.int;
770 description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
771 };
772
773 iterationStep = mkOption {
774 default = 0;
775 type = types.int;
776 description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
777 };
778
779 gracePeriod = mkOption {
780 default = 10;
781 type = types.int;
782 description = "Time in seconds to wait for the YubiKey.";
783 };
784
785 /* TODO: Add to the documentation of the current module:
786
787 Options related to the storing the salt.
788 */
789 storage = {
790 device = mkOption {
791 default = "/dev/sda1";
792 type = types.path;
793 description = ''
794 An unencrypted device that will temporarily be mounted in stage-1.
795 Must contain the current salt to create the challenge for this LUKS device.
796 '';
797 };
798
799 fsType = mkOption {
800 default = "vfat";
801 type = types.str;
802 description = "The filesystem of the unencrypted device.";
803 };
804
805 path = mkOption {
806 default = "/crypt-storage/default";
807 type = types.str;
808 description = ''
809 Absolute path of the salt on the unencrypted device with
810 that device's root directory as "/".
811 '';
812 };
813 };
814 };
815 });
816 };
817
818 clevis = mkOption {
819 type = types.bool;
820 default = false;
821 description = ''
822 Unlock device via clevis (e.g. with a tpm)
823 '';
824 };
825
826 dmi = mkOption {
827 type = types.bool;
828 default = false;
829 description = ''
830 Unlock device via system-uuid (via dmidecode)
831 '';
832 };
833
834 preOpenCommands = mkOption {
835 type = types.lines;
836 default = "";
837 example = ''
838 mkdir -p /tmp/persistent
839 mount -t zfs rpool/safe/persistent /tmp/persistent
840 '';
841 description = ''
842 Commands that should be run right before we try to mount our LUKS device.
843 This can be useful, if the keys needed to open the drive is on another partion.
844 '';
845 };
846
847 postOpenCommands = mkOption {
848 type = types.lines;
849 default = "";
850 example = ''
851 umount /tmp/persistent
852 '';
853 description = ''
854 Commands that should be run right after we have mounted our LUKS device.
855 '';
856 };
857 };
858 }));
859 };
860
861 boot.initrd.luks.gpgSupport = mkOption {
862 default = false;
863 type = types.bool;
864 description = ''
865 Enables support for authenticating with a GPG encrypted password.
866 '';
867 };
868
869 boot.initrd.luks.yubikeySupport = mkOption {
870 default = false;
871 type = types.bool;
872 description = ''
873 Enables support for authenticating with a YubiKey on LUKS devices.
874 See the NixOS wiki for information on how to properly setup a LUKS device
875 and a YubiKey to work with this feature.
876 '';
877 };
878
879 boot.initrd.luks.fido2Support = mkOption {
880 default = false;
881 type = types.bool;
882 description = ''
883 Enables support for authenticating with FIDO2 devices.
884 '';
885 };
886
887 boot.initrd.luks.clevisSupport = mkOption {
888 default = false;
889 type = types.bool;
890 description = ''
891 Enables support for unlocking luks volumes via clevis (e.g. with a tpm)
892 '';
893 };
894
895 boot.initrd.luks.dmiSupport = mkOption {
896 default = false;
897 type = types.bool;
898 description = ''
899 Enables support for unlocking luks volumes via system-uuid (via dmidecode)
900 '';
901 };
902
903 };
904
905 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
906
907 assertions =
908 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
909 message = "YubiKey and GPG Card may not be used at the same time.";
910 }
911
912 { assertion = !(luks.gpgSupport && luks.fido2Support);
913 message = "FIDO2 and GPG Card may not be used at the same time.";
914 }
915
916 { assertion = !(luks.gpgSupport && luks.clevisSupport);
917 message = "Clevis and GPG Card may not be used at the same time.";
918 }
919
920 { assertion = !(luks.gpgSupport && luks.dmiSupport);
921 message = "DMI and GPG Card may not be used at the same time.";
922 }
923
924 { assertion = !(luks.fido2Support && luks.yubikeySupport);
925 message = "FIDO2 and YubiKey may not be used at the same time.";
926 }
927
928 { assertion = !(luks.fido2Support && luks.clevisSupport);
929 message = "FIDO2 and Clevis may not be used at the same time.";
930 }
931
932 { assertion = !(luks.fido2Support && luks.dmiSupport);
933 message = "FIDO2 and DMI may not be used at the same time.";
934 }
935
936 { assertion = !(luks.yubikeySupport && luks.clevisSupport);
937 message = "Clevis and YubiKey may not be used at the same time.";
938 }
939
940 { assertion = !(luks.yubikeySupport && luks.dmiSupport);
941 message = "DMI and YubiKey may not be used at the same time.";
942 }
943
944 ];
945
946 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
947 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
948 ["firewire_ohci" "firewire_core" "firewire_sbp2"];
949
950 # Some modules that may be needed for mounting anything ciphered
951 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
952 ++ luks.cryptoModules
953 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
954 # remove once 'modprobe --show-depends xts' shows ecb as a dependency
955 ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
956
957 # copy the cryptsetup binary and it's dependencies
958 boot.initrd.extraUtilsCommands =
959 let
960 extraUtils = config.system.build.extraUtils;
961
962 ipkgs = pkgs.appendOverlays [
963 (final: prev: {
964 tpm2-tss = prev.tpm2-tss.overrideAttrs (oldAttrs: {
965 doCheck = false;
966 patches = [];
967 postPatch = ''
968 patchShebangs script
969 '';
970 configureFlags = [];
971 });
972 })
973 ];
974 in ''
975 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
976 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
977 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
978
979 ${optionalString luks.yubikeySupport ''
980 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
981 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
982 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
983
984 cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
985 strip -s pbkdf2-sha512
986 copy_bin_and_libs pbkdf2-sha512
987
988 mkdir -p $out/etc/ssl
989 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
990
991 cat > $out/bin/openssl-wrap <<EOF
992 #!$out/bin/sh
993 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
994 $out/bin/openssl "\$@"
995 EOF
996 chmod +x $out/bin/openssl-wrap
997 ''}
998
999 ${optionalString luks.fido2Support ''
1000 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
1001 ''}
1002
1003
1004 ${optionalString luks.gpgSupport ''
1005 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
1006 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
1007 copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
1008
1009 ${concatMapStringsSep "\n" (x:
1010 if x.gpgCard != null then
1011 ''
1012 mkdir -p $out/secrets/gpg-keys/${x.device}
1013 cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
1014 cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
1015 ''
1016 else ""
1017 ) (attrValues luks.devices)
1018 }
1019 ''}
1020
1021 ${optionalString luks.clevisSupport ''
1022 for bin in ${ipkgs.tpm2-tools}/bin/* ${ipkgs.jose}/bin/* ${ipkgs.libpwquality}/bin/*; do
1023 if [ -L $bin ]; then
1024 cp -v $bin $out/bin
1025 else
1026 copy_bin_and_libs $bin
1027 fi
1028 done
1029
1030 copy_bin_and_libs ${ipkgs.bash}/bin/bash
1031 for bin in ${ipkgs.clevis}/bin/* ${ipkgs.clevis}/bin/.*; do
1032 [ -f $bin -o -L $bin ] || continue
1033
1034 substitute $bin $out/bin/$(basename $bin) \
1035 --replace ${ipkgs.bash}/bin $out/bin \
1036 --replace ${ipkgs.clevis}/bin $out/bin \
1037 --replace ${ipkgs.tpm2-tools}/bin $out/bin \
1038 --replace ${ipkgs.jose}/bin $out/bin \
1039 --replace ${ipkgs.libpwquality}/bin $out/bin \
1040 --replace ${ipkgs.coreutils}/bin $out/bin
1041
1042 [ -x $bin ] && chmod +x $out/bin/$(basename $bin)
1043 done
1044
1045 for lib in ${ipkgs.tpm2-tss}/lib/*.so*; do
1046 if [ -f $lib ]; then
1047 patchelf --output $out/lib/$(basename $lib) $lib \
1048 --set-rpath $out/lib
1049 else
1050 cp -pdv $lib $out/lib
1051 fi
1052 done
1053 ''}
1054
1055 ${optionalString luks.dmiSupport ''
1056 copy_bin_and_libs ${pkgs.dmidecode}/bin/dmidecode
1057 ''}
1058 '';
1059
1060 boot.initrd.extraUtilsCommandsTest = ''
1061 $out/bin/cryptsetup --version
1062 ${optionalString luks.yubikeySupport ''
1063 $out/bin/ykchalresp -V
1064 $out/bin/ykinfo -V
1065 $out/bin/openssl-wrap version
1066 ''}
1067 ${optionalString luks.gpgSupport ''
1068 $out/bin/gpg --version
1069 $out/bin/gpg-agent --version
1070 $out/bin/scdaemon --version
1071 ''}
1072 ${optionalString luks.fido2Support ''
1073 $out/bin/fido2luks --version
1074 ''}
1075 ${optionalString luks.clevisSupport ''
1076 $out/bin/jose alg
1077 ''}
1078 ${optionalString luks.dmiSupport ''
1079 $out/bin/dmidecode --version
1080 ''}
1081 '';
1082
1083 boot.initrd.preFailCommands = postCommands;
1084 boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
1085 boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
1086
1087 environment.systemPackages = [ pkgs.cryptsetup ];
1088 };
1089}
diff --git a/modules/stage-1/default.nix b/modules/stage-1/default.nix
deleted file mode 100644
index 5d49475f..00000000
--- a/modules/stage-1/default.nix
+++ /dev/null
@@ -1,669 +0,0 @@
1# This module builds the initial ramdisk, which contains an init
2# script that performs the first stage of booting the system: it loads
3# the modules necessary to mount the root file system, then calls the
4# init in the root file system to start the second boot stage.
5
6{ config, lib, utils, pkgs, ... }:
7
8with lib;
9
10let
11
12 udev = config.systemd.package;
13
14 kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
15
16 modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
17 firmware = config.hardware.firmware;
18
19
20 # Determine the set of modules that we need to mount the root FS.
21 modulesClosure = pkgs.makeModulesClosure {
22 rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
23 kernel = modulesTree;
24 firmware = firmware;
25 allowMissing = false;
26 };
27
28
29 # The initrd only has to mount `/` or any FS marked as necessary for
30 # booting (such as the FS containing `/nix/store`, or an FS needed for
31 # mounting `/`, like `/` on a loopback).
32 fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
33
34 # A utility for enumerating the shared-library dependencies of a program
35 findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" ''
36 set -euo pipefail
37
38 declare -A seen
39 left=()
40
41 patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf"
42
43 function add_needed {
44 rpath="$($patchelf --print-rpath $1)"
45 dir="$(dirname $1)"
46 for lib in $($patchelf --print-needed $1); do
47 left+=("$lib" "$rpath" "$dir")
48 done
49 }
50
51 add_needed "$1"
52
53 while [ ''${#left[@]} -ne 0 ]; do
54 next=''${left[0]}
55 rpath=''${left[1]}
56 ORIGIN=''${left[2]}
57 left=("''${left[@]:3}")
58 if [ -z ''${seen[$next]+x} ]; then
59 seen[$next]=1
60
61 # Ignore the dynamic linker which for some reason appears as a DT_NEEDED of glibc but isn't in glibc's RPATH.
62 case "$next" in
63 ld*.so.?) continue;;
64 esac
65
66 IFS=: read -ra paths <<< $rpath
67 res=
68 for path in "''${paths[@]}"; do
69 path=$(eval "echo $path")
70 if [ -f "$path/$next" ]; then
71 res="$path/$next"
72 echo "$res"
73 add_needed "$res"
74 break
75 fi
76 done
77 if [ -z "$res" ]; then
78 echo "Couldn't satisfy dependency $next" >&2
79 exit 1
80 fi
81 fi
82 done
83 '';
84
85 # Some additional utilities needed in stage 1, like mount, lvm, fsck
86 # etc. We don't want to bring in all of those packages, so we just
87 # copy what we need. Instead of using statically linked binaries,
88 # we just copy what we need from Glibc and use patchelf to make it
89 # work.
90 extraUtils = pkgs.runCommandCC "extra-utils"
91 { nativeBuildInputs = [pkgs.buildPackages.nukeReferences];
92 allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
93 }
94 ''
95 set +o pipefail
96
97 mkdir -p $out/bin $out/lib
98 ln -s $out/bin $out/sbin
99
100 copy_bin_and_libs () {
101 [ -f "$out/bin/$(basename $1)" ] && rm "$out/bin/$(basename $1)"
102 cp -pdv $1 $out/bin
103 }
104
105 # Copy BusyBox.
106 for BIN in ${pkgs.busybox}/{s,}bin/*; do
107 copy_bin_and_libs $BIN
108 done
109
110 # Copy some util-linux stuff.
111 copy_bin_and_libs ${pkgs.util-linux}/sbin/blkid
112
113 # Copy dmsetup and lvm.
114 copy_bin_and_libs ${getBin pkgs.lvm2}/bin/dmsetup
115 copy_bin_and_libs ${getBin pkgs.lvm2}/bin/lvm
116
117 # Add RAID mdadm tool.
118 copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
119 copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
120
121 # Copy udev.
122 copy_bin_and_libs ${udev}/bin/udevadm
123 copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl
124 for BIN in ${udev}/lib/udev/*_id; do
125 copy_bin_and_libs $BIN
126 done
127 # systemd-udevd is only a symlink to udevadm these days
128 ln -sf udevadm $out/bin/systemd-udevd
129
130 # Copy modprobe.
131 copy_bin_and_libs ${pkgs.kmod}/bin/kmod
132 ln -sf kmod $out/bin/modprobe
133
134 # Copy resize2fs if any ext* filesystems are to be resized
135 ${optionalString (any (fs: fs.autoResize && (lib.hasPrefix "ext" fs.fsType)) fileSystems) ''
136 # We need mke2fs in the initrd.
137 copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
138 ''}
139
140 # Copy secrets if needed.
141 #
142 # TODO: move out to a separate script; see #85000.
143 ${optionalString (!config.boot.loader.supportsInitrdSecrets)
144 (concatStringsSep "\n" (mapAttrsToList (dest: source:
145 let source' = if source == null then dest else source; in
146 ''
147 mkdir -p $(dirname "$out/secrets/${dest}")
148 # Some programs (e.g. ssh) doesn't like secrets to be
149 # symlinks, so we use `cp -L` here to match the
150 # behaviour when secrets are natively supported.
151 cp -Lr ${source'} "$out/secrets/${dest}"
152 ''
153 ) config.boot.initrd.secrets))
154 }
155
156 ${config.boot.initrd.extraUtilsCommands}
157
158 # Copy ld manually since it isn't detected correctly
159 cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
160
161 # Copy all of the needed libraries
162 find $out/bin $out/lib -type f | while read BIN; do
163 echo "Copying libs for executable $BIN"
164 for LIB in $(${findLibs}/bin/find-libs $BIN); do
165 TGT="$out/lib/$(basename $LIB)"
166 if [ ! -f "$TGT" ]; then
167 SRC="$(readlink -e $LIB)"
168 cp -pdv "$SRC" "$TGT"
169 fi
170 done
171 done
172
173 # Strip binaries further than normal.
174 chmod -R u+w $out
175 stripDirs "$STRIP" "lib bin" "-s"
176
177 # Run patchelf to make the programs refer to the copied libraries.
178 find $out/bin $out/lib -type f | while read i; do
179 if ! test -L $i; then
180 nuke-refs -e $out $i
181 fi
182 done
183
184 find $out/bin -type f | while read i; do
185 if ! test -L $i; then
186 echo "patching $i..."
187 patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
188 fi
189 done
190
191 if [ -z "${toString (pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform)}" ]; then
192 # Make sure that the patchelf'ed binaries still work.
193 echo "testing patched programs..."
194 $out/bin/ash -c 'echo hello world' | grep "hello world"
195 export LD_LIBRARY_PATH=$out/lib
196 $out/bin/mount --help 2>&1 | grep -q "BusyBox"
197 $out/bin/blkid -V 2>&1 | grep -q 'libblkid'
198 $out/bin/udevadm --version
199 $out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
200 LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM"
201 $out/bin/mdadm --version
202
203 ${config.boot.initrd.extraUtilsCommandsTest}
204 fi
205 ''; # */
206
207
208 # Networkd link files are used early by udev to set up interfaces early.
209 # This must be done in stage 1 to avoid race conditions between udev and
210 # network daemons.
211 linkUnits = pkgs.runCommand "link-units" {
212 allowedReferences = [ extraUtils ];
213 preferLocalBuild = true;
214 } (''
215 mkdir -p $out
216 cp -v ${udev}/lib/systemd/network/*.link $out/
217 '' + (
218 let
219 links = filterAttrs (n: v: hasSuffix ".link" n) config.systemd.network.units;
220 files = mapAttrsToList (n: v: "${v.unit}/${n}") links;
221 in
222 concatMapStringsSep "\n" (file: "cp -v ${file} $out/") files
223 ));
224
225 udevRules = pkgs.runCommand "udev-rules" {
226 allowedReferences = [ extraUtils ];
227 preferLocalBuild = true;
228 } ''
229 mkdir -p $out
230
231 echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules
232
233 cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
234 cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
235 cp -v ${udev}/lib/udev/rules.d/75-net-description.rules $out/
236 cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/
237 cp -v ${udev}/lib/udev/rules.d/80-net-setup-link.rules $out/
238 cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/
239 ${config.boot.initrd.extraUdevRulesCommands}
240
241 for i in $out/*.rules; do
242 substituteInPlace $i \
243 --replace ata_id ${extraUtils}/bin/ata_id \
244 --replace scsi_id ${extraUtils}/bin/scsi_id \
245 --replace cdrom_id ${extraUtils}/bin/cdrom_id \
246 --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
247 --replace ${pkgs.util-linux}/bin/blkid ${extraUtils}/bin/blkid \
248 --replace ${getBin pkgs.lvm2}/bin ${extraUtils}/bin \
249 --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
250 --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
251 --replace ${udev} ${extraUtils}
252 done
253
254 # Work around a bug in QEMU, which doesn't implement the "READ
255 # DISC INFORMATION" SCSI command:
256 # https://bugzilla.redhat.com/show_bug.cgi?id=609049
257 # As a result, `cdrom_id' doesn't print
258 # ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the
259 # /dev/disk/by-label symlinks from being created. We need these
260 # in the NixOS installation CD, so use ID_CDROM_MEDIA in the
261 # corresponding udev rules for now. This was the behaviour in
262 # udev <= 154. See also
263 # http://www.spinics.net/lists/hotplug/msg03935.html
264 substituteInPlace $out/60-persistent-storage.rules \
265 --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
266 ''; # */
267
268
269 # The init script of boot stage 1 (loading kernel modules for
270 # mounting the root FS).
271 bootStage1 = pkgs.substituteAll {
272 src = ./stage-1-init.sh;
273
274 shell = "${extraUtils}/bin/ash";
275
276 isExecutable = true;
277
278 postInstall = ''
279 echo checking syntax
280 # check both with bash
281 ${pkgs.buildPackages.bash}/bin/sh -n $target
282 # and with ash shell, just in case
283 ${pkgs.buildPackages.busybox}/bin/ash -n $target
284 '';
285
286 inherit linkUnits udevRules extraUtils modulesClosure;
287
288 inherit (config.boot) resumeDevice;
289
290 inherit (config.system.build) earlyMountScript;
291
292 inherit (config.boot.initrd) checkJournalingFS verbose
293 preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
294
295 resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
296 (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
297 # Don't include zram devices
298 && !(hasPrefix "/dev/zram" sd.device)
299 ) config.swapDevices);
300
301 fsInfo =
302 let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType (builtins.concatStringsSep "," fs.options) ];
303 in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
304
305 setHostId = optionalString (config.networking.hostId != null) ''
306 hi="${config.networking.hostId}"
307 ${if pkgs.stdenv.isBigEndian then ''
308 echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid
309 '' else ''
310 echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid
311 ''}
312 '';
313 };
314
315
316 # The closure of the init script of boot stage 1 is what we put in
317 # the initial RAM disk.
318 initialRamdisk = pkgs.makeInitrd {
319 name = "initrd-${kernel-name}";
320 inherit (config.boot.initrd) compressor compressorArgs prepend;
321
322 contents =
323 [ { object = bootStage1;
324 symlink = "/init";
325 }
326 { object = pkgs.writeText "mdadm.conf" config.boot.initrd.mdadmConf;
327 symlink = "/etc/mdadm.conf";
328 }
329 { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
330 src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
331 preferLocalBuild = true;
332 } ''
333 target=$out
334 ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
335 '';
336 symlink = "/etc/modprobe.d/ubuntu.conf";
337 }
338 { object = pkgs.kmod-debian-aliases;
339 symlink = "/etc/modprobe.d/debian.conf";
340 }
341 ];
342 };
343
344 # Script to add secret files to the initrd at bootloader update time
345 initialRamdiskSecretAppender =
346 let
347 compressorExe = initialRamdisk.compressorExecutableFunction pkgs;
348 in pkgs.writeScriptBin "append-initrd-secrets"
349 ''
350 #!${pkgs.bash}/bin/bash -e
351 function usage {
352 echo "USAGE: $0 INITRD_FILE" >&2
353 echo "Appends this configuration's secrets to INITRD_FILE" >&2
354 }
355
356 if [ $# -ne 1 ]; then
357 usage
358 exit 1
359 fi
360
361 if [ "$1"x = "--helpx" ]; then
362 usage
363 exit 0
364 fi
365
366 ${lib.optionalString (config.boot.initrd.secrets == {})
367 "exit 0"}
368
369 export PATH=${pkgs.coreutils}/bin:${pkgs.cpio}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
370
371 function cleanup {
372 if [ -n "$tmp" -a -d "$tmp" ]; then
373 rm -fR "$tmp"
374 fi
375 }
376 trap cleanup EXIT
377
378 tmp=$(mktemp -d initrd-secrets.XXXXXXXXXX)
379
380 ${lib.concatStringsSep "\n" (mapAttrsToList (dest: source:
381 let source' = if source == null then dest else toString source; in
382 ''
383 mkdir -p $(dirname "$tmp/${dest}")
384 cp -aL ${source'} "$tmp/${dest}"
385 ''
386 ) config.boot.initrd.secrets)
387 }
388
389 (cd "$tmp" && find . -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null) | \
390 ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
391 '';
392
393in
394
395{
396 disabledModules = [ "system/boot/stage-1.nix" ];
397
398 options = {
399
400 boot.resumeDevice = mkOption {
401 type = types.str;
402 default = "";
403 example = "/dev/sda3";
404 description = ''
405 Device for manual resume attempt during boot. This should be used primarily
406 if you want to resume from file. If left empty, the swap partitions are used.
407 Specify here the device where the file resides.
408 You should also use <varname>boot.kernelParams</varname> to specify
409 <literal><replaceable>resume_offset</replaceable></literal>.
410 '';
411 };
412
413 boot.initrd.enable = mkOption {
414 type = types.bool;
415 default = !config.boot.isContainer;
416 defaultText = "!config.boot.isContainer";
417 description = ''
418 Whether to enable the NixOS initial RAM disk (initrd). This may be
419 needed to perform some initialisation tasks (like mounting
420 network/encrypted file systems) before continuing the boot process.
421 '';
422 };
423
424 boot.initrd.prepend = mkOption {
425 default = [ ];
426 type = types.listOf types.str;
427 description = ''
428 Other initrd files to prepend to the final initrd we are building.
429 '';
430 };
431
432 boot.initrd.checkJournalingFS = mkOption {
433 default = true;
434 type = types.bool;
435 description = ''
436 Whether to run <command>fsck</command> on journaling filesystems such as ext3.
437 '';
438 };
439
440 boot.initrd.mdadmConf = mkOption {
441 default = "";
442 type = types.lines;
443 description = ''
444 Contents of <filename>/etc/mdadm.conf</filename> in stage 1.
445 '';
446 };
447
448 boot.initrd.preLVMCommands = mkOption {
449 default = "";
450 type = types.lines;
451 description = ''
452 Shell commands to be executed immediately before LVM discovery.
453 '';
454 };
455
456 boot.initrd.preDeviceCommands = mkOption {
457 default = "";
458 type = types.lines;
459 description = ''
460 Shell commands to be executed before udev is started to create
461 device nodes.
462 '';
463 };
464
465 boot.initrd.postDeviceCommands = mkOption {
466 default = "";
467 type = types.lines;
468 description = ''
469 Shell commands to be executed immediately after stage 1 of the
470 boot has loaded kernel modules and created device nodes in
471 <filename>/dev</filename>.
472 '';
473 };
474
475 boot.initrd.postMountCommands = mkOption {
476 default = "";
477 type = types.lines;
478 description = ''
479 Shell commands to be executed immediately after the stage 1
480 filesystems have been mounted.
481 '';
482 };
483
484 boot.initrd.preFailCommands = mkOption {
485 default = "";
486 type = types.lines;
487 description = ''
488 Shell commands to be executed before the failure prompt is shown.
489 '';
490 };
491
492 boot.initrd.extraUtilsCommands = mkOption {
493 internal = true;
494 default = "";
495 type = types.lines;
496 description = ''
497 Shell commands to be executed in the builder of the
498 extra-utils derivation. This can be used to provide
499 additional utilities in the initial ramdisk.
500 '';
501 };
502
503 boot.initrd.extraUtilsCommandsTest = mkOption {
504 internal = true;
505 default = "";
506 type = types.lines;
507 description = ''
508 Shell commands to be executed in the builder of the
509 extra-utils derivation after patchelf has done its
510 job. This can be used to test additional utilities
511 copied in extraUtilsCommands.
512 '';
513 };
514
515 boot.initrd.extraUdevRulesCommands = mkOption {
516 internal = true;
517 default = "";
518 type = types.lines;
519 description = ''
520 Shell commands to be executed in the builder of the
521 udev-rules derivation. This can be used to add
522 additional udev rules in the initial ramdisk.
523 '';
524 };
525
526 boot.initrd.compressor = mkOption {
527 default = (
528 if lib.versionAtLeast config.boot.kernelPackages.kernel.version "5.9"
529 then "zstd"
530 else "gzip"
531 );
532 defaultText = "zstd if the kernel supports it (5.9+), gzip if not.";
533 type = types.unspecified; # We don't have a function type...
534 description = ''
535 The compressor to use on the initrd image. May be any of:
536
537 <itemizedlist>
538 <listitem><para>The name of one of the predefined compressors, see <filename>pkgs/build-support/kernel/initrd-compressor-meta.nix</filename> for the definitions.</para></listitem>
539 <listitem><para>A function which, given the nixpkgs package set, returns the path to a compressor tool, e.g. <literal>pkgs: "''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
540 <listitem><para>(not recommended, because it does not work when cross-compiling) the full path to a compressor tool, e.g. <literal>"''${pkgs.pigz}/bin/pigz"</literal></para></listitem>
541 </itemizedlist>
542
543 The given program should read data from stdin and write it to stdout compressed.
544 '';
545 example = "xz";
546 };
547
548 boot.initrd.compressorArgs = mkOption {
549 default = null;
550 type = types.nullOr (types.listOf types.str);
551 description = "Arguments to pass to the compressor for the initrd image, or null to use the compressor's defaults.";
552 };
553
554 boot.initrd.secrets = mkOption
555 { default = {};
556 type = types.attrsOf (types.nullOr types.path);
557 description =
558 ''
559 Secrets to append to the initrd. The attribute name is the
560 path the secret should have inside the initrd, the value
561 is the path it should be copied from (or null for the same
562 path inside and out).
563 '';
564 example = literalExpression
565 ''
566 { "/etc/dropbear/dropbear_rsa_host_key" =
567 ./secret-dropbear-key;
568 }
569 '';
570 };
571
572 boot.initrd.supportedFilesystems = mkOption {
573 default = [ ];
574 example = [ "btrfs" ];
575 type = types.listOf types.str;
576 description = "Names of supported filesystem types in the initial ramdisk.";
577 };
578
579 boot.initrd.verbose = mkOption {
580 default = true;
581 type = types.bool;
582 description =
583 ''
584 Verbosity of the initrd. Please note that disabling verbosity removes
585 only the mandatory messages generated by the NixOS scripts. For a
586 completely silent boot, you might also want to set the two following
587 configuration options:
588
589 <itemizedlist>
590 <listitem><para><literal>boot.consoleLogLevel = 0;</literal></para></listitem>
591 <listitem><para><literal>boot.kernelParams = [ "quiet" "udev.log_priority=3" ];</literal></para></listitem>
592 </itemizedlist>
593 '';
594 };
595
596 boot.loader.supportsInitrdSecrets = mkOption
597 { internal = true;
598 default = false;
599 type = types.bool;
600 description =
601 ''
602 Whether the bootloader setup runs append-initrd-secrets.
603 If not, any needed secrets must be copied into the initrd
604 and thus added to the store.
605 '';
606 };
607
608 fileSystems = mkOption {
609 type = with lib.types; attrsOf (submodule {
610 options.neededForBoot = mkOption {
611 default = false;
612 type = types.bool;
613 description = ''
614 If set, this file system will be mounted in the initial ramdisk.
615 Note that the file system will always be mounted in the initial
616 ramdisk if its mount point is one of the following:
617 ${concatStringsSep ", " (
618 forEach utils.pathsNeededForBoot (i: "<filename>${i}</filename>")
619 )}.
620 '';
621 };
622 });
623 };
624
625 };
626
627 config = mkIf config.boot.initrd.enable {
628 assertions = [
629 { assertion = any (fs: fs.mountPoint == "/") fileSystems;
630 message = "The ‘fileSystems’ option does not specify your root file system.";
631 }
632 { assertion = let inherit (config.boot) resumeDevice; in
633 resumeDevice == "" || builtins.substring 0 1 resumeDevice == "/";
634 message = "boot.resumeDevice has to be an absolute path."
635 + " Old \"x:y\" style is no longer supported.";
636 }
637 # TODO: remove when #85000 is fixed
638 { assertion = !config.boot.loader.supportsInitrdSecrets ->
639 all (source:
640 builtins.isPath source ||
641 (builtins.isString source && hasPrefix builtins.storeDir source))
642 (attrValues config.boot.initrd.secrets);
643 message = ''
644 boot.loader.initrd.secrets values must be unquoted paths when
645 using a bootloader that doesn't natively support initrd
646 secrets, e.g.:
647
648 boot.initrd.secrets = {
649 "/etc/secret" = /path/to/secret;
650 };
651
652 Note that this will result in all secrets being stored
653 world-readable in the Nix store!
654 '';
655 }
656 ];
657
658 system.build =
659 { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; };
660
661 system.requiredKernelConfig = with config.lib.kernelConfig; [
662 (isYes "TMPFS")
663 (isYes "BLK_DEV_INITRD")
664 ];
665
666 boot.initrd.supportedFilesystems = map (fs: fs.fsType) fileSystems;
667
668 };
669}
diff --git a/modules/stage-1/stage-1-init.sh b/modules/stage-1/stage-1-init.sh
deleted file mode 100644
index ddaf9858..00000000
--- a/modules/stage-1/stage-1-init.sh
+++ /dev/null
@@ -1,638 +0,0 @@
1#! @shell@
2
3targetRoot=/mnt-root
4console=tty1
5verbose="@verbose@"
6
7info() {
8 if [[ -n "$verbose" ]]; then
9 echo "$@"
10 fi
11}
12
13extraUtils="@extraUtils@"
14export LD_LIBRARY_PATH=@extraUtils@/lib
15export PATH=@extraUtils@/bin
16ln -s @extraUtils@/bin /bin
17
18# Copy the secrets to their needed location
19if [ -d "@extraUtils@/secrets" ]; then
20 for secret in $(cd "@extraUtils@/secrets"; find . -type f); do
21 mkdir -p $(dirname "/$secret")
22 ln -s "@extraUtils@/secrets/$secret" "$secret"
23 done
24fi
25
26# Stop LVM complaining about fd3
27export LVM_SUPPRESS_FD_WARNINGS=true
28
29fail() {
30 if [ -n "$panicOnFail" ]; then exit 1; fi
31
32 @preFailCommands@
33
34 # If starting stage 2 failed, allow the user to repair the problem
35 # in an interactive shell.
36 cat <<EOF
37
38An error occurred in stage 1 of the boot process, which must mount the
39root filesystem on \`$targetRoot' and then start stage 2. Press one
40of the following keys:
41
42EOF
43 if [ -n "$allowShell" ]; then cat <<EOF
44 i) to launch an interactive shell
45 f) to start an interactive shell having pid 1 (needed if you want to
46 start stage 2's init manually)
47EOF
48 fi
49 cat <<EOF
50 r) to reboot immediately
51 *) to ignore the error and continue
52EOF
53
54 read -n 1 reply
55
56 if [ -n "$allowShell" -a "$reply" = f ]; then
57 exec setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console"
58 elif [ -n "$allowShell" -a "$reply" = i ]; then
59 echo "Starting interactive shell..."
60 setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
61 elif [ "$reply" = r ]; then
62 echo "Rebooting..."
63 reboot -f
64 else
65 info "Continuing..."
66 fi
67}
68
69trap 'fail' 0
70
71
72# Print a greeting.
73info
74info "<<< NixOS Stage 1 >>>"
75info
76
77# Make several required directories.
78mkdir -p /etc/udev
79touch /etc/fstab # to shut up mount
80ln -s /proc/mounts /etc/mtab # to shut up mke2fs
81touch /etc/udev/hwdb.bin # to shut up udev
82touch /etc/initrd-release
83
84# Function for waiting a device to appear.
85waitDevice() {
86 local device="$1"
87
88 # USB storage devices tend to appear with some delay. It would be
89 # great if we had a way to synchronously wait for them, but
90 # alas... So just wait for a few seconds for the device to
91 # appear.
92 if test ! -e $device; then
93 echo -n "waiting for device $device to appear..."
94 try=20
95 while [ $try -gt 0 ]; do
96 sleep 1
97 # also re-try lvm activation now that new block devices might have appeared
98 lvm vgchange -ay
99 # and tell udev to create nodes for the new LVs
100 udevadm trigger --action=add
101 if test -e $device; then break; fi
102 echo -n "."
103 try=$((try - 1))
104 done
105 echo
106 [ $try -ne 0 ]
107 fi
108}
109
110# Mount special file systems.
111specialMount() {
112 local device="$1"
113 local mountPoint="$2"
114 local options="$3"
115 local fsType="$4"
116
117 mkdir -m 0755 -p "$mountPoint"
118 mount -n -t "$fsType" -o "$options" "$device" "$mountPoint"
119}
120source @earlyMountScript@
121
122# Log the script output to /dev/kmsg or /run/log/stage-1-init.log.
123mkdir -p /tmp
124mkfifo /tmp/stage-1-init.log.fifo
125logOutFd=8 && logErrFd=9
126eval "exec $logOutFd>&1 $logErrFd>&2"
127if test -w /dev/kmsg; then
128 tee -i < /tmp/stage-1-init.log.fifo /proc/self/fd/"$logOutFd" | while read -r line; do
129 if test -n "$line"; then
130 echo "<7>stage-1-init: [$(date)] $line" > /dev/kmsg
131 fi
132 done &
133else
134 mkdir -p /run/log
135 tee -i < /tmp/stage-1-init.log.fifo /run/log/stage-1-init.log &
136fi
137exec > /tmp/stage-1-init.log.fifo 2>&1
138
139
140# Process the kernel command line.
141export stage2Init=/init
142for o in $(cat /proc/cmdline); do
143 case $o in
144 console=*)
145 set -- $(IFS==; echo $o)
146 params=$2
147 set -- $(IFS=,; echo $params)
148 console=$1
149 ;;
150 init=*)
151 set -- $(IFS==; echo $o)
152 stage2Init=$2
153 ;;
154 boot.persistence=*)
155 set -- $(IFS==; echo $o)
156 persistence=$2
157 ;;
158 boot.persistence.opt=*)
159 set -- $(IFS==; echo $o)
160 persistence_opt=$2
161 ;;
162 boot.trace|debugtrace)
163 # Show each command.
164 set -x
165 ;;
166 boot.shell_on_fail)
167 allowShell=1
168 ;;
169 boot.debug1|debug1) # stop right away
170 allowShell=1
171 fail
172 ;;
173 boot.debug1devices) # stop after loading modules and creating device nodes
174 allowShell=1
175 debug1devices=1
176 ;;
177 boot.debug1mounts) # stop after mounting file systems
178 allowShell=1
179 debug1mounts=1
180 ;;
181 boot.panic_on_fail|stage1panic=1)
182 panicOnFail=1
183 ;;
184 root=*)
185 # If a root device is specified on the kernel command
186 # line, make it available through the symlink /dev/root.
187 # Recognise LABEL= and UUID= to support UNetbootin.
188 set -- $(IFS==; echo $o)
189 if [ $2 = "LABEL" ]; then
190 root="/dev/disk/by-label/$3"
191 elif [ $2 = "UUID" ]; then
192 root="/dev/disk/by-uuid/$3"
193 else
194 root=$2
195 fi
196 ln -s "$root" /dev/root
197 ;;
198 copytoram)
199 copytoram=1
200 ;;
201 findiso=*)
202 # if an iso name is supplied, try to find the device where
203 # the iso resides on
204 set -- $(IFS==; echo $o)
205 isoPath=$2
206 ;;
207 esac
208done
209
210# Set hostid before modules are loaded.
211# This is needed by the spl/zfs modules.
212@setHostId@
213
214# Load the required kernel modules.
215mkdir -p /lib
216ln -s @modulesClosure@/lib/modules /lib/modules
217ln -s @modulesClosure@/lib/firmware /lib/firmware
218echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
219for i in @kernelModules@; do
220 info "loading module $(basename $i)..."
221 modprobe $i
222done
223
224
225# Create device nodes in /dev.
226@preDeviceCommands@
227info "running udev..."
228ln -sfn /proc/self/fd /dev/fd
229ln -sfn /proc/self/fd/0 /dev/stdin
230ln -sfn /proc/self/fd/1 /dev/stdout
231ln -sfn /proc/self/fd/2 /dev/stderr
232mkdir -p /etc/systemd
233ln -sfn @linkUnits@ /etc/systemd/network
234mkdir -p /etc/udev
235ln -sfn @udevRules@ /etc/udev/rules.d
236mkdir -p /dev/.mdadm
237systemd-udevd --daemon
238udevadm trigger --action=add
239udevadm settle
240
241
242# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered
243@preLVMCommands@
244
245info "starting device mapper and LVM..."
246lvm vgchange -ay
247
248if test -n "$debug1devices"; then fail; fi
249
250
251@postDeviceCommands@
252
253
254# Return true if the machine is on AC power, or if we can't determine
255# whether it's on AC power.
256onACPower() {
257 ! test -d "/proc/acpi/battery" ||
258 ! ls /proc/acpi/battery/BAT[0-9]* > /dev/null 2>&1 ||
259 ! cat /proc/acpi/battery/BAT*/state | grep "^charging state" | grep -q "discharg"
260}
261
262
263# Check the specified file system, if appropriate.
264checkFS() {
265 local device="$1"
266 local fsType="$2"
267
268 # Only check block devices.
269 if [ ! -b "$device" ]; then return 0; fi
270
271 # Don't check ROM filesystems.
272 if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi
273
274 # Don't check resilient COWs as they validate the fs structures at mount time
275 if [ "$fsType" = btrfs -o "$fsType" = zfs -o "$fsType" = bcachefs ]; then return 0; fi
276
277 # Skip fsck for nilfs2 - not needed by design and no fsck tool for this filesystem.
278 if [ "$fsType" = nilfs2 ]; then return 0; fi
279
280 # Skip fsck for inherently readonly filesystems.
281 if [ "$fsType" = squashfs ]; then return 0; fi
282
283 # If we couldn't figure out the FS type, then skip fsck.
284 if [ "$fsType" = auto ]; then
285 echo 'cannot check filesystem with type "auto"!'
286 return 0
287 fi
288
289 # Device might be already mounted manually
290 # e.g. NBD-device or the host filesystem of the file which contains encrypted root fs
291 if mount | grep -q "^$device on "; then
292 echo "skip checking already mounted $device"
293 return 0
294 fi
295
296 # Optionally, skip fsck on journaling filesystems. This option is
297 # a hack - it's mostly because e2fsck on ext3 takes much longer to
298 # recover the journal than the ext3 implementation in the kernel
299 # does (minutes versus seconds).
300 if test -z "@checkJournalingFS@" -a \
301 \( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \
302 -o "$fsType" = xfs -o "$fsType" = jfs -o "$fsType" = f2fs \)
303 then
304 return 0
305 fi
306
307 # Don't run `fsck' if the machine is on battery power. !!! Is
308 # this a good idea?
309 if ! onACPower; then
310 echo "on battery power, so no \`fsck' will be performed on \`$device'"
311 return 0
312 fi
313
314 echo "checking $device..."
315
316 fsckFlags=
317 if test "$fsType" != "btrfs"; then
318 fsckFlags="-V -a"
319 fi
320 fsck $fsckFlags "$device"
321 fsckResult=$?
322
323 if test $(($fsckResult | 2)) = $fsckResult; then
324 echo "fsck finished, rebooting..."
325 sleep 3
326 reboot -f
327 fi
328
329 if test $(($fsckResult | 4)) = $fsckResult; then
330 echo "$device has unrepaired errors, please fix them manually."
331 fail
332 fi
333
334 if test $fsckResult -ge 8; then
335 echo "fsck on $device failed."
336 fail
337 fi
338
339 return 0
340}
341
342
343# Function for mounting a file system.
344mountFS() {
345 local device="$1"
346 local mountPoint="$2"
347 local options="$3"
348 local fsType="$4"
349
350 if [ "$fsType" = auto ]; then
351 fsType=$(blkid -o value -s TYPE "$device")
352 if [ -z "$fsType" ]; then fsType=auto; fi
353 fi
354
355 # Filter out x- options, which busybox doesn't do yet.
356 local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)"
357 # Prefix (lower|upper|work)dir with /mnt-root (overlayfs)
358 local optionsPrefixed="$( echo "$optionsFiltered" | sed -E 's#\<(lowerdir|upperdir|workdir)=#\1=/mnt-root#g' )"
359
360 echo "$device /mnt-root$mountPoint $fsType $optionsPrefixed" >> /etc/fstab
361
362 checkFS "$device" "$fsType"
363
364 # Optionally resize the filesystem.
365 case $options in
366 *x-nixos.autoresize*)
367 if [ "$fsType" = ext2 -o "$fsType" = ext3 -o "$fsType" = ext4 ]; then
368 modprobe "$fsType"
369 echo "resizing $device..."
370 e2fsck -fp "$device"
371 resize2fs "$device"
372 elif [ "$fsType" = f2fs ]; then
373 echo "resizing $device..."
374 fsck.f2fs -fp "$device"
375 resize.f2fs "$device"
376 fi
377 ;;
378 esac
379
380 # Create backing directories for overlayfs
381 if [ "$fsType" = overlay ]; then
382 for i in upper work; do
383 dir="$( echo "$optionsPrefixed" | grep -o "${i}dir=[^,]*" )"
384 mkdir -m 0700 -p "${dir##*=}"
385 done
386 fi
387
388 info "mounting $device on $mountPoint..."
389
390 mkdir -p "/mnt-root$mountPoint"
391
392 # For ZFS and CIFS mounts, retry a few times before giving up.
393 # We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383.
394 local n=0
395 while true; do
396 mount "/mnt-root$mountPoint" && break
397 if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi
398 echo "retrying..."
399 sleep 1
400 n=$((n + 1))
401 done
402
403 [ "$mountPoint" == "/" ] &&
404 [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] &&
405 lustrateRoot "/mnt-root"
406
407 true
408}
409
410lustrateRoot () {
411 local root="$1"
412
413 echo
414 echo -e "\e[1;33m<<< NixOS is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m"
415 echo
416
417 mkdir -m 0755 -p "$root/old-root.tmp"
418
419 echo
420 echo "Moving impurities out of the way:"
421 for d in "$root"/*
422 do
423 [ "$d" == "$root/nix" ] && continue
424 [ "$d" == "$root/boot" ] && continue # Don't render the system unbootable
425 [ "$d" == "$root/old-root.tmp" ] && continue
426
427 mv -v "$d" "$root/old-root.tmp"
428 done
429
430 # Use .tmp to make sure subsequent invokations don't clash
431 mv -v "$root/old-root.tmp" "$root/old-root"
432
433 mkdir -m 0755 -p "$root/etc"
434 touch "$root/etc/NIXOS"
435
436 exec 4< "$root/old-root/etc/NIXOS_LUSTRATE"
437
438 echo
439 echo "Restoring selected impurities:"
440 while read -u 4 keeper; do
441 dirname="$(dirname "$keeper")"
442 mkdir -m 0755 -p "$root/$dirname"
443 cp -av "$root/old-root/$keeper" "$root/$keeper"
444 done
445
446 exec 4>&-
447}
448
449
450
451if test -e /sys/power/resume -a -e /sys/power/disk; then
452 if test -n "@resumeDevice@" && waitDevice "@resumeDevice@"; then
453 resumeDev="@resumeDevice@"
454 resumeInfo="$(udevadm info -q property "$resumeDev" )"
455 else
456 for sd in @resumeDevices@; do
457 # Try to detect resume device. According to Ubuntu bug:
458 # https://bugs.launchpad.net/ubuntu/+source/pm-utils/+bug/923326/comments/1
459 # when there are multiple swap devices, we can't know where the hibernate
460 # image will reside. We can check all of them for swsuspend blkid.
461 if waitDevice "$sd"; then
462 resumeInfo="$(udevadm info -q property "$sd")"
463 if [ "$(echo "$resumeInfo" | sed -n 's/^ID_FS_TYPE=//p')" = "swsuspend" ]; then
464 resumeDev="$sd"
465 break
466 fi
467 fi
468 done
469 fi
470 if test -n "$resumeDev"; then
471 resumeMajor="$(echo "$resumeInfo" | sed -n 's/^MAJOR=//p')"
472 resumeMinor="$(echo "$resumeInfo" | sed -n 's/^MINOR=//p')"
473 echo "$resumeMajor:$resumeMinor" > /sys/power/resume 2> /dev/null || echo "failed to resume..."
474 fi
475fi
476
477# If we have a path to an iso file, find the iso and link it to /dev/root
478if [ -n "$isoPath" ]; then
479 mkdir -p /findiso
480
481 for delay in 5 10; do
482 blkid | while read -r line; do
483 device=$(echo "$line" | sed 's/:.*//')
484 type=$(echo "$line" | sed 's/.*TYPE="\([^"]*\)".*/\1/')
485
486 mount -t "$type" "$device" /findiso
487 if [ -e "/findiso$isoPath" ]; then
488 ln -sf "/findiso$isoPath" /dev/root
489 break 2
490 else
491 umount /findiso
492 fi
493 done
494
495 sleep "$delay"
496 done
497fi
498
499# Try to find and mount the root device.
500mkdir -p $targetRoot
501
502exec 3< @fsInfo@
503
504while read -u 3 mountPoint; do
505 read -u 3 device
506 read -u 3 fsType
507 read -u 3 options
508
509 # !!! Really quick hack to support bind mounts, i.e., where the
510 # "device" should be taken relative to /mnt-root, not /. Assume
511 # that every device that starts with / but doesn't start with /dev
512 # is a bind mount.
513 pseudoDevice=
514 case $device in
515 /dev/*)
516 ;;
517 //*)
518 # Don't touch SMB/CIFS paths.
519 pseudoDevice=1
520 ;;
521 /*)
522 device=/mnt-root$device
523 ;;
524 *)
525 # Not an absolute path; assume that it's a pseudo-device
526 # like an NFS path (e.g. "server:/path").
527 pseudoDevice=1
528 ;;
529 esac
530
531 if test -z "$pseudoDevice" && ! waitDevice "$device"; then
532 # If it doesn't appear, try to mount it anyway (and
533 # probably fail). This is a fallback for non-device "devices"
534 # that we don't properly recognise.
535 echo "Timed out waiting for device $device, trying to mount anyway."
536 fi
537
538 # Wait once more for the udev queue to empty, just in case it's
539 # doing something with $device right now.
540 udevadm settle
541
542 # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs.
543 if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then
544 fsType=$(blkid -o value -s TYPE "$device")
545 fsSize=$(blockdev --getsize64 "$device")
546
547 mkdir -p /tmp-iso
548 mount -t "$fsType" /dev/root /tmp-iso
549 mountFS tmpfs /iso size="$fsSize" tmpfs
550
551 cp -r /tmp-iso/* /mnt-root/iso/
552
553 umount /tmp-iso
554 rmdir /tmp-iso
555 continue
556 fi
557
558 if [ "$mountPoint" = / ] && [ "$device" = tmpfs ] && [ ! -z "$persistence" ]; then
559 echo persistence...
560 waitDevice "$persistence"
561 echo enabling persistence...
562 mountFS "$persistence" "$mountPoint" "$persistence_opt" "auto"
563 continue
564 fi
565
566 mountFS "$device" "$mountPoint" "$options" "$fsType"
567done
568
569exec 3>&-
570
571
572@postMountCommands@
573
574
575# Emit a udev rule for /dev/root to prevent systemd from complaining.
576if [ -e /mnt-root/iso ]; then
577 eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=/mnt-root/iso)
578else
579 eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=$targetRoot)
580fi
581if [ "$ROOT_MAJOR" -a "$ROOT_MINOR" -a "$ROOT_MAJOR" != 0 ]; then
582 mkdir -p /run/udev/rules.d
583 echo 'ACTION=="add|change", SUBSYSTEM=="block", ENV{MAJOR}=="'$ROOT_MAJOR'", ENV{MINOR}=="'$ROOT_MINOR'", SYMLINK+="root"' > /run/udev/rules.d/61-dev-root-link.rules
584fi
585
586
587# Stop udevd.
588udevadm control --exit
589
590# Reset the logging file descriptors.
591# Do this just before pkill, which will kill the tee process.
592exec 1>&$logOutFd 2>&$logErrFd
593eval "exec $logOutFd>&- $logErrFd>&-"
594
595# Kill any remaining processes, just to be sure we're not taking any
596# with us into stage 2. But keep storage daemons like unionfs-fuse.
597#
598# Storage daemons are distinguished by an @ in front of their command line:
599# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
600for pid in $(pgrep -v -f '^@'); do
601 # Make sure we don't kill kernel processes, see #15226 and:
602 # http://stackoverflow.com/questions/12213445/identifying-kernel-threads
603 readlink "/proc/$pid/exe" &> /dev/null || continue
604 # Try to avoid killing ourselves.
605 [ $pid -eq $$ ] && continue
606 kill -9 "$pid"
607done
608
609if test -n "$debug1mounts"; then fail; fi
610
611
612# Restore /proc/sys/kernel/modprobe to its original value.
613echo /sbin/modprobe > /proc/sys/kernel/modprobe
614
615
616# Start stage 2. `switch_root' deletes all files in the ramfs on the
617# current root. The path has to be valid in the chroot not outside.
618if [ ! -e "$targetRoot/$stage2Init" ]; then
619 stage2Check=${stage2Init}
620 while [ "$stage2Check" != "${stage2Check%/*}" ] && [ ! -L "$targetRoot/$stage2Check" ]; do
621 stage2Check=${stage2Check%/*}
622 done
623 if [ ! -L "$targetRoot/$stage2Check" ]; then
624 echo "stage 2 init script ($targetRoot/$stage2Init) not found"
625 fail
626 fi
627fi
628
629mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
630
631mount --move /proc $targetRoot/proc
632mount --move /sys $targetRoot/sys
633mount --move /dev $targetRoot/dev
634mount --move /run $targetRoot/run
635
636exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init"
637
638fail # should never be reached