@@ -21,6 +21,7 @@ nixOptions=(
21
21
" --no-write-lock-file"
22
22
)
23
23
SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY-}
24
+ SUDO_PASSWORD=${SUDO_PASSWORD-}
24
25
25
26
declare -A phases
26
27
phases[kexec]=1
@@ -53,6 +54,7 @@ hasTar=
53
54
hasCpio=
54
55
hasSudo=
55
56
hasDoas=
57
+ hasPasswordlessSudo=
56
58
hasWget=
57
59
hasCurl=
58
60
hasSetsid=
@@ -91,7 +93,9 @@ Options:
91
93
print full build logs
92
94
* --env-password
93
95
set a password used by ssh-copy-id, the password should be set by
94
- the environment variable SSHPASS
96
+ the environment variable SSHPASS. Additionally, sudo password can be set
97
+ via SUDO_PASSWORD environment variable for remote sudo operations
98
+ (only supported with sudo, not doas).
95
99
* -s, --store-paths <disko-script> <nixos-system>
96
100
set the store paths to the disko-script and nixos-system directly
97
101
if this is given, flake is not needed
@@ -247,6 +251,10 @@ parseArgs() {
247
251
;;
248
252
--debug)
249
253
enableDebug=" -x"
254
+ if [[ ${SUDO_PASSWORD} != " " ]]; then
255
+ echo " WARNING: Debug mode enabled with SUDO_PASSWORD. Password authentication may interfere with debug output." >&2
256
+ sleep 2
257
+ fi
250
258
printBuildLogs=y
251
259
set -x
252
260
;;
@@ -420,6 +428,106 @@ runSsh() {
420
428
ssh " $sshTtyParam " " ${sshArgs[@]} " " $sshConnection " " $@ "
421
429
}
422
430
431
+ # Helper function to authenticate sudo with password if needed
432
+ maybeSudo () {
433
+ # Early return if no command provided and no sudo password
434
+ if [[ $# -eq 0 && -z ${SUDO_PASSWORD} ]]; then
435
+ return
436
+ fi
437
+
438
+ # Use 'true' as default command if none provided but we have sudo password
439
+ local cmd=(" ${@:- true} " )
440
+
441
+ if [[ -n ${SUDO_PASSWORD} ]] && [[ ${maybeSudoCommand} == " sudo" ]]; then
442
+ # If debug is enabled and we have a sudo password, warn about potential issues
443
+
444
+ # Use sudo with password authentication - pipe password to all sudo commands
445
+ printf " printf %%s %q | sudo -S " " $SUDO_PASSWORD "
446
+ printf ' %q ' " ${cmd[@]} "
447
+ elif [[ -n ${maybeSudoCommand} ]]; then
448
+ printf ' %s ' " ${maybeSudoCommand} "
449
+ printf ' %q ' " ${cmd[@]} "
450
+ else
451
+ # No sudo command needed (e.g., already root after kexec)
452
+ printf ' %q ' " ${cmd[@]} "
453
+ fi
454
+ echo
455
+ }
456
+
457
+ # Test and cache sudo password if needed
458
+ testAndCacheSudoPassword () {
459
+ # Skip if no sudo command available
460
+ if [[ -z ${maybeSudoCommand} ]]; then
461
+ return 0
462
+ fi
463
+
464
+ # Skip if using doas (doesn't support password authentication)
465
+ if [[ ${maybeSudoCommand} == " doas" ]]; then
466
+ return 0
467
+ fi
468
+
469
+ # Skip if sudo works without password
470
+ if [[ ${hasPasswordlessSudo} == " y" ]]; then
471
+ step " Passwordless sudo confirmed"
472
+ return 0
473
+ fi
474
+
475
+ # If we already have a password supplied, trust it
476
+ if [[ -n ${SUDO_PASSWORD} ]]; then
477
+ step " Using supplied sudo password"
478
+ return 0
479
+ fi
480
+
481
+ # Only prompt for password in interactive sessions
482
+ if [[ -t 0 ]]; then
483
+ step " Sudo requires password authentication"
484
+ local attempts=0
485
+ local maxAttempts=5
486
+
487
+ while [[ $attempts -lt $maxAttempts ]]; do
488
+ echo -n " Enter sudo password for ${sshConnection} : "
489
+ read -rs password
490
+ echo
491
+
492
+ # Test the password
493
+ local testOutput
494
+ testOutput=$( runSshNoTty " echo $( printf %q " $password " ) | sudo -S echo 'SUDO_TEST_SUCCESS'" 2>&1 )
495
+ if [[ $testOutput == * " SUDO_TEST_SUCCESS" * ]]; then
496
+ SUDO_PASSWORD=" $password "
497
+ step " Sudo password verified and cached"
498
+ return 0
499
+ else
500
+ (( attempts++ ))
501
+ if [[ $attempts -lt $maxAttempts ]]; then
502
+ echo " Invalid password, please try again ($attempts /$maxAttempts )"
503
+ fi
504
+ fi
505
+ done
506
+
507
+ abort " Failed to authenticate sudo after $maxAttempts attempts"
508
+ else
509
+ # Non-interactive session without working sudo
510
+ abort " Sudo requires password but running in non-interactive mode. Set SUDO_PASSWORD environment variable or configure passwordless sudo."
511
+ fi
512
+ }
513
+
514
+ urlEncode () {
515
+ local string=" ${1} "
516
+ local strlen=${# string}
517
+ local encoded=" "
518
+ local pos c o
519
+
520
+ for (( pos = 0 ; pos < strlen; pos++ )) ; do
521
+ c=${string: pos: 1}
522
+ case " $c " in
523
+ [-_.~a-zA-Z0-9]) o=" ${c} " ;;
524
+ * ) printf -v o ' %%%02x' " '$c " ;;
525
+ esac
526
+ encoded+=" ${o} "
527
+ done
528
+ echo " ${encoded} "
529
+ }
530
+
423
531
buildStoreUrl () {
424
532
local storeUrl=" $1 "
425
533
@@ -433,11 +541,19 @@ buildStoreUrl() {
433
541
fi
434
542
435
543
# Add remote-program parameter when sudo is needed
436
- if [[ -n ${maybeSudo} ]] && [[ $storeUrl == ssh-ng://* ]]; then
544
+ if [[ -n ${maybeSudoCommand} ]] && [[ $storeUrl == ssh-ng://* ]]; then
545
+ local remoteProgram
546
+ if [[ -n ${SUDO_PASSWORD} ]] && [[ ${maybeSudoCommand} == " sudo" ]]; then
547
+ # Use password authentication for nix-daemon
548
+ remoteProgram=" sh -c $( urlEncode " $( printf %s " $( printf ' %q' " $SUDO_PASSWORD " ) " | sudo -S nix-daemon) " ) "
549
+ else
550
+ remoteProgram=" ${maybeSudoCommand} ,nix-daemon"
551
+ fi
552
+
437
553
if [[ $storeUrl == * " ?" * ]]; then
438
- storeUrl=" ${storeUrl} &remote-program=${maybeSudo} nix-daemon "
554
+ storeUrl=" ${storeUrl} &remote-program=${remoteProgram} "
439
555
else
440
- storeUrl=" ${storeUrl} ?remote-program=${maybeSudo} nix-daemon "
556
+ storeUrl=" ${storeUrl} ?remote-program=${remoteProgram} "
441
557
fi
442
558
fi
443
559
@@ -575,7 +691,7 @@ importFacts() {
575
691
# shellcheck disable=SC2046
576
692
export $( echo " $filteredFacts " | xargs)
577
693
578
- for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid; do
694
+ for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasPasswordlessSudo hasWget hasCurl hasSetsid; do
579
695
if [[ -z ${! var} ]]; then
580
696
abort " Failed to retrieve fact $var from host"
581
697
fi
@@ -626,20 +742,17 @@ checkBuildLocally() {
626
742
}
627
743
628
744
generateHardwareConfig () {
629
- local maybeSudo=" $maybeSudo "
630
745
mkdir -p " $( dirname " $hardwareConfigPath " ) "
631
746
case " $hardwareConfigBackend " in
632
747
nixos-facter)
633
748
if [[ ${isInstaller} == " y" ]]; then
634
749
if [[ ${hasNixOSFacter} == " n" ]]; then
635
750
abort " nixos-facter is not available in booted installer, use nixos-generate-config. For nixos-facter, you may want to boot an installer image from here instead: https://github.com/nix-community/nixos-images"
636
751
fi
637
- else
638
- maybeSudo=" "
639
752
fi
640
753
641
754
step " Generating hardware-configuration.nix using nixos-facter"
642
- runSshNoTty -o ConnectTimeout=10 ${ maybeSudo} " nixos-facter" > " $hardwareConfigPath "
755
+ runSshNoTty -o ConnectTimeout=10 " $( maybeSudo nixos-facter) " > " $hardwareConfigPath "
643
756
;;
644
757
nixos-generate-config)
645
758
step " Generating hardware-configuration.nix using nixos-generate-config"
@@ -693,10 +806,10 @@ runKexec() {
693
806
local remoteCommandTemplate
694
807
remoteCommandTemplate="
695
808
set -eu ${enableDebug}
696
- ${ maybeSudo} rm -rf /root/kexec
697
- ${ maybeSudo} mkdir -p /root/kexec
809
+ $( maybeSudo rm -rf /root/kexec)
810
+ $( maybeSudo mkdir -p /root/kexec)
698
811
%TAR_COMMAND%
699
- TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags $( printf ' %q ' " $kexecExtraFlags " )
812
+ $( maybeSudo TMPDIR=/root/kexec setsid --wait /root/kexec/kexec/run --kexec-extra-flags " $kexecExtraFlags " )
700
813
"
701
814
702
815
# Define upload commands
@@ -716,16 +829,17 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
716
829
717
830
local tarCommand
718
831
local remoteCommands
832
+
719
833
if [[ ${# localUploadCommand[@]} -eq 0 ]]; then
720
834
# Use remote command for download and execution
721
- tarCommand=" $( printf ' %q ' " ${remoteUploadCommand[@]} " ) | ${maybeSudo } tar -C /root/kexec -xvzf-"
835
+ tarCommand=" $( printf ' %q ' " ${remoteUploadCommand[@]} " ) | ${maybeSudoCommand } tar -C /root/kexec -xvzf-"
722
836
723
837
remoteCommands=${remoteCommandTemplate// ' %TAR_COMMAND%' / $tarCommand }
724
838
725
839
runSsh sh -c " $( printf ' %q' " $remoteCommands " ) "
726
840
else
727
841
# Use local command with pipe to remote
728
- tarCommand=" ${maybeSudo } tar -C /root/kexec -xvzf-"
842
+ tarCommand=" ${maybeSudoCommand } tar -C /root/kexec -xvzf-"
729
843
remoteCommands=${remoteCommandTemplate// ' %TAR_COMMAND%' / $tarCommand }
730
844
731
845
" ${localUploadCommand[@]} " | runSsh sh -c " $( printf ' %q' " $remoteCommands " ) "
@@ -746,7 +860,7 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
746
860
# After kexec we explicitly set the user to root@
747
861
sshConnection=" root@${sshHost} "
748
862
# After kexec, we're running as root in the NixOS installer, so no need for sudo
749
- maybeSudo =" "
863
+ maybeSudoCommand =" "
750
864
751
865
# waiting for machine to become available again
752
866
until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done
@@ -756,7 +870,7 @@ runDisko() {
756
870
local diskoScript=$1
757
871
for path in " ${! diskEncryptionKeys[@]} " ; do
758
872
step " Uploading ${diskEncryptionKeys[$path]} to $path "
759
- runSsh " ${ maybeSudo} sh -c $( printf ' %q' " umask 077; mkdir -p $( dirname " $path " ) ; cat > $path " ) " < " ${diskEncryptionKeys[$path]} "
873
+ runSsh " $( maybeSudo sh) -c $( printf ' %q' " umask 077; mkdir -p $( dirname " $path " ) ; cat > $path " ) " < " ${diskEncryptionKeys[$path]} "
760
874
done
761
875
if [[ -n ${diskoScript} ]]; then
762
876
nixCopy --to " ssh-ng://$sshConnection " " $diskoScript "
@@ -773,7 +887,7 @@ runDisko() {
773
887
fi
774
888
775
889
step Formatting hard drive with disko
776
- runSsh " ${ maybeSudo} $diskoScript "
890
+ runSsh " $( maybeSudo " $diskoScript " ) "
777
891
}
778
892
779
893
nixosInstall () {
@@ -796,45 +910,46 @@ nixosInstall() {
796
910
797
911
if [[ -n ${extraFiles} ]]; then
798
912
step Copying extra files
799
- tar -C " $extraFiles " -cpf- . | runSsh " ${maybeSudo } tar -C /mnt -xf- --no-same-owner"
913
+ tar -C " $extraFiles " -cpf- . | runSsh " ${maybeSudoCommand } tar -C /mnt -xf- --no-same-owner"
800
914
801
- runSsh " ${ maybeSudo} chmod 755 /mnt" # tar also changes permissions of /mnt
915
+ runSsh " $( maybeSudo chmod 755 /mnt) " # tar also changes permissions of /mnt
802
916
fi
803
917
804
918
if [[ ${# extraFilesOwnership[@]} -gt 0 ]]; then
805
919
# shellcheck disable=SC2016
806
- printf " %s\n" " ${! extraFilesOwnership[@]} " " ${extraFilesOwnership[@]} " | pr -2t | runSsh ' while read file ownership; do ' " ${ maybeSudo} " ' chown -R " $ownership" "/mnt/$file" ; done'
920
+ printf " %s\n" " ${! extraFilesOwnership[@]} " " ${extraFilesOwnership[@]} " | pr -2t | runSsh ' while read file ownership; do ' " $( maybeSudo chown -R \ $ ownership \ " /mnt/\ $ file\" ) " ' ; done'
807
921
fi
808
922
809
923
step Installing NixOS
924
+ # shellcheck disable=SC2016
810
925
runSsh sh << SSH
811
926
set -eu ${enableDebug}
812
927
# when running not in nixos we might miss this directory, but it's needed in the nixos chroot during installation
813
928
export PATH="\$ PATH:/run/current-system/sw/bin"
814
929
815
930
if [ ! -d "/mnt/tmp" ]; then
816
931
# needed for installation if initrd-secrets are used
817
- ${ maybeSudo} mkdir -p /mnt/tmp
818
- ${ maybeSudo} chmod 777 /mnt/tmp
932
+ $( maybeSudo mkdir -p /mnt/tmp)
933
+ $( maybeSudo chmod 777 /mnt/tmp)
819
934
fi
820
935
821
936
if [ ${copyHostKeys-n} = "y" ]; then
822
937
# NB we copy host keys that are in turn copied by kexec installer.
823
- ${ maybeSudo} mkdir -m 755 -p /mnt/etc/ssh
938
+ $( maybeSudo mkdir -m 755 -p /mnt/etc/ssh)
824
939
for p in /etc/ssh/ssh_host_*; do
825
940
# Skip if the source file does not exist (i.e. glob did not match any files)
826
941
# or the destination already exists (e.g. copied with --extra-files).
827
942
if [ ! -e "\$ p" ] || [ -e "/mnt/\$ p" ]; then
828
943
continue
829
944
fi
830
- ${ maybeSudo} cp -a " \$ p" " /mnt/\$ p"
945
+ $( maybeSudo cp -a ' $p ' ' /mnt/$p ' )
831
946
done
832
947
fi
833
948
# https://stackoverflow.com/a/13864829
834
949
if [ ! -z ${NIXOS_NO_CHECK+0} ]; then
835
950
export NIXOS_NO_CHECK
836
951
fi
837
- ${ maybeSudo} nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem "
952
+ $( maybeSudo nixos-install --no-root-passwd --no-channel-copy --system " $nixosSystem " )
838
953
SSH
839
954
840
955
}
@@ -844,11 +959,11 @@ nixosReboot() {
844
959
runSsh sh << SSH
845
960
if command -v zpool >/dev/null && [ "\$ (zpool list)" != "no pools available" ]; then
846
961
# we always want to export the zfs pools so people can boot from it without force import
847
- ${ maybeSudo} umount -Rv /mnt/
848
- ${ maybeSudo} swapoff -a
849
- ${ maybeSudo} zpool export -a || true
962
+ $( maybeSudo umount -Rv /mnt/)
963
+ $( maybeSudo swapoff -a)
964
+ $( maybeSudo zpool export -a || true)
850
965
fi
851
- ${ maybeSudo} nohup sh -c 'sleep 6 && reboot' >/dev/null &
966
+ $( maybeSudo nohup sh -c ' sleep 6 && reboot' ) >/dev/null &
852
967
SSH
853
968
854
969
step Waiting for the machine to become unreachable due to reboot
@@ -916,13 +1031,19 @@ main() {
916
1031
abort " no setsid command found, but required to run the kexec script under a new session"
917
1032
fi
918
1033
919
- maybeSudo =" "
1034
+ maybeSudoCommand =" "
920
1035
if [[ ${hasSudo-n} == " y" ]]; then
921
- maybeSudo =" sudo"
1036
+ maybeSudoCommand =" sudo"
922
1037
elif [[ ${hasDoas-n} == " y" ]]; then
923
- maybeSudo=" doas"
1038
+ maybeSudoCommand=" doas"
1039
+ if [[ -n ${SUDO_PASSWORD} ]]; then
1040
+ abort " SUDO_PASSWORD environment variable is not supported with doas. Please configure passwordless doas or use sudo instead."
1041
+ fi
924
1042
fi
925
1043
1044
+ # Test and cache sudo password if needed
1045
+ testAndCacheSudoPassword
1046
+
926
1047
if [[ ${isOs} != " Linux" ]]; then
927
1048
abort " This script requires Linux as the operating system, but got $isOs "
928
1049
fi
0 commit comments