@@ -58,6 +58,7 @@ hasWget=
5858hasCurl=
5959hasSetsid=
6060hasNixOSFacter=
61+ remoteHomeDir=
6162
6263tempDir=$( mktemp -d)
6364trap ' rm -rf "$tempDir"' EXIT
@@ -68,6 +69,53 @@ declare -A extraFilesOwnership=()
6869declare -a nixCopyOptions=()
6970declare -a sshArgs=(" -o" " IdentitiesOnly=yes" " -i" " $tempDir /nixos-anywhere" " -o" " UserKnownHostsFile=/dev/null" " -o" " StrictHostKeyChecking=no" )
7071
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+
71119showUsage () {
72120 cat << USAGE
73121Usage: nixos-anywhere [options] [<ssh-host>]
@@ -423,9 +471,15 @@ runSshTimeout() {
423471 timeout 10 ssh " ${sshArgs[@]} " " $sshConnection " " $@ "
424472}
425473runSsh () {
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+ )
429483}
430484
431485nixCopy () {
@@ -518,23 +572,34 @@ importFacts() {
518572 if ! facts=$( runSsh -o ConnectTimeout=10 enableDebug=$enableDebug sh -- < " $here " /get-facts.sh) ; then
519573 exit 1
520574 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+' )
522576 if [[ -z $filteredFacts ]]; then
523577 abort " Retrieving host facts via SSH failed. Check with --debug for the root cause, unless you have done so already"
524578 fi
579+
580+ # disable debug output temporarily to prevent log spam
581+ set +x
582+
525583 # make facts available in script
526584 # shellcheck disable=SC2046
527585 export $( echo " $filteredFacts " | xargs)
528586
529587 # Necessary to prevent Bash erroring before printing out which fact had an issue
530588 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
532590 if [[ -z ${! var} ]]; then
533591 abort " Failed to retrieve fact $var from host"
534592 fi
535593 done
536594 set -u
537595
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+
538603 if [[ ${isRoot} == " y" ]]; then
539604 maybeSudo=
540605 elif [[ ${hasSudo} == " y" ]]; then
@@ -657,14 +722,60 @@ runKexec() {
657722 kexecUrl=${kexecUrl/ " github.com" / " gh-v6.com" }
658723 fi
659724
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+
660751 # Define common remote commands template
661752 local remoteCommandTemplate
662753 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
663760set -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
668779"
669780
670781 # Define upload commands
@@ -694,21 +805,50 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
694805 localUploadCommand=(curl --fail -Ss -L " ${kexecUrl} " )
695806 fi
696807
697- local tarCommand
698- local remoteCommands
808+ # If no local upload command is defined, we use the remote command to download and execute
699809 if [[ ${# localUploadCommand[@]} -eq 0 ]]; then
700810 # 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
703814 remoteCommands=${remoteCommandTemplate// ' %TAR_COMMAND%' / $tarCommand }
704815
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"
706825 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+
707839 # 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=$?
710850
711- " ${localUploadCommand[@]} " | runSsh sh -c " $( printf ' %q ' " $remoteCommands " ) "
851+ handleKexecResult $uploadExitCode " Upload "
712852 fi
713853
714854 # use the default SSH port to connect at this point
@@ -876,6 +1016,12 @@ main() {
8761016 sshUser=$( echo " $sshSettings " | awk ' /^user / { print $2 }' )
8771017 sshHost=" ${sshConnection//*@/ } "
8781018
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+
8791025 uploadSshKey
8801026
8811027 importFacts
0 commit comments