@@ -58,6 +58,7 @@ hasWget=
58
58
hasCurl=
59
59
hasSetsid=
60
60
hasNixOSFacter=
61
+ remoteHomeDir=
61
62
62
63
tempDir=$( mktemp -d)
63
64
trap ' rm -rf "$tempDir"' EXIT
@@ -68,6 +69,53 @@ declare -A extraFilesOwnership=()
68
69
declare -a nixCopyOptions=()
69
70
declare -a sshArgs=(" -o" " IdentitiesOnly=yes" " -i" " $tempDir /nixos-anywhere" " -o" " UserKnownHostsFile=/dev/null" " -o" " StrictHostKeyChecking=no" )
70
71
72
+ breakpoint () {
73
+ (
74
+ set +x
75
+ echo " Breakpoint reached at line ${BASH_LINENO[0]} ."
76
+
77
+ # Create a temporary directory for debug files
78
+ debugTmpDir=$( mktemp -d /tmp/nixos-anywhere-debug.XXXXXX)
79
+ chmod 700 " $debugTmpDir " # Secure the directory
80
+
81
+ # Set up cleanup trap
82
+ # shellcheck disable=SC2064
83
+ trap " rm -rf \" $debugTmpDir \" " EXIT
84
+
85
+ # Save all variables (local and exported) to a file
86
+ (
87
+ set -o posix
88
+ set
89
+ ) > " $debugTmpDir /debug_vars.sh"
90
+
91
+ # Create the rcfile with explicit terminal handling
92
+ cat > " $debugTmpDir /debug_rcfile.sh" << EOF
93
+ # Source all variables
94
+ set +o posix
95
+ source "$debugTmpDir /debug_vars.sh"
96
+
97
+ # Force output to terminal
98
+ exec 2>/dev/tty
99
+ exec 1>/dev/tty
100
+ exec 0</dev/tty
101
+
102
+ # Show some helpful info
103
+ echo "Debug shell started. All variables from parent scope are available."
104
+ echo "Example: echo \\\$ tempDir"
105
+ echo "Type 'exit' to continue execution."
106
+
107
+ # Set a nice prompt
108
+ PS1="[DEBUG]> "
109
+ EOF
110
+
111
+ echo " Variables saved to $debugTmpDir /debug_vars.sh"
112
+ echo " Starting debug shell (redirecting to /dev/tty for interactivity)..."
113
+
114
+ # Start an interactive shell with explicit terminal redirection
115
+ bash --rcfile " $debugTmpDir /debug_rcfile.sh" < /dev/tty > /dev/tty 2>&1
116
+ )
117
+ }
118
+
71
119
showUsage () {
72
120
cat << USAGE
73
121
Usage: nixos-anywhere [options] [<ssh-host>]
@@ -423,9 +471,15 @@ runSshTimeout() {
423
471
timeout 10 ssh " ${sshArgs[@]} " " $sshConnection " " $@ "
424
472
}
425
473
runSsh () {
426
- # shellcheck disable=SC2029
427
- # We want to expand "$@" to get the command to run over SSH
428
- ssh " $sshTtyParam " " ${sshArgs[@]} " " $sshConnection " " $@ "
474
+ (
475
+ set +x
476
+ if [[ -n ${enableDebug} ]]; then
477
+ echo -e " \033[1;34mSSH COMMAND:\033[0m ssh $sshTtyParam ${sshArgs[*]} $sshConnection $* \n"
478
+ fi
479
+ # shellcheck disable=SC2029
480
+ # We want to expand "$@" to get the command to run over SSH
481
+ ssh " $sshTtyParam " " ${sshArgs[@]} " " $sshConnection " " $@ "
482
+ )
429
483
}
430
484
431
485
nixCopy () {
@@ -518,23 +572,34 @@ importFacts() {
518
572
if ! facts=$( runSsh -o ConnectTimeout=10 enableDebug=$enableDebug sh -- < " $here " /get-facts.sh) ; then
519
573
exit 1
520
574
fi
521
- filteredFacts=$( echo " $facts " | grep -E ' ^(has|is)[A-Za-z0-9_]+=\S+' )
575
+ filteredFacts=$( echo " $facts " | grep -E ' ^(has|is|remote )[A-Za-z0-9_]+=\S+' )
522
576
if [[ -z $filteredFacts ]]; then
523
577
abort " Retrieving host facts via SSH failed. Check with --debug for the root cause, unless you have done so already"
524
578
fi
579
+
580
+ # disable debug output temporarily to prevent log spam
581
+ set +x
582
+
525
583
# make facts available in script
526
584
# shellcheck disable=SC2046
527
585
export $( echo " $filteredFacts " | xargs)
528
586
529
587
# Necessary to prevent Bash erroring before printing out which fact had an issue
530
588
set +u
531
- for var in isOs isArch isInstaller isContainer isRoot hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid; do
589
+ for var in isOs isArch isInstaller isContainer isRoot hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid remoteHomeDir ; do
532
590
if [[ -z ${! var} ]]; then
533
591
abort " Failed to retrieve fact $var from host"
534
592
fi
535
593
done
536
594
set -u
537
595
596
+ # Compute the log file path from the home directory
597
+ remoteLogFile=" ${remoteHomeDir} /.nixos-anywhere.log"
598
+
599
+ if [[ -n ${enableDebug} ]]; then
600
+ set -x
601
+ fi
602
+
538
603
if [[ ${isRoot} == " y" ]]; then
539
604
maybeSudo=
540
605
elif [[ ${hasSudo} == " y" ]]; then
@@ -657,14 +722,60 @@ runKexec() {
657
722
kexecUrl=${kexecUrl/ " github.com" / " gh-v6.com" }
658
723
fi
659
724
725
+ if [[ -z $remoteLogFile ]]; then
726
+ abort " Could not create a temporary log file for $sshUser "
727
+ fi
728
+
729
+ # Unified kexec error handling function
730
+ handleKexecResult () {
731
+ local exitCode=$1
732
+ local operation=$2
733
+
734
+ if [[ $exitCode -eq 0 ]]; then
735
+ echo " $operation completed successfully" >&2
736
+ else
737
+ # If operation failed, try to fetch the log file
738
+ local logContent=" "
739
+ if logContent=$(
740
+ set +x
741
+ runSsh " cat \" $remoteLogFile \" 2>/dev/null" 2> /dev/null
742
+ ) ; then
743
+ echo " Remote output log:" >&2
744
+ echo " $logContent " >&2
745
+ fi
746
+ echo " $operation failed" >&2
747
+ exit 1
748
+ fi
749
+ }
750
+
660
751
# Define common remote commands template
661
752
local remoteCommandTemplate
662
753
remoteCommandTemplate="
754
+ ${enableDebug: +set -x}
755
+ # Create a script that we can run with sudo
756
+ kexec_script_tmp=\$ (mktemp /tmp/kexec-script.XXXXXX.sh)
757
+ trap 'rm -f \"\$ kexec_script_tmp\" ' EXIT
758
+ cat > \"\$ kexec_script_tmp\" << 'KEXEC_SCRIPT'
759
+ #!/usr/bin/env bash
663
760
set -eu ${enableDebug}
664
- ${maybeSudo} rm -rf /root/kexec
665
- ${maybeSudo} mkdir -p /root/kexec
666
- %TAR_COMMAND%
667
- TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags $( printf ' %q ' " $kexecExtraFlags " )
761
+ rm -rf /root/kexec
762
+ mkdir -p /root/kexec
763
+ cd /root/kexec
764
+ echo 'Downloading kexec tarball (this may take a moment)...'
765
+ # Execute tar command
766
+ %TAR_COMMAND% && TMPDIR=/root/kexec setsid --wait /root/kexec/kexec/run --kexec-extra-flags $( printf ' %q ' " $kexecExtraFlags " )
767
+ KEXEC_SCRIPT
768
+
769
+ # Run the script and let output flow naturally
770
+ ${maybeSudo} bash \"\$ kexec_script_tmp\" 2>&1 | tee \" $remoteLogFile \" || true
771
+ # The script will likely disconnect us, so we consider it successful if we see the kexec message
772
+ if grep -q 'machine will boot into nixos' \" $remoteLogFile \" ; then
773
+ echo 'Kexec initiated successfully'
774
+ exit 0
775
+ else
776
+ echo 'Kexec may have failed - check output above'
777
+ exit 1
778
+ fi
668
779
"
669
780
670
781
# Define upload commands
@@ -694,21 +805,50 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
694
805
localUploadCommand=(curl --fail -Ss -L " ${kexecUrl} " )
695
806
fi
696
807
697
- local tarCommand
698
- local remoteCommands
808
+ # If no local upload command is defined, we use the remote command to download and execute
699
809
if [[ ${# localUploadCommand[@]} -eq 0 ]]; then
700
810
# Use remote command for download and execution
701
- tarCommand=" $( printf ' %q ' " ${remoteUploadCommand[@]} " ) | ${maybeSudo} tar -C /root/kexec -xv ${tarDecomp} "
702
-
811
+ local tarCommand
812
+ tarCommand=" $( printf ' %q ' " ${remoteUploadCommand[@]} " ) | tar -xv ${tarDecomp} "
813
+ local remoteCommands
703
814
remoteCommands=${remoteCommandTemplate// ' %TAR_COMMAND%' / $tarCommand }
704
815
705
- runSsh sh -c " $( printf ' %q' " $remoteCommands " ) "
816
+ # Run the SSH command - for kexec with sudo, we expect it might disconnect
817
+ local sshExitCode
818
+ (
819
+ set +x
820
+ runSsh sh -c " $( printf ' %q' " $remoteCommands " ) "
821
+ )
822
+ sshExitCode=$?
823
+
824
+ handleKexecResult $sshExitCode " Kexec"
706
825
else
826
+ # Why do we need $remoteHomeDir?
827
+ # In the case where the ssh user is not root, we need to upload the kexec tarball
828
+ # to a location where the user has write permissions. We then use sudo to run
829
+ # kexec from that location.
830
+ if [[ -z $remoteHomeDir ]]; then
831
+ abort " Could not determine home directory for user $sshUser "
832
+ fi
833
+
834
+ (
835
+ set +x
836
+ " ${localUploadCommand[@]} " | runSsh " cat > \" $remoteHomeDir \" /kexec-tarball.tar.gz"
837
+ )
838
+
707
839
# Use local command with pipe to remote
708
- tarCommand=" ${maybeSudo} tar -C /root/kexec -xv ${tarDecomp} "
709
- remoteCommands=${remoteCommandTemplate// ' %TAR_COMMAND%' / $tarCommand }
840
+ local tarCommand=" cat \" $remoteHomeDir \" /kexec-tarball.tar.gz | tar -xv ${tarDecomp} "
841
+ local remoteCommands=${remoteCommandTemplate// ' %TAR_COMMAND%' / $tarCommand }
842
+
843
+ # Execute the local upload command and check for success
844
+ local uploadExitCode
845
+ (
846
+ set +x
847
+ runSsh sh -c " $( printf ' %q' " $remoteCommands " ) "
848
+ )
849
+ uploadExitCode=$?
710
850
711
- " ${localUploadCommand[@]} " | runSsh sh -c " $( printf ' %q ' " $remoteCommands " ) "
851
+ handleKexecResult $uploadExitCode " Upload "
712
852
fi
713
853
714
854
# use the default SSH port to connect at this point
@@ -876,6 +1016,12 @@ main() {
876
1016
sshUser=$( echo " $sshSettings " | awk ' /^user / { print $2 }' )
877
1017
sshHost=" ${sshConnection//*@/ } "
878
1018
1019
+ # If kexec phase is not present, we assume kexec has already been run
1020
+ # and change the user to root@<sshHost> for the rest of the script.
1021
+ if [[ ${phases[kexec]} != 1 ]]; then
1022
+ sshConnection=" root@${sshHost} "
1023
+ fi
1024
+
879
1025
uploadSshKey
880
1026
881
1027
importFacts
0 commit comments