Skip to content

Commit 22ce6cb

Browse files
authored
Fix sudo password prompt (#573)
2 parents af70cbe + 197343e commit 22ce6cb

File tree

2 files changed

+164
-17
lines changed

2 files changed

+164
-17
lines changed

src/get-facts.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ hasWget=$(has wget)
2020
hasCurl=$(has curl)
2121
hasSetsid=$(has setsid)
2222
hasNixOSFacter=$(command -v nixos-facter >/dev/null && echo "y" || echo "n")
23+
remoteHomeDir=$HOME
2324
FACTS

src/nixos-anywhere.sh

Lines changed: 163 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ hasWget=
5858
hasCurl=
5959
hasSetsid=
6060
hasNixOSFacter=
61+
remoteHomeDir=
6162

6263
tempDir=$(mktemp -d)
6364
trap 'rm -rf "$tempDir"' EXIT
@@ -68,6 +69,53 @@ declare -A extraFilesOwnership=()
6869
declare -a nixCopyOptions=()
6970
declare -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+
71119
showUsage() {
72120
cat <<USAGE
73121
Usage: nixos-anywhere [options] [<ssh-host>]
@@ -423,9 +471,15 @@ runSshTimeout() {
423471
timeout 10 ssh "${sshArgs[@]}" "$sshConnection" "$@"
424472
}
425473
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+
)
429483
}
430484

431485
nixCopy() {
@@ -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
663760
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
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

Comments
 (0)