Skip to content

Fix sudo password prompt #573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 183 additions & 16 deletions src/nixos-anywhere.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,52 @@ declare -A extraFilesOwnership=()
declare -a nixCopyOptions=()
declare -a sshArgs=("-o" "IdentitiesOnly=yes" "-i" "$tempDir/nixos-anywhere" "-o" "UserKnownHostsFile=/dev/null" "-o" "StrictHostKeyChecking=no")

breakpoint() {
(
set +x
echo "Breakpoint reached at line ${BASH_LINENO[0]}."

# Create a temporary directory for debug files
debugTmpDir=$(mktemp -d /tmp/nixos-anywhere-debug.XXXXXX)
export debugTmpDir

# Set up cleanup trap
trap 'rm -rf "$debugTmpDir"' RETURN

# Save all variables (local and exported) to a file
(
set -o posix
set
) >"$debugTmpDir/debug_vars.sh"

# Create the rcfile with explicit terminal handling
cat >"$debugTmpDir/debug_rcfile.sh" <<EOF
# Source all variables
set +o posix
source "$debugTmpDir/debug_vars.sh"

# Force output to terminal
exec 2>/dev/tty
exec 1>/dev/tty
exec 0</dev/tty

# Show some helpful info
echo "Debug shell started. All variables from parent scope are available."
echo "Example: echo \\$tempDir"
echo "Type 'exit' to continue execution."

# Set a nice prompt
PS1="[DEBUG]> "
EOF

echo "Variables saved to $debugTmpDir/debug_vars.sh"
echo "Starting debug shell (redirecting to /dev/tty for interactivity)..."

# Start an interactive shell with explicit terminal redirection
bash --rcfile "$debugTmpDir/debug_rcfile.sh" </dev/tty >/dev/tty 2>&1
)
}

showUsage() {
cat <<USAGE
Usage: nixos-anywhere [options] [<ssh-host>]
Expand Down Expand Up @@ -526,11 +572,14 @@ importFacts() {
# shellcheck disable=SC2046
export $(echo "$filteredFacts" | xargs)

for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid; do
if [[ -z ${!var} ]]; then
abort "Failed to retrieve fact $var from host"
fi
done
(
set +x
for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasWget hasCurl hasSetsid; do
if [[ -z ${!var} ]]; then
abort "Failed to retrieve fact $var from host"
fi
done
)
}

checkBuildLocally() {
Expand Down Expand Up @@ -643,13 +692,53 @@ runKexec() {

# Define common remote commands template
local remoteCommandTemplate
remoteCommandTemplate="

# If we need sudo and have a TTY, use a script that can handle password prompts
if [[ -n ${maybeSudo} ]] && [[ ${sshTtyParam} == "-t" ]]; then
remoteCommandTemplate="
${enableDebug:+set -x}
# Create a script that we can run with sudo
cat > /tmp/kexec-script.sh << 'KEXEC_SCRIPT'
#!/bin/bash
set -eu ${enableDebug}
rm -rf /root/kexec
mkdir -p /root/kexec
cd /root/kexec
echo \"Downloading kexec tarball (this may take a moment)...\"
# Execute tar command
%TAR_COMMAND% && TMPDIR=/root/kexec setsid --wait /root/kexec/kexec/run --kexec-extra-flags $(printf '%q ' "$kexecExtraFlags")
KEXEC_SCRIPT
chmod +x /tmp/kexec-script.sh
# Run the script and let output flow naturally
${maybeSudo} /tmp/kexec-script.sh 2>&1 | tee /tmp/kexec-output.log || true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insecure tempfile handling. Why do we need this file?

Copy link
Contributor Author

@Qubasa Qubasa Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mic92 Ah because of a potential race condition with an attacker you are right, I didn't think of that.

The output log is static because then we can get the logs from the host easily without needing to back communicate the path

# The script will likely disconnect us, so we consider it successful if we see the kexec message
if grep -q 'machine will boot into nixos' /tmp/kexec-output.log; then
echo 'Kexec initiated successfully'
exit 0
else
echo 'Kexec may have failed - check output above'
cat /tmp/kexec-output.log
exit 1
fi
"
else
remoteCommandTemplate="
set -eu ${enableDebug}
${maybeSudo} rm -rf /root/kexec
${maybeSudo} mkdir -p /root/kexec
%TAR_COMMAND%
TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags $(printf '%q ' "$kexecExtraFlags")
{
%TAR_COMMAND% && TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags $(printf '%q ' "$kexecExtraFlags")
} 2>&1 | tee /tmp/kexec-output.log
if grep -q 'machine will boot into nixos' /tmp/kexec-output.log; then
echo 'Kexec initiated successfully'
exit 0
else
echo 'Kexec may have failed - check output above'
cat /tmp/kexec-output.log
exit 1
fi
"
fi

# Define upload commands
local localUploadCommand=()
Expand All @@ -670,17 +759,93 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
local remoteCommands
if [[ ${#localUploadCommand[@]} -eq 0 ]]; then
# Use remote command for download and execution
tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | ${maybeSudo} tar -C /root/kexec -xvzf-"
if [[ -n ${maybeSudo} ]] && [[ ${sshTtyParam} == "-t" ]]; then
# For sudo with TTY, tar runs inside the sudo script
tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | tar -xvzf-"
else
tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | ${maybeSudo} tar -C /root/kexec -xvzf-"
fi

remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand}

runSsh sh -c "$(printf '%q' "$remoteCommands")"
# Run the SSH command - for kexec with sudo, we expect it might disconnect
local sshExitCode
(
set +x
runSsh sh -c "$(printf '%q' "$remoteCommands")"
)
sshExitCode=$?

# For the sudo case, exit code 0 means success, 1 means failure was detected
if [[ -n ${maybeSudo} ]] && [[ ${sshTtyParam} == "-t" ]]; then
if [[ $sshExitCode -eq 0 ]]; then
echo "Kexec initiated successfully" >&2
else
# Try to get more info if possible
local logContent=""
if logContent=$(
set +x
runSsh "cat /tmp/kexec-output.log 2>/dev/null" 2>/dev/null
); then
echo "Remote output log:" >&2
echo "$logContent" >&2
fi
echo "Kexec command failed" >&2
exit 1
fi
else
# For non-sudo case, check the log as before
local kexecSuccess=false
local logContent=""
if logContent=$(
set +x
runSsh "cat /tmp/kexec-output.log 2>/dev/null" 2>/dev/null
); then
if echo "$logContent" | grep -q "machine will boot into nixos"; then
kexecSuccess=true
fi
fi

if [[ $sshExitCode -ne 0 ]] && [[ $kexecSuccess != true ]]; then
echo "Error: Failed to execute kexec commands on remote host" >&2

if [[ -n $logContent ]]; then
echo "Remote output log:" >&2
echo "$logContent" >&2
else
echo "Could not retrieve remote log file" >&2
fi

echo "Remote commands were: $remoteCommands" >&2
exit 1
elif [[ $kexecSuccess == true ]]; then
echo "Kexec initiated successfully" >&2
fi
fi

# Clean up the log file
(
set +x
runSsh "rm -f /tmp/kexec-output.log" 2>/dev/null || true
)
else
# Use local command with pipe to remote
tarCommand="${maybeSudo} tar -C /root/kexec -xvzf-"
if [[ -n ${maybeSudo} ]] && [[ ${sshTtyParam} == "-t" ]]; then
# For sudo with TTY, tar runs inside the sudo script
tarCommand="tar -xvzf-"
else
tarCommand="${maybeSudo} tar -C /root/kexec -xvzf-"
fi
remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand}

"${localUploadCommand[@]}" | runSsh sh -c "$(printf '%q' "$remoteCommands")"
if ! "${localUploadCommand[@]}" | (
set +x
runSsh sh -c "$(printf '%q' "$remoteCommands")"
); then
echo "Error: Failed to upload and execute kexec on remote host" >&2
echo "Remote commands were: $remoteCommands" >&2
exit 1
fi
fi

# use the default SSH port to connect at this point
Expand Down Expand Up @@ -859,10 +1024,12 @@ main() {
fi

maybeSudo=""
if [[ ${hasSudo-n} == "y" ]]; then
maybeSudo="sudo"
elif [[ ${hasDoas-n} == "y" ]]; then
maybeSudo="doas"
if [[ ${sshUser} != "root" ]]; then
if [[ ${hasSudo-n} == "y" ]]; then
maybeSudo="sudo"
elif [[ ${hasDoas-n} == "y" ]]; then
maybeSudo="doas"
fi
fi

if [[ ${isOs} != "Linux" ]]; then
Expand Down
Loading