Skip to content

Commit 0f63320

Browse files
Mic92mergify[bot]
authored andcommitted
allow to disable/enable phases in nixos-anywhere
Instead of having flags like --no-reboot or --stop-after-disko, we now can specify all phases to run.
1 parent 0174074 commit 0f63320

File tree

1 file changed

+158
-119
lines changed

1 file changed

+158
-119
lines changed

src/nixos-anywhere.sh

Lines changed: 158 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ Options:
2323
* -s, --store-paths <disko-script> <nixos-system>
2424
set the store paths to the disko-script and nixos-system directly
2525
if this is given, flake is not needed
26-
* --no-reboot
27-
do not reboot after installation, allowing further customization of the target installation.
2826
* --kexec <path>
2927
use another kexec tarball to bootstrap NixOS
3028
* --kexec-extra-flags
@@ -33,8 +31,6 @@ Options:
3331
after kexec is executed, use a custom ssh port to connect. Defaults to 22
3432
* --copy-host-keys
3533
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
3834
* --extra-files <path>
3935
path to a directory to copy into the root of the new nixos installation.
4036
Copied files will be owned by root.
@@ -53,6 +49,12 @@ Options:
5349
build the closure on the remote machine instead of locally and copy-closuring it
5450
* --vm-test
5551
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
5658
USAGE
5759
}
5860

@@ -69,11 +71,17 @@ here=$(dirname "${BASH_SOURCE[0]}")
6971
kexec_url=""
7072
kexec_extra_flags=""
7173
enable_debug=""
72-
maybe_reboot="sleep 6 && reboot"
7374
nix_options=(
7475
--extra-experimental-features 'nix-command flakes'
7576
"--no-write-lock-file"
7677
)
78+
79+
declare -A phases
80+
phases[kexec]=1
81+
phases[disko]=1
82+
phases[install]=1
83+
phases[reboot]=1
84+
7785
substitute_on_destination=y
7886
ssh_private_key_file=
7987
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
151159
shift
152160
shift
153161
;;
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+
;;
154176
--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
156182
;;
157183
--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
159189
;;
160190
--from)
161191
nix_copy_options+=("--from" "$2")
@@ -328,7 +358,7 @@ do
328358
sleep 3
329359
done
330360

331-
import_facts() {
361+
importFacts() {
332362
local facts filtered_facts
333363
if ! facts=$(ssh_ -o ConnectTimeout=10 enable_debug=$enable_debug sh -- <"$here"/get-facts.sh); then
334364
exit 1
@@ -343,7 +373,7 @@ import_facts() {
343373
}
344374

345375
step Gathering machine facts
346-
import_facts
376+
importFacts
347377

348378
if [[ ${has_tar-n} == "n" ]]; then
349379
abort "no tar command found, but required to unpack kexec tarball"
@@ -364,132 +394,114 @@ if [[ ${is_os-n} != "Linux" ]]; then
364394
abort "This script requires Linux as the operating system, but got $is_os"
365395
fi
366396

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
371402

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
382413

383-
step Switching system into kexec
384-
ssh_ sh <<SSH
414+
step Switching system into kexec
415+
ssh_ sh <<SSH
385416
set -efu ${enable_debug}
386417
$maybe_sudo rm -rf /root/kexec
387418
$maybe_sudo mkdir -p /root/kexec
388419
SSH
389420

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
394425

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
404435

405-
ssh_ <<SSH
436+
ssh_ <<SSH
406437
TMPDIR=/root/kexec setsid ${maybe_sudo} /root/kexec/kexec/run --kexec-extra-flags "${kexec_extra_flags}"
407438
SSH
408439

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
426447

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
434450

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}"
439453

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+
}
460458

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
463477

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+
}
470481

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
484496

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
490502

491-
step Installing NixOS
492-
ssh_ sh <<SSH
503+
step Installing NixOS
504+
ssh_ sh <<SSH
493505
set -eu ${enable_debug}
494506
# when running not in nixos we might miss this directory, but it's needed in the nixos chroot during installation
495507
export PATH="\$PATH:/run/current-system/sw/bin"
@@ -515,12 +527,39 @@ if command -v zpool >/dev/null && [ "\$(zpool list)" != "no pools available" ];
515527
umount -Rv /mnt/
516528
zpool export -a || true
517529
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 &
521530
SSH
522531

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
524563
step Waiting for the machine to become unreachable due to reboot
525564
while timeout_ssh_ -- exit 0; do sleep 1; done
526565
fi

0 commit comments

Comments
 (0)