@@ -23,8 +23,6 @@ Options:
23
23
* -s, --store-paths <disko-script> <nixos-system>
24
24
set the store paths to the disko-script and nixos-system directly
25
25
if this is given, flake is not needed
26
- * --no-reboot
27
- do not reboot after installation, allowing further customization of the target installation.
28
26
* --kexec <path>
29
27
use another kexec tarball to bootstrap NixOS
30
28
* --kexec-extra-flags
@@ -33,8 +31,6 @@ Options:
33
31
after kexec is executed, use a custom ssh port to connect. Defaults to 22
34
32
* --copy-host-keys
35
33
copy over existing /etc/ssh/ssh_host_* host keys to the installation
36
- * --stop-after-disko
37
- exit after disko formatting, you can then proceed to install manually or some other way
38
34
* --extra-files <path>
39
35
path to a directory to copy into the root of the new nixos installation.
40
36
Copied files will be owned by root.
@@ -53,6 +49,12 @@ Options:
53
49
build the closure on the remote machine instead of locally and copy-closuring it
54
50
* --vm-test
55
51
build the system and test the disk configuration inside a VM without installing it to the target.
52
+ * --phases
53
+ comma separated list of phases to run. Default is: kexec,disko,install,reboot
54
+ kexec: kexec into the nixos installer
55
+ disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode
56
+ install: install the system
57
+ reboot: reboot the machine
56
58
USAGE
57
59
}
58
60
@@ -69,11 +71,17 @@ here=$(dirname "${BASH_SOURCE[0]}")
69
71
kexec_url=" "
70
72
kexec_extra_flags=" "
71
73
enable_debug=" "
72
- maybe_reboot=" sleep 6 && reboot"
73
74
nix_options=(
74
75
--extra-experimental-features ' nix-command flakes'
75
76
" --no-write-lock-file"
76
77
)
78
+
79
+ declare -A phases
80
+ phases[kexec]=1
81
+ phases[disko]=1
82
+ phases[install]=1
83
+ phases[reboot]=1
84
+
77
85
substitute_on_destination=y
78
86
ssh_private_key_file=
79
87
if [ -t 0 ]; then # stdin is a tty, we allow interactive input to ssh i.e. passwords
@@ -151,11 +159,33 @@ while [[ $# -gt 0 ]]; do
151
159
shift
152
160
shift
153
161
;;
162
+ --phases)
163
+ phases[kexec]=0
164
+ phases[disko]=0
165
+ phases[install]=0
166
+ phases[reboot]=0
167
+ IFS=, read -r -a phase_list <<< " $2"
168
+ for phase in " ${phase_list[@]} " ; do
169
+ if [[ ${phases[$phase]:- unset} == unset ]]; then
170
+ abort " Unknown phase: $phase "
171
+ fi
172
+ phases[$phase ]=1
173
+ done
174
+ shift
175
+ ;;
154
176
--stop-after-disko)
155
- stop_after_disko=y
177
+ echo " WARNING: --stop-after-disko is deprecated, use --phases=kexec,disko instead" 2>&1
178
+ phases[kexec]=1
179
+ phases[disko]=1
180
+ phases[install]=0
181
+ phases[reboot]=0
156
182
;;
157
183
--no-reboot)
158
- maybe_reboot=" "
184
+ echo " WARNING: --no-reboot is deprecated, use --phases=kexec,disko,install instead" 2>&1
185
+ phases[kexec]=1
186
+ phases[disko]=1
187
+ phases[install]=1
188
+ phases[reboot]=0
159
189
;;
160
190
--from)
161
191
nix_copy_options+=(" --from" " $2 " )
328
358
sleep 3
329
359
done
330
360
331
- import_facts () {
361
+ importFacts () {
332
362
local facts filtered_facts
333
363
if ! facts=$( ssh_ -o ConnectTimeout=10 enable_debug=$enable_debug sh -- < " $here " /get-facts.sh) ; then
334
364
exit 1
@@ -343,7 +373,7 @@ import_facts() {
343
373
}
344
374
345
375
step Gathering machine facts
346
- import_facts
376
+ importFacts
347
377
348
378
if [[ ${has_tar-n} == " n" ]]; then
349
379
abort " no tar command found, but required to unpack kexec tarball"
@@ -364,132 +394,114 @@ if [[ ${is_os-n} != "Linux" ]]; then
364
394
abort " This script requires Linux as the operating system, but got $is_os "
365
395
fi
366
396
367
- if [[ ${is_kexec-n} == " n" ]] && [[ ${is_installer-n} == " n" ]]; then
368
- if [[ ${is_container-none} != " none" ]]; then
369
- echo " WARNING: This script does not support running from a '${is_container} ' container. kexec will likely not work" >&2
370
- fi
397
+ runKexec () {
398
+ if [[ ${is_kexec-n} == " n" ]] && [[ ${is_installer-n} == " n" ]]; then
399
+ if [[ ${is_container-none} != " none" ]]; then
400
+ echo " WARNING: This script does not support running from a '${is_container} ' container. kexec will likely not work" >&2
401
+ fi
371
402
372
- if [[ $kexec_url == " " ]]; then
373
- case " ${is_arch-unknown} " in
374
- x86_64 | aarch64)
375
- kexec_url=" https://github.com/nix-community/nixos-images/releases/download/nixos-24.05/nixos-kexec-installer-noninteractive-${is_arch} -linux.tar.gz"
376
- ;;
377
- * )
378
- abort " Unsupported architecture: ${is_arch} . Our default kexec images only support x86_64 and aarch64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information."
379
- ;;
380
- esac
381
- fi
403
+ if [[ $kexec_url == " " ]]; then
404
+ case " ${is_arch-unknown} " in
405
+ x86_64 | aarch64)
406
+ kexec_url=" https://github.com/nix-community/nixos-images/releases/download/nixos-24.05/nixos-kexec-installer-noninteractive-${is_arch} -linux.tar.gz"
407
+ ;;
408
+ * )
409
+ abort " Unsupported architecture: ${is_arch} . Our default kexec images only support x86_64 and aarch64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information."
410
+ ;;
411
+ esac
412
+ fi
382
413
383
- step Switching system into kexec
384
- ssh_ sh << SSH
414
+ step Switching system into kexec
415
+ ssh_ sh << SSH
385
416
set -efu ${enable_debug}
386
417
$maybe_sudo rm -rf /root/kexec
387
418
$maybe_sudo mkdir -p /root/kexec
388
419
SSH
389
420
390
- # no way to reach global ipv4 destinations, use gh-v6.com automatically if github url
391
- if [[ ${has_ipv6_only-n} == " y" ]] && [[ $kexec_url == " https://github.com/" * ]]; then
392
- kexec_url=${kexec_url/ " github.com" / " gh-v6.com" }
393
- fi
421
+ # no way to reach global ipv4 destinations, use gh-v6.com automatically if github url
422
+ if [[ ${has_ipv6_only-n} == " y" ]] && [[ $kexec_url == " https://github.com/" * ]]; then
423
+ kexec_url=${kexec_url/ " github.com" / " gh-v6.com" }
424
+ fi
394
425
395
- if [[ -f $kexec_url ]]; then
396
- ssh_ " ${maybe_sudo} tar -C /root/kexec -xvzf-" < " $kexec_url "
397
- elif [[ ${has_curl-n} == " y" ]]; then
398
- ssh_ " curl --fail -Ss -L '${kexec_url} ' | ${maybe_sudo} tar -C /root/kexec -xvzf-"
399
- elif [[ ${has_wget-n} == " y" ]]; then
400
- ssh_ " wget '${kexec_url} ' -O- | ${maybe_sudo} tar -C /root/kexec -xvzf-"
401
- else
402
- curl --fail -Ss -L " ${kexec_url} " | ssh_ " ${maybe_sudo} tar -C /root/kexec -xvzf-"
403
- fi
426
+ if [[ -f $kexec_url ]]; then
427
+ ssh_ " ${maybe_sudo} tar -C /root/kexec -xvzf-" < " $kexec_url "
428
+ elif [[ ${has_curl-n} == " y" ]]; then
429
+ ssh_ " curl --fail -Ss -L '${kexec_url} ' | ${maybe_sudo} tar -C /root/kexec -xvzf-"
430
+ elif [[ ${has_wget-n} == " y" ]]; then
431
+ ssh_ " wget '${kexec_url} ' -O- | ${maybe_sudo} tar -C /root/kexec -xvzf-"
432
+ else
433
+ curl --fail -Ss -L " ${kexec_url} " | ssh_ " ${maybe_sudo} tar -C /root/kexec -xvzf-"
434
+ fi
404
435
405
- ssh_ << SSH
436
+ ssh_ << SSH
406
437
TMPDIR=/root/kexec setsid ${maybe_sudo} /root/kexec/kexec/run --kexec-extra-flags "${kexec_extra_flags} "
407
438
SSH
408
439
409
- # use the default SSH port to connect at this point
410
- for i in " ${! ssh_args[@]} " ; do
411
- if [[ ${ssh_args[i]} == " -p" ]]; then
412
- ssh_args[i + 1]=$post_kexec_ssh_port
413
- break
414
- fi
415
- done
416
-
417
- # wait for machine to become unreachable.
418
- while timeout_ssh_ -- exit 0; do sleep 1; done
419
-
420
- # After kexec we explicitly set the user to root@
421
- ssh_connection=" root@${ssh_host} "
422
-
423
- # waiting for machine to become available again
424
- until ssh_ -o ConnectTimeout=10 -- exit 0; do sleep 5; done
425
- fi
440
+ # use the default SSH port to connect at this point
441
+ for i in " ${! ssh_args[@]} " ; do
442
+ if [[ ${ssh_args[i]} == " -p" ]]; then
443
+ ssh_args[i + 1]=$post_kexec_ssh_port
444
+ break
445
+ fi
446
+ done
426
447
427
- # Installation will fail if non-root user is used for installer.
428
- # Switch to root user by copying authorized_keys.
429
- if [[ ${is_installer-n} == " y" ]] && [[ ${ssh_user} != " root" ]]; then
430
- # Allow copy to fail if authorized_keys does not exist, like if using /etc/ssh/authorized_keys.d/
431
- ssh_ " ${maybe_sudo} mkdir -p /root/.ssh; ${maybe_sudo} cp ~/.ssh/authorized_keys /root/.ssh || true"
432
- ssh_connection=" root@${ssh_host} "
433
- fi
448
+ # wait for machine to become unreachable.
449
+ while timeout_ssh_ -- exit 0; do sleep 1; done
434
450
435
- for path in " ${! disk_encryption_keys[@]} " ; do
436
- step " Uploading ${disk_encryption_keys[$path]} to $path "
437
- ssh_ " umask 077; cat > $path " < " ${disk_encryption_keys[$path]} "
438
- done
451
+ # After kexec we explicitly set the user to root@
452
+ ssh_connection=" root@${ssh_host} "
439
453
440
- if [[ ${build_on_remote-n} == " y" ]]; then
441
- pubkey=$( ssh-keyscan -p " $ssh_port " -t ed25519 " $ssh_host " 2> /dev/null || {
442
- echo " ERROR: failed to retrieve host public key for ${ssh_connection} " >&2
443
- exit 1
444
- })
445
- pubkey=$( echo " $pubkey " | sed -e ' s/^[^ ]* //' | base64 -w0)
446
- fi
447
-
448
- if [[ -n ${disko_script-} ]]; then
449
- nix_copy --to " ssh://$ssh_connection " " $disko_script "
450
- elif [[ ${build_on_remote-n} == " y" ]]; then
451
- step Building disko script
452
- # We need to do a nix copy first because nix build doesn't have --no-check-sigs
453
- nix_copy --to " ssh-ng://$ssh_connection " " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.diskoScript" \
454
- --derivation --no-check-sigs
455
- disko_script=$(
456
- nix_build " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.diskoScript" \
457
- --eval-store auto --store " ssh-ng://$ssh_connection ?ssh-key=$ssh_key_dir /nixos-anywhere"
458
- )
459
- fi
454
+ # waiting for machine to become available again
455
+ until ssh_ -o ConnectTimeout=10 -- exit 0; do sleep 5; done
456
+ fi
457
+ }
460
458
461
- step Formatting hard drive with disko
462
- ssh_ " $disko_script "
459
+ runDisko () {
460
+ local disko_script=$1
461
+ for path in " ${! disk_encryption_keys[@]} " ; do
462
+ step " Uploading ${disk_encryption_keys[$path]} to $path "
463
+ ssh_ " umask 077; cat > $path " < " ${disk_encryption_keys[$path]} "
464
+ done
465
+ if [[ -n ${disko_script-} ]]; then
466
+ nix_copy --to " ssh://$ssh_connection " " $disko_script "
467
+ elif [[ ${build_on_remote-n} == " y" ]]; then
468
+ step Building disko script
469
+ # We need to do a nix copy first because nix build doesn't have --no-check-sigs
470
+ nix_copy --to " ssh-ng://$ssh_connection " " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.diskoScript" \
471
+ --derivation --no-check-sigs
472
+ disko_script=$(
473
+ nix_build " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.diskoScript" \
474
+ --eval-store auto --store " ssh-ng://$ssh_connection ?ssh-key=$ssh_key_dir /nixos-anywhere"
475
+ )
476
+ fi
463
477
464
- if [[ ${stop_after_disko-n} == " y" ]]; then
465
- # Should we also do this for `--no-reboot`?
466
- echo " WARNING: leaving temporary ssh key at '$ssh_key_dir /nixos-anywhere' to login to the machine" >&2
467
- trap - EXIT
468
- exit 0
469
- fi
478
+ step Formatting hard drive with disko
479
+ ssh_ " $disko_script "
480
+ }
470
481
471
- if [[ -n ${nixos_system-} ]]; then
472
- step Uploading the system closure
473
- nix_copy --to " ssh://$ssh_connection ?remote-store=local?root=/mnt" " $nixos_system "
474
- elif [[ ${build_on_remote-n} == " y" ]]; then
475
- step Building the system closure
476
- # We need to do a nix copy first because nix build doesn't have --no-check-sigs
477
- nix_copy --to " ssh-ng://$ssh_connection ?remote-store=local?root=/mnt" " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.toplevel" \
478
- --derivation --no-check-sigs
479
- nixos_system=$(
480
- nix_build " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.toplevel" \
481
- --eval-store auto --store " ssh-ng://$ssh_connection ?ssh-key=$ssh_key_dir /nixos-anywhere&remote-store=local?root=/mnt"
482
- )
483
- fi
482
+ nixosInstall () {
483
+ if [[ -n ${nixos_system-} ]]; then
484
+ step Uploading the system closure
485
+ nix_copy --to " ssh://$ssh_connection ?remote-store=local?root=/mnt" " $nixos_system "
486
+ elif [[ ${build_on_remote-n} == " y" ]]; then
487
+ step Building the system closure
488
+ # We need to do a nix copy first because nix build doesn't have --no-check-sigs
489
+ nix_copy --to " ssh-ng://$ssh_connection ?remote-store=local?root=/mnt" " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.toplevel" \
490
+ --derivation --no-check-sigs
491
+ nixos_system=$(
492
+ nix_build " ${flake} #nixosConfigurations.\" ${flakeAttr} \" .config.system.build.toplevel" \
493
+ --eval-store auto --store " ssh-ng://$ssh_connection ?ssh-key=$ssh_key_dir /nixos-anywhere&remote-store=local?root=/mnt"
494
+ )
495
+ fi
484
496
485
- if [[ -n ${extra_files-} ]]; then
486
- step Copying extra files
487
- tar -C " $extra_files " -cpf- . | ssh_ " ${maybe_sudo} tar -C /mnt -xf- --no-same-owner"
488
- ssh_ " chmod 755 /mnt" # tar also changes permissions of /mnt
489
- fi
497
+ if [[ -n ${extra_files-} ]]; then
498
+ step Copying extra files
499
+ tar -C " $extra_files " -cpf- . | ssh_ " ${maybe_sudo} tar -C /mnt -xf- --no-same-owner"
500
+ ssh_ " chmod 755 /mnt" # tar also changes permissions of /mnt
501
+ fi
490
502
491
- step Installing NixOS
492
- ssh_ sh << SSH
503
+ step Installing NixOS
504
+ ssh_ sh << SSH
493
505
set -eu ${enable_debug}
494
506
# when running not in nixos we might miss this directory, but it's needed in the nixos chroot during installation
495
507
export PATH="\$ PATH:/run/current-system/sw/bin"
@@ -515,12 +527,39 @@ if command -v zpool >/dev/null && [ "\$(zpool list)" != "no pools available" ];
515
527
umount -Rv /mnt/
516
528
zpool export -a || true
517
529
fi
518
- # We will reboot in background so we can cleanly finish the script before the hosts go down.
519
- # This makes integration into scripts easier
520
- nohup sh -c '${maybe_reboot} ' >/dev/null &
521
530
SSH
522
531
523
- if [[ -n ${maybe_reboot} ]]; then
532
+ }
533
+
534
+ if [[ ${phases[kexec]-} == 1 ]]; then
535
+ runKexec
536
+ fi
537
+
538
+ # Installation will fail if non-root user is used for installer.
539
+ # Switch to root user by copying authorized_keys.
540
+ if [[ ${is_installer-n} == " y" ]] && [[ ${ssh_user} != " root" ]]; then
541
+ # Allow copy to fail if authorized_keys does not exist, like if using /etc/ssh/authorized_keys.d/
542
+ ssh_ " ${maybe_sudo} mkdir -p /root/.ssh; ${maybe_sudo} cp ~/.ssh/authorized_keys /root/.ssh || true"
543
+ ssh_connection=" root@${ssh_host} "
544
+ fi
545
+
546
+ if [[ ${build_on_remote-n} == " y" ]]; then
547
+ pubkey=$( ssh-keyscan -p " $ssh_port " -t ed25519 " $ssh_host " 2> /dev/null || {
548
+ echo " ERROR: failed to retrieve host public key for ${ssh_connection} " >&2
549
+ exit 1
550
+ })
551
+ pubkey=$( echo " $pubkey " | sed -e ' s/^[^ ]* //' | base64 -w0)
552
+ fi
553
+
554
+ if [[ ${phases[disko]-} == 1 ]]; then
555
+ runDisko " $disko_script "
556
+ fi
557
+
558
+ if [[ ${phases[install]-} == 1 ]]; then
559
+ nixosInstall
560
+ fi
561
+
562
+ if [[ ${phases[reboot]-} == 1 ]]; then
524
563
step Waiting for the machine to become unreachable due to reboot
525
564
while timeout_ssh_ -- exit 0; do sleep 1; done
526
565
fi
0 commit comments