Skip to content

Commit 554757f

Browse files
committed
Merge branch 'main' of https://github.com/nix-community/nixos-anywhere into system-features
2 parents 9a6bb22 + 2cbfde7 commit 554757f

File tree

8 files changed

+143
-20
lines changed

8 files changed

+143
-20
lines changed

src/nixos-anywhere.sh

Lines changed: 124 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,53 @@ declare -A extraFilesOwnership=()
6868
declare -a nixCopyOptions=()
6969
declare -a sshArgs=("-o" "IdentitiesOnly=yes" "-i" "$tempDir/nixos-anywhere" "-o" "UserKnownHostsFile=/dev/null" "-o" "StrictHostKeyChecking=no")
7070

71+
breakpoint() {
72+
(
73+
set +x
74+
echo "Breakpoint reached at line ${BASH_LINENO[0]}."
75+
76+
# Create a temporary directory for debug files
77+
debugTmpDir=$(mktemp -d /tmp/nixos-anywhere-debug.XXXXXX)
78+
chmod 700 "$debugTmpDir" # Secure the directory
79+
80+
# Set up cleanup trap
81+
# shellcheck disable=SC2064
82+
trap "rm -rf \"$debugTmpDir\"" EXIT
83+
84+
# Save all variables (local and exported) to a file
85+
(
86+
set -o posix
87+
set
88+
) >"$debugTmpDir/debug_vars.sh"
89+
90+
# Create the rcfile with explicit terminal handling
91+
cat >"$debugTmpDir/debug_rcfile.sh" <<EOF
92+
# Source all variables
93+
set +o posix
94+
source "$debugTmpDir/debug_vars.sh"
95+
96+
# Force output to terminal
97+
exec 2>/dev/tty
98+
exec 1>/dev/tty
99+
exec 0</dev/tty
100+
101+
# Show some helpful info
102+
echo "Debug shell started. All variables from parent scope are available."
103+
echo "Example: echo \\\$tempDir"
104+
echo "Type 'exit' to continue execution."
105+
106+
# Set a nice prompt
107+
PS1="[DEBUG]> "
108+
EOF
109+
110+
echo "Variables saved to $debugTmpDir/debug_vars.sh"
111+
echo "Starting debug shell (redirecting to /dev/tty for interactivity)..."
112+
113+
# Start an interactive shell with explicit terminal redirection
114+
bash --rcfile "$debugTmpDir/debug_rcfile.sh" </dev/tty >/dev/tty 2>&1
115+
)
116+
}
117+
71118
showUsage() {
72119
cat <<USAGE
73120
Usage: nixos-anywhere [options] [<ssh-host>]
@@ -423,9 +470,15 @@ runSshTimeout() {
423470
timeout 10 ssh "${sshArgs[@]}" "$sshConnection" "$@"
424471
}
425472
runSsh() {
426-
# shellcheck disable=SC2029
427-
# We want to expand "$@" to get the command to run over SSH
428-
ssh "$sshTtyParam" "${sshArgs[@]}" "$sshConnection" "$@"
473+
(
474+
set +x
475+
if [[ -n ${enableDebug} ]]; then
476+
echo -e "\033[1;34mSSH COMMAND:\033[0m ssh $sshTtyParam ${sshArgs[*]} $sshConnection $*\n"
477+
fi
478+
# shellcheck disable=SC2029
479+
# We want to expand "$@" to get the command to run over SSH
480+
ssh "$sshTtyParam" "${sshArgs[@]}" "$sshConnection" "$@"
481+
)
429482
}
430483

431484
nixCopy() {
@@ -518,10 +571,14 @@ importFacts() {
518571
if ! facts=$(runSsh -o ConnectTimeout=10 enableDebug=$enableDebug sh -- <"$here"/get-facts.sh); then
519572
exit 1
520573
fi
521-
filteredFacts=$(echo "$facts" | grep -E '^(has|is)[A-Za-z0-9_]+=\S+')
574+
filteredFacts=$(echo "$facts" | grep -E '^(has|is|remote)[A-Za-z0-9_]+=\S+')
522575
if [[ -z $filteredFacts ]]; then
523576
abort "Retrieving host facts via SSH failed. Check with --debug for the root cause, unless you have done so already"
524577
fi
578+
579+
# disable debug output temporarily to prevent log spam
580+
set +x
581+
525582
# make facts available in script
526583
# shellcheck disable=SC2046
527584
export $(echo "$filteredFacts" | xargs)
@@ -535,6 +592,10 @@ importFacts() {
535592
done
536593
set -u
537594

595+
if [[ -n ${enableDebug} ]]; then
596+
set -x
597+
fi
598+
538599
if [[ ${isRoot} == "y" ]]; then
539600
maybeSudo=
540601
elif [[ ${hasSudo} == "y" ]]; then
@@ -657,14 +718,42 @@ runKexec() {
657718
kexecUrl=${kexecUrl/"github.com"/"gh-v6.com"}
658719
fi
659720
721+
# Handle kexec operation failures
722+
handleKexecFailure() {
723+
local operation=$1
724+
725+
# Try to fetch the log file
726+
local logContent=""
727+
if logContent=$(
728+
set +x
729+
# shellcheck disable=SC2016 # We want $HOME to expand on the remote server
730+
runSsh 'cat "$HOME/kexec/nixos-anywhere.log" 2>/dev/null' 2>/dev/null
731+
); then
732+
echo "Remote output log:" >&2
733+
echo "$logContent" >&2
734+
fi
735+
echo "$operation failed" >&2
736+
exit 1
737+
}
738+
660739
# Define common remote commands template
661740
local remoteCommandTemplate
662741
remoteCommandTemplate="
663-
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")
742+
# Run kexec commands with sudo if needed
743+
{
744+
set -eu ${enableDebug}
745+
cd \"\$HOME/kexec\"
746+
echo Downloading kexec tarball, this may take a moment...
747+
# Execute tar command
748+
%TAR_COMMAND%
749+
TMPDIR=\"\$HOME/kexec\" ${maybeSudo} setsid --wait \"\$HOME/kexec/kexec/run\" --kexec-extra-flags $(printf '%q' "$kexecExtraFlags")
750+
} 2>&1 | tee \"\$HOME/kexec/nixos-anywhere.log\" || true
751+
752+
# The script will likely disconnect us, so we consider it successful if we see the kexec message
753+
if ! grep -q 'machine will boot into nixos' \"\$HOME/kexec/nixos-anywhere.log\"; then
754+
echo 'Kexec may have failed - check output above'
755+
exit 1
756+
fi
668757
"
669758
670759
# Define upload commands
@@ -694,22 +783,31 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr
694783
localUploadCommand=(curl --fail -Ss -L "${kexecUrl}")
695784
fi
696785
786+
# Determine the tar command based on upload method
697787
local tarCommand
698-
local remoteCommands
699788
if [[ ${#localUploadCommand[@]} -eq 0 ]]; then
700-
# Use remote command for download and execution
701-
tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | ${maybeSudo} tar -C /root/kexec -xv ${tarDecomp}"
702-
703-
remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand}
704-
705-
runSsh sh -c "$(printf '%q' "$remoteCommands")"
789+
# Use remote command for download
790+
tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | tar -xv ${tarDecomp}"
706791
else
707-
# Use local command with pipe to remote
708-
tarCommand="${maybeSudo} tar -C /root/kexec -xv ${tarDecomp}"
709-
remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand}
792+
# Use local file for extraction
793+
tarCommand="cat \"\$HOME/kexec/kexec-tarball.tar.gz\" | tar -xv ${tarDecomp}"
794+
fi
710795
711-
"${localUploadCommand[@]}" | runSsh sh -c "$(printf '%q' "$remoteCommands")"
796+
local remoteCommands
797+
remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand}
798+
799+
# Create and execute the script on the remote system
800+
# shellcheck disable=SC2016 # We want $HOME to expand on the remote server
801+
runSsh 'mkdir -p "$HOME/kexec" && cat > "$HOME/kexec/nixos-anywhere-kexec.sh"' <<EOF
802+
$remoteCommands
803+
EOF
804+
if [[ ${#localUploadCommand[@]} -gt 0 ]]; then
805+
# Upload the kexec tarball first
806+
# shellcheck disable=SC2016 # We want $HOME to expand on the remote server
807+
"${localUploadCommand[@]}" | runSsh 'cat > "$HOME/kexec/kexec-tarball.tar.gz"'
712808
fi
809+
# shellcheck disable=SC2016 # We want $HOME to expand on the remote server
810+
runSsh 'bash "$HOME/kexec/nixos-anywhere-kexec.sh"' || handleKexecFailure "Kexec"
713811
714812
# use the default SSH port to connect at this point
715813
local i
@@ -876,6 +974,12 @@ main() {
876974
sshUser=$(echo "$sshSettings" | awk '/^user / { print $2 }')
877975
sshHost="${sshConnection//*@/}"
878976
977+
# If kexec phase is not present, we assume kexec has already been run
978+
# and change the user to root@<sshHost> for the rest of the script.
979+
if [[ ${phases[kexec]} != 1 ]]; then
980+
sshConnection="root@${sshHost}"
981+
fi
982+
879983
uploadSshKey
880984
881985
importFacts

terraform/all-in-one.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ No resources.
202202
| Name | Description | Type | Default | Required |
203203
| --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | :------: |
204204
| <a name="input_build_on_remote"></a> [build\_on\_remote](#input_build_on_remote) | Build the closure on the remote machine instead of building it locally and copying it over | `bool` | `false` | no |
205+
| <a name="input_copy_host_keys"></a> [copy\_host\_keys](#input_copy_host_keys) | copy over existing /etc/ssh/ssh\_host\_* host keys to the installation | `bool` | `false` | no |
205206
| <a name="input_debug_logging"></a> [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no |
206207
| <a name="input_deployment_ssh_key"></a> [deployment\_ssh\_key](#input_deployment_ssh_key) | Content of private key used to deploy to the target\_host after initial installation. To ensure maximum security, it is advisable to connect to your host using ssh-agent instead of relying on this variable | `string` | `null` | no |
207208
| <a name="input_disk_encryption_key_scripts"></a> [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system | <pre>list(object({<br/> path = string<br/> script = string<br/> }))</pre> | `[]` | no |

terraform/all-in-one/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module "install" {
3939
nixos_generate_config_path = var.nixos_generate_config_path
4040
nixos_facter_path = var.nixos_facter_path
4141
build_on_remote = var.build_on_remote
42+
copy_host_keys = var.copy_host_keys
4243
# deprecated attributes
4344
stop_after_disko = var.stop_after_disko
4445
no_reboot = var.no_reboot

terraform/all-in-one/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,9 @@ variable "install_bootloader" {
149149
description = "Install/re-install the bootloader"
150150
default = false
151151
}
152+
153+
variable "copy_host_keys" {
154+
type = bool
155+
description = "copy over existing /etc/ssh/ssh_host_* host keys to the installation"
156+
default = false
157+
}

terraform/install.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ No modules.
6464
| Name | Description | Type | Default | Required |
6565
| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | :------: |
6666
| <a name="input_build_on_remote"></a> [build\_on\_remote](#input_build_on_remote) | Build the closure on the remote machine instead of building it locally and copying it over | `bool` | `false` | no |
67+
| <a name="input_copy_host_keys"></a> [copy\_host\_keys](#input_copy_host_keys) | copy over existing /etc/ssh/ssh\_host\_* host keys to the installation | `bool` | `false` | no |
6768
| <a name="input_debug_logging"></a> [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no |
6869
| <a name="input_disk_encryption_key_scripts"></a> [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system | <pre>list(object({<br/> path = string<br/> script = string<br/> }))</pre> | `[]` | no |
6970
| <a name="input_extra_environment"></a> [extra\_environment](#input_extra_environment) | Extra environment variables to be set during installation. This can be useful to set extra variables for the extra\_files\_script or disk\_encryption\_key\_scripts | `map(string)` | `{}` | no |

terraform/install/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ locals {
1818
phases = join(",", local.phases)
1919
nixos_generate_config_path = var.nixos_generate_config_path
2020
nixos_facter_path = var.nixos_facter_path
21+
copy_host_keys = var.copy_host_keys
2122
})
2223
}
2324

terraform/install/run-nixos-anywhere.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ if [[ ${input[target_pass]} != null ]]; then
4444
export SSHPASS=${input[target_pass]}
4545
args+=("--env-password")
4646
fi
47+
if [[ ${input[copy_host_keys]} == "true" ]]; then
48+
args+=("--copy-host-keys")
49+
fi
4750

4851
tmpdir=$(mktemp -d)
4952
cleanup() {

terraform/install/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,9 @@ variable "nixos_facter_path" {
121121
description = "Path to which to write a `facter.json` generated by `nixos-facter`. This option cannot be set at the same time as `nixos_generate_config_path`."
122122
default = ""
123123
}
124+
125+
variable "copy_host_keys" {
126+
type = bool
127+
description = "copy over existing /etc/ssh/ssh_host_* host keys to the installation"
128+
default = false
129+
}

0 commit comments

Comments
 (0)