From 31e9ff1000324b0623ac0de5cfdebcf472a75774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 9 Jun 2025 18:36:06 +0200 Subject: [PATCH 1/5] from-nix-seperated-phases: remove trailing whitespace --- tests/from-nixos-separated-phases.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/from-nixos-separated-phases.nix b/tests/from-nixos-separated-phases.nix index 27633702..e2a17d74 100644 --- a/tests/from-nixos-separated-phases.nix +++ b/tests/from-nixos-separated-phases.nix @@ -46,7 +46,7 @@ --debug \ --phases install \ --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ - root@installed >&2 + root@installed >&2 """) ''; } From 48f06de82a55b9798750b590e2185be8cf4e959b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 9 Jun 2025 16:34:20 +0200 Subject: [PATCH 2/5] make installer with sudo-only access work and re-enable ssh-ng Now that our nixos installers should have a fixed ssh-ng implementation, it should be safe to re-enable it. On top we now test the use-case of an installer that doesn't need kexec but sudo explicitly. --- src/nixos-anywhere.sh | 123 ++++++++++++++++------- tests/flake-module.nix | 11 ++ tests/from-nixos-installer-with-sudo.nix | 50 +++++++++ tests/modules/system-to-install.nix | 74 ++++++++------ 4 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 tests/from-nixos-installer-with-sudo.nix diff --git a/src/nixos-anywhere.sh b/src/nixos-anywhere.sh index e6746967..817a0dc1 100755 --- a/src/nixos-anywhere.sh +++ b/src/nixos-anywhere.sh @@ -64,7 +64,7 @@ mkdir -p "$tempDir" declare -A diskEncryptionKeys=() declare -A extraFilesOwnership=() -declare -a nixCopyOptions=() +declare -a nixCopyOptions=(--no-check-sigs) declare -a sshArgs=("-o" "IdentitiesOnly=yes" "-i" "$tempDir/nixos-anywhere" "-o" "UserKnownHostsFile=/dev/null" "-o" "StrictHostKeyChecking=no") showUsage() { @@ -420,19 +420,76 @@ runSsh() { ssh "$sshTtyParam" "${sshArgs[@]}" "$sshConnection" "$@" } +buildStoreUrl() { + local storeUrl="$1" + + # Add sshStoreSettings if present + if [[ -n ${sshStoreSettings} ]] && [[ $storeUrl == ssh-ng://* ]]; then + if [[ $storeUrl == *"?"* ]]; then + storeUrl="${storeUrl}&${sshStoreSettings}" + else + storeUrl="${storeUrl}?${sshStoreSettings}" + fi + fi + + # Add remote-program parameter when sudo is needed + if [[ -n ${maybeSudo} ]] && [[ $storeUrl == ssh-ng://* ]]; then + if [[ $storeUrl == *"?"* ]]; then + storeUrl="${storeUrl}&remote-program=${maybeSudo} nix-daemon" + else + storeUrl="${storeUrl}?remote-program=${maybeSudo} nix-daemon" + fi + fi + + echo "$storeUrl" +} + nixCopy() { - NIX_SSHOPTS="${sshArgs[*]}" nix copy \ - "${nixOptions[@]}" \ - "${nixCopyOptions[@]}" \ - "$@" + # Process arguments to add remote-program parameter when sudo is needed + local processedArgs=() + local i=1 + while [[ $i -le $# ]]; do + local arg="${!i}" + if [[ $arg == "--to" ]]; then + processedArgs+=("$arg") + ((i++)) + local storeUrl="${!i}" + storeUrl=$(buildStoreUrl "$storeUrl") + processedArgs+=("$storeUrl") + else + processedArgs+=("$arg") + fi + ((i++)) + done + + local nixCopyArgs=("${nixOptions[@]}" "${nixCopyOptions[@]}") + + NIX_SSHOPTS="${sshArgs[*]}" nix copy "${nixCopyArgs[@]}" "${processedArgs[@]}" } nixBuild() { + # Process arguments to add remote-program parameter when sudo is needed + local processedArgs=() + local i=1 + while [[ $i -le $# ]]; do + local arg="${!i}" + if [[ $arg == "--store" ]]; then + processedArgs+=("$arg") + ((i++)) + local storeUrl="${!i}" + storeUrl=$(buildStoreUrl "$storeUrl") + processedArgs+=("$storeUrl") + else + processedArgs+=("$arg") + fi + ((i++)) + done + NIX_SSHOPTS="${sshArgs[*]}" nix build \ --print-out-paths \ --no-link \ "${nixBuildFlags[@]}" \ "${nixOptions[@]}" \ - "$@" + "${processedArgs[@]}" } runVmTest() { @@ -688,6 +745,8 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr # After kexec we explicitly set the user to root@ sshConnection="root@${sshHost}" + # After kexec, we're running as root in the NixOS installer, so no need for sudo + maybeSudo="" # waiting for machine to become available again until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done @@ -697,55 +756,54 @@ runDisko() { local diskoScript=$1 for path in "${!diskEncryptionKeys[@]}"; do step "Uploading ${diskEncryptionKeys[$path]} to $path" - runSsh "umask 077; mkdir -p \"$(dirname "$path")\"; cat > $path" <"${diskEncryptionKeys[$path]}" + runSsh "${maybeSudo} sh -c $(printf '%q' "umask 077; mkdir -p $(dirname "$path"); cat > $path")" <"${diskEncryptionKeys[$path]}" done if [[ -n ${diskoScript} ]]; then - nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "$diskoScript" + nixCopy --to "ssh-ng://$sshConnection" "$diskoScript" elif [[ ${buildOn} == "remote" ]]; then step Building disko script # We need to do a nix copy first because nix build doesn't have --no-check-sigs # Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359 - nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "${flake}#${flakeAttr}.system.build.${diskoMode}Script" \ - --derivation --no-check-sigs + nixCopy --to "ssh-ng://$sshConnection" --derivation "${flake}#${flakeAttr}.system.build.${diskoMode}Script" # If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store` diskoScript=$( nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}" \ - --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&$sshStoreSettings" + --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere" ) fi step Formatting hard drive with disko - runSsh "$diskoScript" + runSsh "${maybeSudo} $diskoScript" } nixosInstall() { local nixosSystem=$1 + local remoteStoreUrl="remote-store=local%3Froot=%2Fmnt" if [[ -n ${nixosSystem} ]]; then step Uploading the system closure - nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "$nixosSystem" + nixCopy --to "ssh-ng://$sshConnection?${remoteStoreUrl}" "$nixosSystem" elif [[ ${buildOn} == "remote" ]]; then step Building the system closure # We need to do a nix copy first because nix build doesn't have --no-check-sigs # Use ssh:// here to avoid https://github.com/NixOS/nix/issues/7359 - nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "${flake}#${flakeAttr}.system.build.toplevel" \ - --derivation --no-check-sigs + nixCopy --to "ssh-ng://$sshConnection?${remoteStoreUrl}" --derivation "${flake}#${flakeAttr}.system.build.toplevel" # If we don't use ssh-ng here, we get `error: operation 'getFSAccessor' is not supported by store` nixosSystem=$( nixBuild "${flake}#${flakeAttr}.system.build.toplevel" \ - --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" + --eval-store auto --store "ssh-ng://$sshConnection?ssh-key=$tempDir%2Fnixos-anywhere&${remoteStoreUrl}" ) fi if [[ -n ${extraFiles} ]]; then step Copying extra files - tar -C "$extraFiles" -cpf- . | runSsh "tar -C /mnt -xf- --no-same-owner" + tar -C "$extraFiles" -cpf- . | runSsh "${maybeSudo} tar -C /mnt -xf- --no-same-owner" - runSsh "chmod 755 /mnt" # tar also changes permissions of /mnt + runSsh "${maybeSudo} chmod 755 /mnt" # tar also changes permissions of /mnt fi if [[ ${#extraFilesOwnership[@]} -gt 0 ]]; then # shellcheck disable=SC2016 - printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do chown -R "$ownership" "/mnt/$file"; done' + printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do '"${maybeSudo}"' chown -R "$ownership" "/mnt/$file"; done' fi step Installing NixOS @@ -756,27 +814,27 @@ export PATH="\$PATH:/run/current-system/sw/bin" if [ ! -d "/mnt/tmp" ]; then # needed for installation if initrd-secrets are used - mkdir -p /mnt/tmp - chmod 777 /mnt/tmp + ${maybeSudo} mkdir -p /mnt/tmp + ${maybeSudo} chmod 777 /mnt/tmp fi if [ ${copyHostKeys-n} = "y" ]; then # NB we copy host keys that are in turn copied by kexec installer. - mkdir -m 755 -p /mnt/etc/ssh + ${maybeSudo} mkdir -m 755 -p /mnt/etc/ssh for p in /etc/ssh/ssh_host_*; do # Skip if the source file does not exist (i.e. glob did not match any files) # or the destination already exists (e.g. copied with --extra-files). if [ ! -e "\$p" ] || [ -e "/mnt/\$p" ]; then continue fi - cp -a "\$p" "/mnt/\$p" + ${maybeSudo} cp -a "\$p" "/mnt/\$p" done fi # https://stackoverflow.com/a/13864829 if [ ! -z ${NIXOS_NO_CHECK+0} ]; then export NIXOS_NO_CHECK fi -nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem" +${maybeSudo} nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem" SSH } @@ -786,11 +844,11 @@ nixosReboot() { runSsh sh </dev/null && [ "\$(zpool list)" != "no pools available" ]; then # we always want to export the zfs pools so people can boot from it without force import - umount -Rv /mnt/ - swapoff -a - zpool export -a || true + ${maybeSudo} umount -Rv /mnt/ + ${maybeSudo} swapoff -a + ${maybeSudo} zpool export -a || true fi - nohup sh -c 'sleep 6 && reboot' >/dev/null & + ${maybeSudo} nohup sh -c 'sleep 6 && reboot' >/dev/null & SSH step Waiting for the machine to become unreachable due to reboot @@ -840,7 +898,6 @@ main() { fi sshSettings=$(ssh "${sshArgs[@]}" -G "${sshConnection}") - sshUser=$(echo "$sshSettings" | awk '/^user / { print $2 }') sshHost=$(echo "$sshSettings" | awk '/^hostname / { print $2 }') uploadSshKey @@ -898,14 +955,6 @@ main() { fi fi - # Installation will fail if non-root user is used for installer. - # Switch to root user by copying authorized_keys. - if [[ ${isInstaller} == "y" ]] && [[ ${sshUser} != "root" ]]; then - # Allow copy to fail if authorized_keys does not exist, like if using /etc/ssh/authorized_keys.d/ - runSsh "${maybeSudo} mkdir -p /root/.ssh; ${maybeSudo} cp ~/.ssh/authorized_keys /root/.ssh || true" - sshConnection="root@${sshHost}" - fi - if [[ ${phases[disko]} == 1 ]]; then runDisko "$diskoScript" fi diff --git a/tests/flake-module.nix b/tests/flake-module.nix index dde92cf7..ea76da6c 100644 --- a/tests/flake-module.nix +++ b/tests/flake-module.nix @@ -7,6 +7,13 @@ ./modules/system-to-install.nix inputs.disko.nixosModules.disko ]; + system-to-install-vdb = pkgs.nixos [ + ./modules/system-to-install.nix + inputs.disko.nixosModules.disko + { + nixos-anywhere.diskDevice = "/dev/vdb"; + } + ]; testInputsUnstable = { inherit pkgs; inherit (inputs.disko.nixosModules) disko; @@ -17,6 +24,9 @@ testInputsStable = testInputsUnstable // { kexec-installer = "${inputs'.nixos-images.packages.kexec-installer-nixos-stable-noninteractive}/nixos-kexec-installer-noninteractive-${system}.tar.gz"; }; + testInputsInstallerSudo = testInputsUnstable // { + system-to-install = system-to-install-vdb; + }; linuxTestInputs = testInputsUnstable // { nix-vm-test = inputs.nix-vm-test; }; @@ -26,6 +36,7 @@ from-nixos-stable = import ./from-nixos.nix testInputsStable; from-nixos-with-sudo = import ./from-nixos-with-sudo.nix testInputsUnstable; from-nixos-with-sudo-stable = import ./from-nixos-with-sudo.nix testInputsStable; + from-nixos-installer-with-sudo = import ./from-nixos-installer-with-sudo.nix testInputsInstallerSudo; from-nixos-with-generated-config = import ./from-nixos-generate-config.nix testInputsUnstable; from-nixos-build-on-remote = import ./from-nixos-build-on-remote.nix testInputsUnstable; from-nixos-separated-phases = import ./from-nixos-separated-phases.nix testInputsUnstable; diff --git a/tests/from-nixos-installer-with-sudo.nix b/tests/from-nixos-installer-with-sudo.nix new file mode 100644 index 00000000..66da14e9 --- /dev/null +++ b/tests/from-nixos-installer-with-sudo.nix @@ -0,0 +1,50 @@ +(import ./lib/test-base.nix) { + name = "from-nixos-installer-with-sudo"; + nodes = { + installer = ./modules/installer.nix; + installed = { modulesPath, ... }: { + imports = [ + (modulesPath + "/installer/cd-dvd/installation-cd-base.nix") + ]; + + services.openssh.enable = true; + virtualisation.memorySize = 1500; + virtualisation.emptyDiskImages = [ 1024 ]; + + users.users.nixos = { + isNormalUser = true; + openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; + extraGroups = [ "wheel" ]; + }; + security.sudo.enable = true; + security.sudo.wheelNeedsPassword = false; + + # Configure nix trusted users for remote builds with sudo + nix.settings.trusted-users = [ "root" "nixos" ]; + }; + }; + testScript = '' + start_all() + installer.succeed("echo super-secret > /tmp/disk-1.key") + installer.succeed("mkdir -p /tmp/extra-files/var/lib/secrets") + installer.succeed("echo test-value > /tmp/extra-files/var/lib/secrets/test") + + output = installer.succeed(""" + nixos-anywhere \ + -i /root/.ssh/install_key \ + --debug \ + --phases disko,install \ + --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ + --extra-files /tmp/extra-files \ + --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ + nixos@installed >&2 + echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + nixos@installed sudo cat /tmp/disk-1.key)'" + echo "extra-file: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + nixos@installed sudo cat /mnt/var/lib/secrets/test)'" + """) + + assert "disk-1.key: 'super-secret'" in output, f"output does not contain expected values: {output}" + assert "extra-file: 'test-value'" in output, f"output does not contain expected values: {output}" + ''; +} diff --git a/tests/modules/system-to-install.nix b/tests/modules/system-to-install.nix index ea5f2a37..def5968e 100644 --- a/tests/modules/system-to-install.nix +++ b/tests/modules/system-to-install.nix @@ -1,41 +1,51 @@ -{ modulesPath, self, lib, ... }: { +{ modulesPath, lib, config, ... }: +{ + options.nixos-anywhere.diskDevice = lib.mkOption { + type = lib.types.str; + default = "/dev/vda"; + description = "The disk device to use for installation"; + }; + imports = [ (modulesPath + "/testing/test-instrumentation.nix") (modulesPath + "/profiles/qemu-guest.nix") (modulesPath + "/profiles/minimal.nix") ]; - networking.hostName = lib.mkDefault "nixos-anywhere"; - documentation.enable = false; - hardware.enableAllFirmware = false; - networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs - boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms - disko.devices = { - disk = { - vda = { - device = "/dev/vda"; - type = "disk"; - content = { - type = "gpt"; - partitions = { - boot = { - size = "1M"; - type = "EF02"; - }; - ESP = { - size = "100M"; - type = "EF00"; - content = { - type = "filesystem"; - format = "vfat"; - mountpoint = "/boot"; + + config = { + networking.hostName = lib.mkDefault "nixos-anywhere"; + documentation.enable = false; + hardware.enableAllFirmware = false; + networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs + boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms + disko.devices = { + disk = { + main = { + device = config.nixos-anywhere.diskDevice; + type = "disk"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1M"; + type = "EF02"; }; - }; - root = { - size = "100%"; - content = { - type = "filesystem"; - format = "ext4"; - mountpoint = "/"; + ESP = { + size = "100M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + root = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; }; }; }; From d9d85c4158192764f6a5ca56b35f2c0a9e6062ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 9 Jun 2025 20:39:38 +0200 Subject: [PATCH 3/5] add support for providing sudo passwords via environemnt variables --- docs/cli.md | 4 +- docs/quickstart.md | 4 +- docs/reference.md | 4 +- src/get-facts.sh | 1 + src/nixos-anywhere.sh | 185 +++++++++++++++++++++++++++------ terraform/all-in-one.md | 2 + terraform/install.md | 1 + tests/from-nixos-with-sudo.nix | 7 +- 8 files changed, 170 insertions(+), 38 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 2b2aca67..c1bcc45c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -20,7 +20,9 @@ Options: print full build logs * --env-password set a password used by ssh-copy-id, the password should be set by - the environment variable SSHPASS + the environment variable SSHPASS. Additionally, sudo password can be set + via SUDO_PASSWORD environment variable for remote sudo operations + (only supported with sudo, not doas) * -s, --store-paths set the store paths to the disko-script and nixos-system directly if this is given, flake is not needed diff --git a/docs/quickstart.md b/docs/quickstart.md index 5eb1a10e..cd8c894d 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -120,7 +120,9 @@ example uses a local directory on the source machine. If your SSH key is not found, you will be asked for your password. If you are using a non-root user, you must have access to sudo without a password. To avoid SSH password prompts, set the `SSHPASS` environment variable to your password -and add `--env-password` to the `nixos-anywhere` command. If providing a +and add `--env-password` to the `nixos-anywhere` command. Additionally, if your +target machine requires a sudo password, you can set the `SUDO_PASSWORD` +environment variable (only supported with sudo, not doas). If providing a specific SSH key through `-i` (identity_file), this key will then be used for the installation and no temporary SSH key will be created. diff --git a/docs/reference.md b/docs/reference.md index cb70c8b8..95e9eb7a 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -41,7 +41,9 @@ Options: print full build logs * --env-password set a password used by ssh-copy-id, the password should be set by - the environment variable SSHPASS + the environment variable SSHPASS. Additionally, sudo password can be set + via SUDO_PASSWORD environment variable for remote sudo operations + (only supported with sudo, not doas) * -s, --store-paths set the store paths to the disko-script and nixos-system directly if this is given, flake is not needed diff --git a/src/get-facts.sh b/src/get-facts.sh index 80434422..0a4b7778 100755 --- a/src/get-facts.sh +++ b/src/get-facts.sh @@ -16,6 +16,7 @@ hasTar=$(has tar) hasCpio=$(has cpio) hasSudo=$(has sudo) hasDoas=$(has doas) +hasPasswordlessSudo=$(if [ "$(has sudo)" = "y" ] && sudo -n true >/dev/null 2>&1; then echo "y"; else echo "n"; fi) hasWget=$(has wget) hasCurl=$(has curl) hasSetsid=$(has setsid) diff --git a/src/nixos-anywhere.sh b/src/nixos-anywhere.sh index 817a0dc1..862ecc09 100755 --- a/src/nixos-anywhere.sh +++ b/src/nixos-anywhere.sh @@ -21,6 +21,7 @@ nixOptions=( "--no-write-lock-file" ) SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY-} +SUDO_PASSWORD=${SUDO_PASSWORD-} declare -A phases phases[kexec]=1 @@ -53,6 +54,7 @@ hasTar= hasCpio= hasSudo= hasDoas= +hasPasswordlessSudo= hasWget= hasCurl= hasSetsid= @@ -91,7 +93,9 @@ Options: print full build logs * --env-password set a password used by ssh-copy-id, the password should be set by - the environment variable SSHPASS + the environment variable SSHPASS. Additionally, sudo password can be set + via SUDO_PASSWORD environment variable for remote sudo operations + (only supported with sudo, not doas). * -s, --store-paths set the store paths to the disko-script and nixos-system directly if this is given, flake is not needed @@ -247,6 +251,10 @@ parseArgs() { ;; --debug) enableDebug="-x" + if [[ ${SUDO_PASSWORD} != "" ]]; then + echo "WARNING: Debug mode enabled with SUDO_PASSWORD. Password authentication may interfere with debug output." >&2 + sleep 2 + fi printBuildLogs=y set -x ;; @@ -420,6 +428,106 @@ runSsh() { ssh "$sshTtyParam" "${sshArgs[@]}" "$sshConnection" "$@" } +# Helper function to authenticate sudo with password if needed +maybeSudo() { + # Early return if no command provided and no sudo password + if [[ $# -eq 0 && -z ${SUDO_PASSWORD} ]]; then + return + fi + + # Use 'true' as default command if none provided but we have sudo password + local cmd=("${@:-true}") + + if [[ -n ${SUDO_PASSWORD} ]] && [[ ${maybeSudoCommand} == "sudo" ]]; then + # If debug is enabled and we have a sudo password, warn about potential issues + + # Use sudo with password authentication - pipe password to all sudo commands + printf "printf %%s %q | sudo -S " "$SUDO_PASSWORD" + printf '%q ' "${cmd[@]}" + elif [[ -n ${maybeSudoCommand} ]]; then + printf '%s ' "${maybeSudoCommand}" + printf '%q ' "${cmd[@]}" + else + # No sudo command needed (e.g., already root after kexec) + printf '%q ' "${cmd[@]}" + fi + echo +} + +# Test and cache sudo password if needed +testAndCacheSudoPassword() { + # Skip if no sudo command available + if [[ -z ${maybeSudoCommand} ]]; then + return 0 + fi + + # Skip if using doas (doesn't support password authentication) + if [[ ${maybeSudoCommand} == "doas" ]]; then + return 0 + fi + + # Skip if sudo works without password + if [[ ${hasPasswordlessSudo} == "y" ]]; then + step "Passwordless sudo confirmed" + return 0 + fi + + # If we already have a password supplied, trust it + if [[ -n ${SUDO_PASSWORD} ]]; then + step "Using supplied sudo password" + return 0 + fi + + # Only prompt for password in interactive sessions + if [[ -t 0 ]]; then + step "Sudo requires password authentication" + local attempts=0 + local maxAttempts=5 + + while [[ $attempts -lt $maxAttempts ]]; do + echo -n "Enter sudo password for ${sshConnection}: " + read -rs password + echo + + # Test the password + local testOutput + testOutput=$(runSshNoTty "echo $(printf %q "$password") | sudo -S echo 'SUDO_TEST_SUCCESS'" 2>&1) + if [[ $testOutput == *"SUDO_TEST_SUCCESS"* ]]; then + SUDO_PASSWORD="$password" + step "Sudo password verified and cached" + return 0 + else + ((attempts++)) + if [[ $attempts -lt $maxAttempts ]]; then + echo "Invalid password, please try again ($attempts/$maxAttempts)" + fi + fi + done + + abort "Failed to authenticate sudo after $maxAttempts attempts" + else + # Non-interactive session without working sudo + abort "Sudo requires password but running in non-interactive mode. Set SUDO_PASSWORD environment variable or configure passwordless sudo." + fi +} + +urlEncode() { + local string="${1}" + local strlen=${#string} + local encoded="" + local pos c o + + for ((pos = 0; pos < strlen; pos++)); do + c=${string:pos:1} + case "$c" in + [-_.~a-zA-Z0-9]) o="${c}" ;; + *) printf -v o '%%%02x' "'$c" ;; + esac + encoded+="${o}" + done + echo "${encoded}" +} + buildStoreUrl() { local storeUrl="$1" @@ -433,11 +541,19 @@ buildStoreUrl() { fi # Add remote-program parameter when sudo is needed - if [[ -n ${maybeSudo} ]] && [[ $storeUrl == ssh-ng://* ]]; then + if [[ -n ${maybeSudoCommand} ]] && [[ $storeUrl == ssh-ng://* ]]; then + local remoteProgram + if [[ -n ${SUDO_PASSWORD} ]] && [[ ${maybeSudoCommand} == "sudo" ]]; then + # Use password authentication for nix-daemon + remoteProgram="sh -c $(urlEncode "$(printf %s "$(printf '%q' "$SUDO_PASSWORD")" | sudo -S nix-daemon)")" + else + remoteProgram="${maybeSudoCommand},nix-daemon" + fi + if [[ $storeUrl == *"?"* ]]; then - storeUrl="${storeUrl}&remote-program=${maybeSudo} nix-daemon" + storeUrl="${storeUrl}&remote-program=${remoteProgram}" else - storeUrl="${storeUrl}?remote-program=${maybeSudo} nix-daemon" + storeUrl="${storeUrl}?remote-program=${remoteProgram}" fi fi @@ -575,7 +691,7 @@ 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 + for var in isOs isArch isKexec isInstaller isContainer hasIpv6Only hasTar hasCpio hasSudo hasDoas hasPasswordlessSudo hasWget hasCurl hasSetsid; do if [[ -z ${!var} ]]; then abort "Failed to retrieve fact $var from host" fi @@ -626,7 +742,6 @@ checkBuildLocally() { } generateHardwareConfig() { - local maybeSudo="$maybeSudo" mkdir -p "$(dirname "$hardwareConfigPath")" case "$hardwareConfigBackend" in nixos-facter) @@ -634,12 +749,10 @@ generateHardwareConfig() { if [[ ${hasNixOSFacter} == "n" ]]; then abort "nixos-facter is not available in booted installer, use nixos-generate-config. For nixos-facter, you may want to boot an installer image from here instead: https://github.com/nix-community/nixos-images" fi - else - maybeSudo="" fi step "Generating hardware-configuration.nix using nixos-facter" - runSshNoTty -o ConnectTimeout=10 ${maybeSudo} "nixos-facter" >"$hardwareConfigPath" + runSshNoTty -o ConnectTimeout=10 "$(maybeSudo nixos-facter)" >"$hardwareConfigPath" ;; nixos-generate-config) step "Generating hardware-configuration.nix using nixos-generate-config" @@ -693,10 +806,10 @@ runKexec() { local remoteCommandTemplate remoteCommandTemplate=" set -eu ${enableDebug} -${maybeSudo} rm -rf /root/kexec -${maybeSudo} mkdir -p /root/kexec +$(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") +$(maybeSudo TMPDIR=/root/kexec setsid --wait /root/kexec/kexec/run --kexec-extra-flags "$kexecExtraFlags") " # Define upload commands @@ -716,16 +829,17 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr local tarCommand local remoteCommands + if [[ ${#localUploadCommand[@]} -eq 0 ]]; then # Use remote command for download and execution - tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | ${maybeSudo} tar -C /root/kexec -xvzf-" + tarCommand="$(printf '%q ' "${remoteUploadCommand[@]}") | ${maybeSudoCommand} tar -C /root/kexec -xvzf-" remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand} runSsh sh -c "$(printf '%q' "$remoteCommands")" else # Use local command with pipe to remote - tarCommand="${maybeSudo} tar -C /root/kexec -xvzf-" + tarCommand="${maybeSudoCommand} tar -C /root/kexec -xvzf-" remoteCommands=${remoteCommandTemplate//'%TAR_COMMAND%'/$tarCommand} "${localUploadCommand[@]}" | runSsh sh -c "$(printf '%q' "$remoteCommands")" @@ -746,7 +860,7 @@ TMPDIR=/root/kexec setsid --wait ${maybeSudo} /root/kexec/kexec/run --kexec-extr # After kexec we explicitly set the user to root@ sshConnection="root@${sshHost}" # After kexec, we're running as root in the NixOS installer, so no need for sudo - maybeSudo="" + maybeSudoCommand="" # waiting for machine to become available again until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done @@ -756,7 +870,7 @@ runDisko() { local diskoScript=$1 for path in "${!diskEncryptionKeys[@]}"; do step "Uploading ${diskEncryptionKeys[$path]} to $path" - runSsh "${maybeSudo} sh -c $(printf '%q' "umask 077; mkdir -p $(dirname "$path"); cat > $path")" <"${diskEncryptionKeys[$path]}" + runSsh "$(maybeSudo sh) -c $(printf '%q' "umask 077; mkdir -p $(dirname "$path"); cat > $path")" <"${diskEncryptionKeys[$path]}" done if [[ -n ${diskoScript} ]]; then nixCopy --to "ssh-ng://$sshConnection" "$diskoScript" @@ -773,7 +887,7 @@ runDisko() { fi step Formatting hard drive with disko - runSsh "${maybeSudo} $diskoScript" + runSsh "$(maybeSudo "$diskoScript")" } nixosInstall() { @@ -796,17 +910,18 @@ nixosInstall() { if [[ -n ${extraFiles} ]]; then step Copying extra files - tar -C "$extraFiles" -cpf- . | runSsh "${maybeSudo} tar -C /mnt -xf- --no-same-owner" + tar -C "$extraFiles" -cpf- . | runSsh "${maybeSudoCommand} tar -C /mnt -xf- --no-same-owner" - runSsh "${maybeSudo} chmod 755 /mnt" # tar also changes permissions of /mnt + runSsh "$(maybeSudo chmod 755 /mnt)" # tar also changes permissions of /mnt fi if [[ ${#extraFilesOwnership[@]} -gt 0 ]]; then # shellcheck disable=SC2016 - printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do '"${maybeSudo}"' chown -R "$ownership" "/mnt/$file"; done' + printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do '"$(maybeSudo chown -R \$ownership \"/mnt/\$file\")"'; done' fi step Installing NixOS + # shellcheck disable=SC2016 runSsh sh </dev/null && [ "\$(zpool list)" != "no pools available" ]; then # we always want to export the zfs pools so people can boot from it without force import - ${maybeSudo} umount -Rv /mnt/ - ${maybeSudo} swapoff -a - ${maybeSudo} zpool export -a || true + $(maybeSudo umount -Rv /mnt/) + $(maybeSudo swapoff -a) + $(maybeSudo zpool export -a || true) fi - ${maybeSudo} nohup sh -c 'sleep 6 && reboot' >/dev/null & + $(maybeSudo nohup sh -c 'sleep 6 && reboot') >/dev/null & SSH step Waiting for the machine to become unreachable due to reboot @@ -916,13 +1031,19 @@ main() { abort "no setsid command found, but required to run the kexec script under a new session" fi - maybeSudo="" + maybeSudoCommand="" if [[ ${hasSudo-n} == "y" ]]; then - maybeSudo="sudo" + maybeSudoCommand="sudo" elif [[ ${hasDoas-n} == "y" ]]; then - maybeSudo="doas" + maybeSudoCommand="doas" + if [[ -n ${SUDO_PASSWORD} ]]; then + abort "SUDO_PASSWORD environment variable is not supported with doas. Please configure passwordless doas or use sudo instead." + fi fi + # Test and cache sudo password if needed + testAndCacheSudoPassword + if [[ ${isOs} != "Linux" ]]; then abort "This script requires Linux as the operating system, but got $isOs" fi diff --git a/terraform/all-in-one.md b/terraform/all-in-one.md index bac0ac3a..d9e4986b 100644 --- a/terraform/all-in-one.md +++ b/terraform/all-in-one.md @@ -209,8 +209,10 @@ No resources. | [extra\_files\_script](#input_extra_files_script) | A script that should place files in the current directory that will be copied to the targets / directory | `string` | `null` | no | | [file](#input_file) | Nix file containing the nixos\_system\_attr and nixos\_partitioner\_attr. Use this if you are not using flake | `string` | `null` | no | | [install\_bootloader](#input_install_bootloader) | Install/re-install the bootloader | `bool` | `false` | no | +| [install\_pass](#input_install_pass) | Password used to connect to the target\_host during installation | `string` | `null` | no | | [install\_port](#input_install_port) | SSH port used to connect to the target\_host, before installing NixOS. If null than the value of `target_port` is used | `string` | `null` | no | | [install\_ssh\_key](#input_install_ssh_key) | Content of private key used to connect to the target\_host during initial installation | `string` | `null` | no | +| [install\_sudo\_pass](#input_install_sudo_pass) | Sudo password for remote sudo operations during installation. Only supported with sudo, not doas. | `string` | `null` | no | | [install\_user](#input_install_user) | SSH user used to connect to the target\_host, before installing NixOS. If null than the value of `target_host` is used | `string` | `null` | no | | [instance\_id](#input_instance_id) | The instance id of the target\_host, used to track when to reinstall the machine | `string` | `null` | no | | [kexec\_tarball\_url](#input_kexec_tarball_url) | NixOS kexec installer tarball url | `string` | `null` | no | diff --git a/terraform/install.md b/terraform/install.md index 7494c2ce..62c6585b 100644 --- a/terraform/install.md +++ b/terraform/install.md @@ -82,6 +82,7 @@ No modules. | [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes | | [target\_pass](#input_target_pass) | Password used to connect to the target\_host | `string` | `null` | no | | [target\_port](#input_target_port) | SSH port used to connect to the target\_host | `number` | `22` | no | +| [target\_sudo\_pass](#input_target_sudo_pass) | Sudo password for remote sudo operations on target\_host. Only supported with sudo, not doas. | `string` | `null` | no | | [target\_user](#input_target_user) | SSH user used to connect to the target\_host | `string` | `"root"` | no | ## Outputs diff --git a/tests/from-nixos-with-sudo.nix b/tests/from-nixos-with-sudo.nix index aaf0465a..3e87a5b4 100644 --- a/tests/from-nixos-with-sudo.nix +++ b/tests/from-nixos-with-sudo.nix @@ -8,18 +8,19 @@ users.users.nixos = { isNormalUser = true; + password = "test123"; openssh.authorizedKeys.keyFiles = [ ./modules/ssh-keys/ssh.pub ]; extraGroups = [ "wheel" ]; }; security.sudo.enable = true; - security.sudo.wheelNeedsPassword = false; + security.sudo.wheelNeedsPassword = true; }; }; testScript = '' start_all() installer.succeed("echo super-secret > /tmp/disk-1.key") output = installer.succeed(""" - nixos-anywhere \ + SUDO_PASSWORD=test123 nixos-anywhere \ -i /root/.ssh/install_key \ --debug \ --kexec /etc/nixos-anywhere/kexec-installer \ @@ -27,7 +28,7 @@ --disk-encryption-keys /tmp/disk-1.key /tmp/disk-1.key \ --disk-encryption-keys /tmp/disk-2.key <(echo another-secret) \ --store-paths /etc/nixos-anywhere/disko /etc/nixos-anywhere/system-to-install \ - nixos@installed >&2 + nixos@installed 2>&1 echo "disk-1.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ root@installed cat /tmp/disk-1.key)'" echo "disk-2.key: '$(ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ From 34e4dd751c467d4347acf7dedc30433a45135914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 9 Jun 2025 20:46:33 +0200 Subject: [PATCH 4/5] also support sudo password in terraform --- docs/cli.md | 2 +- docs/reference.md | 2 +- src/nixos-anywhere.sh | 55 ++++++++++--------------- terraform/all-in-one.md | 6 ++- terraform/all-in-one/main.tf | 2 + terraform/all-in-one/variables.tf | 14 +++++++ terraform/install.md | 4 ++ terraform/install/main.tf | 1 + terraform/install/run-nixos-anywhere.sh | 24 ++++++++++- terraform/install/variables.tf | 7 ++++ 10 files changed, 78 insertions(+), 39 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index c1bcc45c..32caf970 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -21,7 +21,7 @@ Options: * --env-password set a password used by ssh-copy-id, the password should be set by the environment variable SSHPASS. Additionally, sudo password can be set - via SUDO_PASSWORD environment variable for remote sudo operations + via SUDO_PASSWORD environment variable for remote sudo operations (only supported with sudo, not doas) * -s, --store-paths set the store paths to the disko-script and nixos-system directly diff --git a/docs/reference.md b/docs/reference.md index 95e9eb7a..15a9177e 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -42,7 +42,7 @@ Options: * --env-password set a password used by ssh-copy-id, the password should be set by the environment variable SSHPASS. Additionally, sudo password can be set - via SUDO_PASSWORD environment variable for remote sudo operations + via SUDO_PASSWORD environment variable for remote sudo operations (only supported with sudo, not doas) * -s, --store-paths set the store paths to the disko-script and nixos-system directly diff --git a/src/nixos-anywhere.sh b/src/nixos-anywhere.sh index 862ecc09..9c26613b 100755 --- a/src/nixos-anywhere.sh +++ b/src/nixos-anywhere.sh @@ -430,28 +430,15 @@ runSsh() { # Helper function to authenticate sudo with password if needed maybeSudo() { - # Early return if no command provided and no sudo password - if [[ $# -eq 0 && -z ${SUDO_PASSWORD} ]]; then - return - fi - - # Use 'true' as default command if none provided but we have sudo password - local cmd=("${@:-true}") - if [[ -n ${SUDO_PASSWORD} ]] && [[ ${maybeSudoCommand} == "sudo" ]]; then # If debug is enabled and we have a sudo password, warn about potential issues - # Use sudo with password authentication - pipe password to all sudo commands printf "printf %%s %q | sudo -S " "$SUDO_PASSWORD" - printf '%q ' "${cmd[@]}" + # Restore debug state if it was enabled elif [[ -n ${maybeSudoCommand} ]]; then printf '%s ' "${maybeSudoCommand}" - printf '%q ' "${cmd[@]}" - else - # No sudo command needed (e.g., already root after kexec) - printf '%q ' "${cmd[@]}" fi - echo + # No output if no sudo needed (e.g., already root after kexec) } # Test and cache sudo password if needed @@ -547,7 +534,7 @@ buildStoreUrl() { # Use password authentication for nix-daemon remoteProgram="sh -c $(urlEncode "$(printf %s "$(printf '%q' "$SUDO_PASSWORD")" | sudo -S nix-daemon)")" else - remoteProgram="${maybeSudoCommand},nix-daemon" + remoteProgram="${maybeSudoCommand} nix-daemon" fi if [[ $storeUrl == *"?"* ]]; then @@ -752,7 +739,7 @@ generateHardwareConfig() { fi step "Generating hardware-configuration.nix using nixos-facter" - runSshNoTty -o ConnectTimeout=10 "$(maybeSudo nixos-facter)" >"$hardwareConfigPath" + runSshNoTty -o ConnectTimeout=10 "$(maybeSudo)nixos-facter" >"$hardwareConfigPath" ;; nixos-generate-config) step "Generating hardware-configuration.nix using nixos-generate-config" @@ -806,10 +793,10 @@ runKexec() { local remoteCommandTemplate remoteCommandTemplate=" set -eu ${enableDebug} -$(maybeSudo rm -rf /root/kexec) -$(maybeSudo mkdir -p /root/kexec) +$(maybeSudo)rm -rf /root/kexec +$(maybeSudo)mkdir -p /root/kexec %TAR_COMMAND% -$(maybeSudo TMPDIR=/root/kexec setsid --wait /root/kexec/kexec/run --kexec-extra-flags "$kexecExtraFlags") +$(maybeSudo)TMPDIR=/root/kexec setsid --wait /root/kexec/kexec/run${kexecExtraFlags:+ --kexec-extra-flags \"$kexecExtraFlags\"} " # Define upload commands @@ -870,7 +857,7 @@ runDisko() { local diskoScript=$1 for path in "${!diskEncryptionKeys[@]}"; do step "Uploading ${diskEncryptionKeys[$path]} to $path" - runSsh "$(maybeSudo sh) -c $(printf '%q' "umask 077; mkdir -p $(dirname "$path"); cat > $path")" <"${diskEncryptionKeys[$path]}" + runSsh "$(maybeSudo)sh -c $(printf '%q' "umask 077; mkdir -p $(dirname "$path"); cat > $path")" <"${diskEncryptionKeys[$path]}" done if [[ -n ${diskoScript} ]]; then nixCopy --to "ssh-ng://$sshConnection" "$diskoScript" @@ -887,7 +874,7 @@ runDisko() { fi step Formatting hard drive with disko - runSsh "$(maybeSudo "$diskoScript")" + runSsh "$(maybeSudo)$diskoScript" } nixosInstall() { @@ -912,12 +899,12 @@ nixosInstall() { step Copying extra files tar -C "$extraFiles" -cpf- . | runSsh "${maybeSudoCommand} tar -C /mnt -xf- --no-same-owner" - runSsh "$(maybeSudo chmod 755 /mnt)" # tar also changes permissions of /mnt + runSsh "$(maybeSudo)chmod 755 /mnt" # tar also changes permissions of /mnt fi if [[ ${#extraFilesOwnership[@]} -gt 0 ]]; then - # shellcheck disable=SC2016 - printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh 'while read file ownership; do '"$(maybeSudo chown -R \$ownership \"/mnt/\$file\")"'; done' + # shellcheck disable=SC2016,SC2086 + printf "%s\n" "${!extraFilesOwnership[@]}" "${extraFilesOwnership[@]}" | pr -2t | runSsh "while read file ownership; do $(maybeSudo)chown -R \$ownership /mnt/\$file; done" fi step Installing NixOS @@ -929,27 +916,27 @@ export PATH="\$PATH:/run/current-system/sw/bin" if [ ! -d "/mnt/tmp" ]; then # needed for installation if initrd-secrets are used - $(maybeSudo mkdir -p /mnt/tmp) - $(maybeSudo chmod 777 /mnt/tmp) + $(maybeSudo)mkdir -p /mnt/tmp + $(maybeSudo)chmod 777 /mnt/tmp fi if [ ${copyHostKeys-n} = "y" ]; then # NB we copy host keys that are in turn copied by kexec installer. - $(maybeSudo mkdir -m 755 -p /mnt/etc/ssh) + $(maybeSudo)mkdir -m 755 -p /mnt/etc/ssh for p in /etc/ssh/ssh_host_*; do # Skip if the source file does not exist (i.e. glob did not match any files) # or the destination already exists (e.g. copied with --extra-files). if [ ! -e "\$p" ] || [ -e "/mnt/\$p" ]; then continue fi - $(maybeSudo cp -a '$p' '/mnt/$p') + $(maybeSudo)cp -a "\$p" "/mnt/\$p" done fi # https://stackoverflow.com/a/13864829 if [ ! -z ${NIXOS_NO_CHECK+0} ]; then export NIXOS_NO_CHECK fi -$(maybeSudo nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem") +$(maybeSudo)nixos-install --no-root-passwd --no-channel-copy --system "$nixosSystem" SSH } @@ -959,11 +946,11 @@ nixosReboot() { runSsh sh </dev/null && [ "\$(zpool list)" != "no pools available" ]; then # we always want to export the zfs pools so people can boot from it without force import - $(maybeSudo umount -Rv /mnt/) - $(maybeSudo swapoff -a) - $(maybeSudo zpool export -a || true) + $(maybeSudo)umount -Rv /mnt/ + $(maybeSudo)swapoff -a + $(maybeSudo)zpool export -a || true fi - $(maybeSudo nohup sh -c 'sleep 6 && reboot') >/dev/null & + $(maybeSudo)nohup sh -c 'sleep 6 && reboot' >/dev/null & SSH step Waiting for the machine to become unreachable due to reboot diff --git a/terraform/all-in-one.md b/terraform/all-in-one.md index d9e4986b..ef41c0d0 100644 --- a/terraform/all-in-one.md +++ b/terraform/all-in-one.md @@ -28,6 +28,10 @@ module "deploy" { # debug_logging = true # build the closure on the remote machine instead of locally # build_on_remote = true + # Optional: SSH password for initial installation + # install_pass = "your-ssh-password" + # Optional: Sudo password for remote operations during installation + # install_sudo_pass = "your-sudo-password" # script is below extra_files_script = "${path.module}/decrypt-ssh-secrets.sh" disk_encryption_key_scripts = [{ @@ -139,7 +143,7 @@ locals { resource "local_file" "nixos_vars" { content = jsonencode(local.nixos_vars) # Converts variables to JSON filename = local.nixos_vars_file # Specifies the output file path - file_permission = "600" + file_permission = "600" # Automatically adds the generated file to Git provisioner "local-exec" { diff --git a/terraform/all-in-one/main.tf b/terraform/all-in-one/main.tf index fd4ec71d..aba9b2cc 100644 --- a/terraform/all-in-one/main.tf +++ b/terraform/all-in-one/main.tf @@ -27,6 +27,8 @@ module "install" { target_user = local.install_user target_host = var.target_host target_port = local.install_port + target_pass = var.install_pass + target_sudo_pass = var.install_sudo_pass nixos_partitioner = module.partitioner-build.result.out nixos_system = module.system-build.result.out ssh_private_key = var.install_ssh_key diff --git a/terraform/all-in-one/variables.tf b/terraform/all-in-one/variables.tf index 4cc33757..00499d39 100644 --- a/terraform/all-in-one/variables.tf +++ b/terraform/all-in-one/variables.tf @@ -149,3 +149,17 @@ variable "install_bootloader" { description = "Install/re-install the bootloader" default = false } + +variable "install_pass" { + type = string + description = "Password used to connect to the target_host during installation" + default = null + sensitive = true +} + +variable "install_sudo_pass" { + type = string + description = "Sudo password for remote sudo operations during installation. Only supported with sudo, not doas." + default = null + sensitive = true +} diff --git a/terraform/install.md b/terraform/install.md index 62c6585b..073c70ab 100644 --- a/terraform/install.md +++ b/terraform/install.md @@ -34,6 +34,10 @@ module "install" { nixos_system = module.system-build.result.out nixos_partitioner = module.disko.result.out target_host = local.ipv4 + # Optional: SSH password authentication + # target_pass = "your-ssh-password" + # Optional: Sudo password for remote operations + # target_sudo_pass = "your-sudo-password" } ``` diff --git a/terraform/install/main.tf b/terraform/install/main.tf index 175da60a..81d441f7 100644 --- a/terraform/install/main.tf +++ b/terraform/install/main.tf @@ -12,6 +12,7 @@ locals { target_host = var.target_host target_port = var.target_port target_pass = var.target_pass + target_sudo_pass = var.target_sudo_pass extra_files_script = var.extra_files_script build_on_remote = var.build_on_remote flake = var.flake diff --git a/terraform/install/run-nixos-anywhere.sh b/terraform/install/run-nixos-anywhere.sh index 1d259a1e..76fd11d3 100755 --- a/terraform/install/run-nixos-anywhere.sh +++ b/terraform/install/run-nixos-anywhere.sh @@ -13,7 +13,14 @@ args=() if [[ ${input[debug_logging]} == "true" ]]; then set -x - declare -p input + # Print input variables but filter out sensitive passwords + for key in "${!input[@]}"; do + if [[ $key == *"pass"* ]]; then + echo "input[$key]='[FILTERED]'" + else + echo "input[$key]='${input[$key]}'" + fi + done args+=("--debug") fi if [[ ${input[kexec_tarball_url]} != "null" ]]; then @@ -40,9 +47,22 @@ args+=(--phases "${input[phases]}") if [[ ${input[ssh_private_key]} != null ]]; then export SSH_PRIVATE_KEY="${input[ssh_private_key]}" fi +if [[ ${input[target_pass]} != null || ${input[target_sudo_pass]} != null ]]; then + args+=("--env-password") +fi +# Temporarily disable debug output when exporting sensitive variables +if [[ ${input[debug_logging]} == "true" ]]; then + set +x +fi if [[ ${input[target_pass]} != null ]]; then export SSHPASS=${input[target_pass]} - args+=("--env-password") +fi +if [[ ${input[target_sudo_pass]} != null ]]; then + export SUDO_PASSWORD=${input[target_sudo_pass]} +fi +# Re-enable debug output if it was enabled +if [[ ${input[debug_logging]} == "true" ]]; then + set -x fi tmpdir=$(mktemp -d) diff --git a/terraform/install/variables.tf b/terraform/install/variables.tf index 7caec0f8..4afa188f 100644 --- a/terraform/install/variables.tf +++ b/terraform/install/variables.tf @@ -41,6 +41,13 @@ variable "target_pass" { default = null } +variable "target_sudo_pass" { + type = string + description = "Sudo password for remote sudo operations on target_host. Only supported with sudo, not doas." + default = null + sensitive = true +} + variable "ssh_private_key" { type = string description = "Content of private key used to connect to the target_host" From 466d13c94053d58a9414bbb098a351da61c6276d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 10 Jun 2025 00:04:06 +0200 Subject: [PATCH 5/5] from-nixos: speed up ssh detection --- tests/from-nixos.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/from-nixos.nix b/tests/from-nixos.nix index c828adec..d7e850f1 100644 --- a/tests/from-nixos.nix +++ b/tests/from-nixos.nix @@ -40,7 +40,7 @@ installer.succeed("chmod 600 /tmp/extra-files/home/user/.ssh/id_ed25519") ssh_key_path = "/etc/ssh/ssh_host_ed25519_key.pub" ssh_key_output = installer.wait_until_succeeds(f""" - ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ + timeout 3 ssh -i /root/.ssh/install_key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ root@installed cat {ssh_key_path} """) installer.succeed("""