forked from fediversity/fediversity
880 lines
26 KiB
Bash
Executable file
880 lines
26 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
here=$(dirname "${BASH_SOURCE[0]}")
|
|
flake=""
|
|
flakeAttr=""
|
|
kexecUrl=""
|
|
kexecExtraFlags=""
|
|
sshStoreSettings=""
|
|
enableDebug=""
|
|
nixBuildFlags=()
|
|
diskoAttr=""
|
|
diskoScript=""
|
|
diskoMode="disko"
|
|
diskoDeps=y
|
|
nixosSystem=""
|
|
extraFiles=""
|
|
vmTest="n"
|
|
nixOptions=(
|
|
--extra-experimental-features 'nix-command flakes'
|
|
"--no-write-lock-file"
|
|
)
|
|
SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY-}
|
|
|
|
declare -A phases
|
|
phases[kexec]=1
|
|
phases[disko]=1
|
|
phases[install]=1
|
|
phases[reboot]=1
|
|
|
|
hardwareConfigBackend=none
|
|
hardwareConfigPath=
|
|
sshPrivateKeyFile=
|
|
if [ -t 0 ]; then # stdin is a tty, we allow interactive input to ssh i.e. passwords
|
|
sshTtyParam="-t"
|
|
else
|
|
sshTtyParam="-T"
|
|
fi
|
|
sshConnection=
|
|
postKexecSshPort=22
|
|
buildOnRemote=n
|
|
buildOn=auto
|
|
envPassword=n
|
|
|
|
# Facts set by get-facts.sh
|
|
isOs=
|
|
isArch=
|
|
isKexec=
|
|
isInstaller=
|
|
isContainer=
|
|
hasIpv6Only=
|
|
hasTar=
|
|
hasCpio=
|
|
hasSudo=
|
|
hasDoas=
|
|
hasWget=
|
|
hasCurl=
|
|
hasSetsid=
|
|
hasNixOSFacter=
|
|
|
|
sshKeyDir=$(mktemp -d)
|
|
trap 'rm -rf "$sshKeyDir"' EXIT
|
|
mkdir -p "$sshKeyDir"
|
|
|
|
declare -A diskEncryptionKeys=()
|
|
declare -A extraFilesOwnership=()
|
|
declare -a nixCopyOptions=()
|
|
declare -a sshArgs=()
|
|
|
|
showUsage() {
|
|
cat <<USAGE
|
|
Usage: nixos-anywhere [options] [<ssh-host>]
|
|
|
|
Options:
|
|
|
|
* -f, --flake <flake_uri>
|
|
set the flake to install the system from. i.e.
|
|
nixos-anywhere --flake .#mymachine
|
|
Also supports variants:
|
|
nixos-anywhere --flake .#nixosConfigurations.mymachine.config.virtualisation.vmVariant
|
|
* --target-host <ssh-host>
|
|
set the SSH target host to deploy onto.
|
|
* -i <identity_file>
|
|
selects which SSH private key file to use.
|
|
* -p, --ssh-port <ssh_port>
|
|
set the ssh port to connect with
|
|
* --ssh-option <ssh_option>
|
|
set an ssh option
|
|
* -L, --print-build-logs
|
|
print full build logs
|
|
* --env-password
|
|
set a password used by ssh-copy-id, the password should be set by
|
|
the environment variable SSHPASS
|
|
* -s, --store-paths <disko-script> <nixos-system>
|
|
set the store paths to the disko-script and nixos-system directly
|
|
if this is given, flake is not needed
|
|
* --kexec <path>
|
|
use another kexec tarball to bootstrap NixOS
|
|
* --kexec-extra-flags
|
|
extra flags to add into the call to kexec, e.g. "--no-sync"
|
|
* --ssh-store-setting <key> <value>
|
|
ssh store settings appended to the store URI, e.g. "compress true". <value> needs to be URI encoded.
|
|
* --post-kexec-ssh-port <ssh_port>
|
|
after kexec is executed, use a custom ssh port to connect. Defaults to 22
|
|
* --copy-host-keys
|
|
copy over existing /etc/ssh/ssh_host_* host keys to the installation
|
|
* --extra-files <path>
|
|
contents of local <path> are recursively copied to the root (/) of the new NixOS installation. Existing files are overwritten
|
|
Copied files will be owned by root unless specified by --chown option. See documentation for details.
|
|
* --chown <path> <ownership>
|
|
change ownership of <path> recursively. Recommended to use uid:gid as opposed to username:groupname for ownership.
|
|
Option can be specified more than once.
|
|
* --disk-encryption-keys <remote_path> <local_path>
|
|
copy the contents of the file or pipe in local_path to remote_path in the installer environment,
|
|
after kexec but before installation. Can be repeated.
|
|
* --no-substitute-on-destination
|
|
disable passing --substitute-on-destination to nix-copy
|
|
* --debug
|
|
enable debug output
|
|
* --show-trace
|
|
show nix build traces
|
|
* --option <key> <value>
|
|
nix option to pass to every nix related command
|
|
* --from <store-uri>
|
|
URL of the source Nix store to copy the nixos and disko closure from
|
|
* --build-on-remote
|
|
build the closure on the remote machine instead of locally and copy-closuring it
|
|
* --vm-test
|
|
build the system and test the disk configuration inside a VM without installing it to the target.
|
|
* --generate-hardware-config nixos-facter|nixos-generate-config <path>
|
|
generate a hardware-configuration.nix file using the specified backend and write it to the specified path.
|
|
The backend can be either 'nixos-facter' or 'nixos-generate-config'.
|
|
* --phases
|
|
comma separated list of phases to run. Default is: kexec,disko,install,reboot
|
|
kexec: kexec into the nixos installer
|
|
disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode
|
|
install: install the system
|
|
reboot: unmount the filesystems, export any ZFS pools and reboot the machine
|
|
* --disko-mode disko|mount|format
|
|
set the disko mode to format, mount or destroy. Default is disko.
|
|
disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode
|
|
* --no-disko-deps
|
|
This will only upload the disko script and not the partitioning tools dependencies.
|
|
Installers usually have dependencies available.
|
|
Use this option if your target machine has not enough RAM to store the dependencies in memory.
|
|
* --build-on auto|remote|local
|
|
sets the build on settings to auto, remote or local. Default is auto.
|
|
auto: tries to figure out, if the build is possible on the local host, if not falls back gracefully to remote build
|
|
local: will build on the local host
|
|
remote: will build on the remote host
|
|
USAGE
|
|
}
|
|
|
|
abort() {
|
|
echo "aborted: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
step() {
|
|
echo "### $* ###"
|
|
}
|
|
|
|
parseArgs() {
|
|
local substituteOnDestination=y
|
|
local printBuildLogs=n
|
|
local buildOnRemote=n
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-f | --flake)
|
|
flake=$2
|
|
shift
|
|
;;
|
|
--target-host)
|
|
sshConnection=$2
|
|
shift
|
|
;;
|
|
-i)
|
|
sshPrivateKeyFile=$2
|
|
shift
|
|
;;
|
|
-p | --ssh-port)
|
|
sshArgs+=("-p" "$2")
|
|
shift
|
|
;;
|
|
--ssh-option)
|
|
sshArgs+=("-o" "$2")
|
|
shift
|
|
;;
|
|
-L | --print-build-logs)
|
|
printBuildLogs=y
|
|
;;
|
|
-s | --store-paths)
|
|
diskoScript=$(readlink -f "$2")
|
|
nixosSystem=$(readlink -f "$3")
|
|
shift
|
|
shift
|
|
;;
|
|
--generate-hardware-config)
|
|
if [[ $# -lt 3 ]]; then
|
|
abort "Missing arguments for --generate-hardware-config <backend> <path>"
|
|
fi
|
|
case "$2" in
|
|
nixos-facter | nixos-generate-config)
|
|
hardwareConfigBackend=$2
|
|
;;
|
|
*)
|
|
abort "Unknown hardware config backend: $2"
|
|
;;
|
|
esac
|
|
hardwareConfigPath=$3
|
|
shift
|
|
shift
|
|
;;
|
|
-t | --tty)
|
|
echo "the '$1' flag is deprecated, a tty is now detected automatically" >&2
|
|
;;
|
|
--help)
|
|
showUsage
|
|
exit 0
|
|
;;
|
|
--kexec)
|
|
kexecUrl=$2
|
|
shift
|
|
;;
|
|
--kexec-extra-flags)
|
|
kexecExtraFlags=$2
|
|
shift
|
|
;;
|
|
--ssh-store-setting)
|
|
key=$2
|
|
shift
|
|
value=$2
|
|
shift
|
|
sshStoreSettings+="$sshStoreSettings$key=$value&"
|
|
shift
|
|
;;
|
|
--post-kexec-ssh-port)
|
|
postKexecSshPort=$2
|
|
shift
|
|
;;
|
|
--copy-host-keys)
|
|
copyHostKeys=y
|
|
;;
|
|
--show-trace)
|
|
nixBuildFlags+=("--show-trace")
|
|
;;
|
|
--debug)
|
|
enableDebug="-x"
|
|
printBuildLogs=y
|
|
set -x
|
|
;;
|
|
--disko-mode)
|
|
case "$2" in
|
|
format | mount | disko)
|
|
diskoMode=$2
|
|
;;
|
|
*)
|
|
abort "Supported values for --disko-mode are disko, mount and format. Unknown mode : $2"
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
;;
|
|
--no-disko-deps)
|
|
diskoDeps=n
|
|
;;
|
|
--build-on)
|
|
case "$2" in
|
|
auto | local | remote)
|
|
buildOn=$2
|
|
;;
|
|
*)
|
|
abort "Supported values for --build-on are auto, local and remote. Unknown mode : $2"
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
;;
|
|
--extra-files)
|
|
extraFiles=$2
|
|
shift
|
|
;;
|
|
--chown)
|
|
extraFilesOwnership["$2"]="$3"
|
|
shift
|
|
shift
|
|
;;
|
|
--disk-encryption-keys)
|
|
diskEncryptionKeys["$2"]="$3"
|
|
shift
|
|
shift
|
|
;;
|
|
--phases)
|
|
phases[kexec]=0
|
|
phases[disko]=0
|
|
phases[install]=0
|
|
phases[reboot]=0
|
|
IFS=, read -r -a phaseList <<<"$2"
|
|
for phase in "${phaseList[@]}"; do
|
|
if [[ ${phases[$phase]:-unset} == unset ]]; then
|
|
abort "Unknown phase: $phase"
|
|
fi
|
|
phases[$phase]=1
|
|
done
|
|
shift
|
|
;;
|
|
--stop-after-disko)
|
|
echo "WARNING: --stop-after-disko is deprecated, use --phases kexec,disko instead" 2>&1
|
|
phases[kexec]=1
|
|
phases[disko]=1
|
|
phases[install]=0
|
|
phases[reboot]=0
|
|
;;
|
|
--no-reboot)
|
|
echo "WARNING: --no-reboot is deprecated, use --phases kexec,disko,install instead" 2>&1
|
|
phases[kexec]=1
|
|
phases[disko]=1
|
|
phases[install]=1
|
|
phases[reboot]=0
|
|
;;
|
|
--from)
|
|
nixCopyOptions+=("--from" "$2")
|
|
shift
|
|
;;
|
|
--option)
|
|
key=$2
|
|
shift
|
|
value=$2
|
|
shift
|
|
nixOptions+=("--option" "$key" "$value")
|
|
;;
|
|
--no-substitute-on-destination)
|
|
substituteOnDestination=n
|
|
;;
|
|
--build-on-remote)
|
|
echo "WARNING: --build-on-remote is deprecated, use --build-on remote instead" 2>&1
|
|
buildOnRemote=y
|
|
buildOn="remote"
|
|
;;
|
|
--env-password)
|
|
envPassword=y
|
|
;;
|
|
--vm-test)
|
|
vmTest=y
|
|
;;
|
|
*)
|
|
if [[ -z ${sshConnection} ]]; then
|
|
sshConnection="$1"
|
|
else
|
|
showUsage
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
diskoAttr="${diskoMode}Script"
|
|
|
|
if [[ ${diskoDeps} == "n" ]]; then
|
|
diskoAttr="${diskoAttr}NoDeps"
|
|
fi
|
|
|
|
if [[ ${printBuildLogs} == "y" ]]; then
|
|
nixOptions+=("-L")
|
|
fi
|
|
|
|
if [[ $substituteOnDestination == "y" ]]; then
|
|
nixCopyOptions+=("--substitute-on-destination")
|
|
fi
|
|
|
|
if [[ $vmTest == "n" ]] && [[ -z ${sshConnection} ]]; then
|
|
abort "ssh-host must be set"
|
|
fi
|
|
|
|
if [[ $buildOn == "local" ]] && [[ $buildOnRemote == "y" ]]; then
|
|
abort "Conflicting flags: --build-on local and --build-on-remote used."
|
|
fi
|
|
|
|
if [[ -n ${flake} ]]; then
|
|
if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then
|
|
flake="${BASH_REMATCH[1]}"
|
|
flakeAttr="${BASH_REMATCH[2]}"
|
|
fi
|
|
if [[ -z ${flakeAttr} ]]; then
|
|
echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri." >&2
|
|
echo 'For example, to use the output nixosConfigurations.foo from the flake.nix, append "#foo" to the flake-uri.' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Support .#foo shorthand
|
|
if [[ $flakeAttr != nixosConfigurations.* ]]; then
|
|
flakeAttr="nixosConfigurations.\"$flakeAttr\".config"
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
# ssh wrapper
|
|
runSshNoTty() {
|
|
ssh -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@"
|
|
}
|
|
runSshTimeout() {
|
|
timeout 10 ssh -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@"
|
|
}
|
|
runSsh() {
|
|
ssh "$sshTtyParam" -i "$sshKeyDir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${sshArgs[@]}" "$sshConnection" "$@"
|
|
}
|
|
|
|
nixCopy() {
|
|
NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $sshKeyDir/nixos-anywhere ${sshArgs[*]}" nix copy \
|
|
"${nixOptions[@]}" \
|
|
"${nixCopyOptions[@]}" \
|
|
"$@"
|
|
}
|
|
nixBuild() {
|
|
NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $sshKeyDir/nixos-anywhere ${sshArgs[*]}" nix build \
|
|
--print-out-paths \
|
|
--no-link \
|
|
"${nixBuildFlags[@]}" \
|
|
"${nixOptions[@]}" \
|
|
"$@"
|
|
}
|
|
|
|
runVmTest() {
|
|
if [[ -z ${flakeAttr} ]]; then
|
|
echo "--vm-test is not supported with --store-paths" >&2
|
|
echo "Please use --flake instead or build config.system.build.installTest of your nixos configuration manually" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ${buildOn} == "remote" ]]; then
|
|
echo "--vm-test is not supported with --build-on-remote" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n ${extraFiles} ]]; then
|
|
echo "--vm-test is not supported with --extra-files" >&2
|
|
exit 1
|
|
fi
|
|
if [ ${#diskEncryptionKeys[@]} -gt 0 ]; then
|
|
echo "--vm-test is not supported with --disk-encryption-keys" >&2
|
|
exit 1
|
|
fi
|
|
nix build \
|
|
--print-out-paths \
|
|
--no-link \
|
|
-L \
|
|
"${nixBuildFlags[@]}" \
|
|
"${nixOptions[@]}" \
|
|
"${flake}#${flakeAttr}.system.build.installTest"
|
|
}
|
|
|
|
uploadSshKey() {
|
|
# ssh-copy-id requires this directory
|
|
mkdir -p "$HOME/.ssh/"
|
|
if [[ -n ${sshPrivateKeyFile} ]]; then
|
|
cp "$sshPrivateKeyFile" "$sshKeyDir/nixos-anywhere"
|
|
ssh-keygen -y -f "$sshKeyDir/nixos-anywhere" >"$sshKeyDir/nixos-anywhere.pub"
|
|
else
|
|
# we generate a temporary ssh keypair that we can use during nixos-anywhere
|
|
ssh-keygen -t ed25519 -f "$sshKeyDir"/nixos-anywhere -P "" -C "nixos-anywhere" >/dev/null
|
|
fi
|
|
|
|
declare -a sshCopyIdArgs
|
|
if [[ -n ${sshPrivateKeyFile} ]]; then
|
|
unset SSH_AUTH_SOCK # don't use system agent if key was supplied
|
|
sshCopyIdArgs+=(-o "IdentityFile=${sshPrivateKeyFile}" -f)
|
|
fi
|
|
|
|
step Uploading install SSH keys
|
|
until
|
|
if [[ ${envPassword} == y ]]; then
|
|
sshpass -e \
|
|
ssh-copy-id \
|
|
-i "$sshKeyDir"/nixos-anywhere.pub \
|
|
-o ConnectTimeout=10 \
|
|
-o UserKnownHostsFile=/dev/null \
|
|
-o IdentitiesOnly=yes \
|
|
-o StrictHostKeyChecking=no \
|
|
"${sshCopyIdArgs[@]}" \
|
|
"${sshArgs[@]}" \
|
|
"$sshConnection"
|
|
else
|
|
ssh-copy-id \
|
|
-i "$sshKeyDir"/nixos-anywhere.pub \
|
|
-o ConnectTimeout=10 \
|
|
-o UserKnownHostsFile=/dev/null \
|
|
-o StrictHostKeyChecking=no \
|
|
"${sshCopyIdArgs[@]}" \
|
|
"${sshArgs[@]}" \
|
|
"$sshConnection"
|
|
fi
|
|
do
|
|
sleep 3
|
|
done
|
|
}
|
|
|
|
importFacts() {
|
|
step Gathering machine facts
|
|
local facts filteredFacts
|
|
if ! facts=$(runSsh -o ConnectTimeout=10 enableDebug=$enableDebug sh -- <"$here"/get-facts.sh); then
|
|
exit 1
|
|
fi
|
|
filteredFacts=$(echo "$facts" | grep -E '^(has|is)[A-Za-z0-9_]+=\S+')
|
|
if [[ -z $filteredFacts ]]; then
|
|
abort "Retrieving host facts via ssh failed. Check with --debug for the root cause, unless you have done so already"
|
|
fi
|
|
# make facts available in script
|
|
# 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
|
|
}
|
|
|
|
checkBuildLocally() {
|
|
local system extraPlatforms machineSystem
|
|
system="$(nix --extra-experimental-features 'nix-command flakes' config show system)"
|
|
extraPlatforms="$(nix --extra-experimental-features 'nix-command flakes' config show extra-platforms)"
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
machineSystem=$1
|
|
elif [[ -n ${nixosSystem} ]]; then
|
|
machineSystem="$(cat "${nixosSystem}"/system)"
|
|
else
|
|
machineSystem="$(nix --extra-experimental-features 'nix-command flakes' eval --raw "${flake}"#"${flakeAttr}".pkgs.system 2>/dev/null || echo "unknown")"
|
|
if [[ ${machineSystem} == "unknown" ]]; then
|
|
buildOn=auto
|
|
return
|
|
fi
|
|
fi
|
|
|
|
if [[ ${system} == "${machineSystem}" ]]; then
|
|
buildOn=local
|
|
return
|
|
fi
|
|
|
|
if [[ ${extraPlatforms} == "*${machineSystem}*" ]]; then
|
|
buildOn=local
|
|
return
|
|
fi
|
|
|
|
local entropy
|
|
entropy="$(date +'%Y%m%d%H%M%S')"
|
|
if nix build \
|
|
-L \
|
|
"${nixOptions[@]}" \
|
|
--expr \
|
|
"derivation { system = \"$system\"; name = \"env-$entropy\"; builder = \"/bin/sh\"; args = [ \"-c\" \"echo > \$out\" ]; }"; then
|
|
# The local build failed
|
|
buildOn=local
|
|
fi
|
|
|
|
buildOn=remote
|
|
}
|
|
|
|
generateHardwareConfig() {
|
|
local maybeSudo="$maybeSudo"
|
|
mkdir -p "$(dirname "$hardwareConfigPath")"
|
|
case "$hardwareConfigBackend" in
|
|
nixos-facter)
|
|
if [[ ${isInstaller} == "y" ]]; then
|
|
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"
|
|
;;
|
|
nixos-generate-config)
|
|
step "Generating hardware-configuration.nix using nixos-generate-config"
|
|
runSshNoTty -o ConnectTimeout=10 nixos-generate-config --show-hardware-config --no-filesystems >"$hardwareConfigPath"
|
|
;;
|
|
*)
|
|
abort "Unknown hardware config backend: $hardwareConfigBackend"
|
|
;;
|
|
esac
|
|
|
|
# to make sure nix knows about the new file
|
|
if command -v git >/dev/null; then
|
|
# handle relative paths
|
|
hardwareConfigPath="$(realpath "$hardwareConfigPath")"
|
|
pushd "$(dirname "$hardwareConfigPath")"
|
|
if git rev-parse --is-inside-work-tree >/dev/null; then
|
|
git add --intent-to-add --force -- "$hardwareConfigPath"
|
|
fi
|
|
popd
|
|
fi
|
|
}
|
|
|
|
runKexec() {
|
|
if [[ ${isKexec} == "y" ]] || [[ ${isInstaller} == "y" ]]; then
|
|
return
|
|
fi
|
|
|
|
if [[ ${isContainer} != "none" ]]; then
|
|
echo "WARNING: This script does not support running from a '${isContainer}' container. kexec will likely not work" >&2
|
|
fi
|
|
|
|
if [[ $kexecUrl == "" ]]; then
|
|
case "${isArch}" in
|
|
x86_64 | aarch64)
|
|
kexecUrl="https://github.com/nix-community/nixos-images/releases/download/nixos-24.11/nixos-kexec-installer-noninteractive-${isArch}-linux.tar.gz"
|
|
;;
|
|
*)
|
|
abort "Unsupported architecture: ${isArch}. Our default kexec images only support x86_64 and aarch64 cpus. Checkout https://github.com/nix-community/nixos-anywhere/#using-your-own-kexec-image for more information."
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
step Switching system into kexec
|
|
runSsh sh <<SSH
|
|
set -efu ${enableDebug}
|
|
$maybeSudo rm -rf /root/kexec
|
|
$maybeSudo mkdir -p /root/kexec
|
|
SSH
|
|
|
|
# no way to reach global ipv4 destinations, use gh-v6.com automatically if github url
|
|
if [[ ${hasIpv6Only} == "y" ]] && [[ $kexecUrl == "https://github.com/"* ]]; then
|
|
kexecUrl=${kexecUrl/"github.com"/"gh-v6.com"}
|
|
fi
|
|
|
|
if [[ -f $kexecUrl ]]; then
|
|
runSsh "${maybeSudo} tar -C /root/kexec -xvzf-" <"$kexecUrl"
|
|
elif [[ ${hasCurl} == "y" ]]; then
|
|
runSsh "curl --fail -Ss -L '${kexecUrl}' | ${maybeSudo} tar -C /root/kexec -xvzf-"
|
|
elif [[ ${hasWget} == "y" ]]; then
|
|
runSsh "wget '${kexecUrl}' -O- | ${maybeSudo} tar -C /root/kexec -xvzf-"
|
|
else
|
|
curl --fail -Ss -L "${kexecUrl}" | runSsh "${maybeSudo} tar -C /root/kexec -xvzf-"
|
|
fi
|
|
|
|
runSsh <<SSH
|
|
TMPDIR=/root/kexec setsid ${maybeSudo} /root/kexec/kexec/run --kexec-extra-flags "${kexecExtraFlags}"
|
|
SSH
|
|
|
|
# use the default SSH port to connect at this point
|
|
for i in "${!sshArgs[@]}"; do
|
|
if [[ ${sshArgs[i]} == "-p" ]]; then
|
|
sshArgs[i + 1]=$postKexecSshPort
|
|
break
|
|
fi
|
|
done
|
|
|
|
# wait for machine to become unreachable.
|
|
while runSshTimeout -- exit 0; do sleep 1; done
|
|
|
|
# After kexec we explicitly set the user to root@
|
|
sshConnection="root@${sshHost}"
|
|
|
|
# waiting for machine to become available again
|
|
until runSsh -o ConnectTimeout=10 -- exit 0; do sleep 5; done
|
|
}
|
|
|
|
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]}"
|
|
done
|
|
if [[ -n ${diskoScript} ]]; then
|
|
nixCopy --to "ssh://$sshConnection?$sshStoreSettings" "$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
|
|
# 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=$sshKeyDir%2Fnixos-anywhere&$sshStoreSettings"
|
|
)
|
|
fi
|
|
|
|
step Formatting hard drive with disko
|
|
runSsh "$diskoScript"
|
|
}
|
|
|
|
nixosInstall() {
|
|
local nixosSystem=$1
|
|
if [[ -n ${nixosSystem} ]]; then
|
|
step Uploading the system closure
|
|
nixCopy --to "ssh://$sshConnection?remote-store=local%3Froot=%2Fmnt&$sshStoreSettings" "$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
|
|
# 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=$sshKeyDir%2Fnixos-anywhere&remote-store=local%3Froot=%2Fmnt&$sshStoreSettings"
|
|
)
|
|
fi
|
|
|
|
if [[ -n ${extraFiles} ]]; then
|
|
step Copying extra files
|
|
tar -C "$extraFiles" -cpf- . | runSsh "tar -C /mnt -xf- --no-same-owner"
|
|
|
|
runSsh "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'
|
|
fi
|
|
|
|
step Installing NixOS
|
|
runSsh sh <<SSH
|
|
set -eu ${enableDebug}
|
|
# when running not in nixos we might miss this directory, but it's needed in the nixos chroot during installation
|
|
export PATH="\$PATH:/run/current-system/sw/bin"
|
|
|
|
# needed for installation if initrd-secrets are used
|
|
mkdir -p /mnt/tmp
|
|
chmod 777 /mnt/tmp
|
|
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
|
|
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"
|
|
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"
|
|
if [[ ${phases[reboot]} == 1 ]]; then
|
|
if command -v zpool >/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
|
|
fi
|
|
nohup sh -c 'sleep 6 && reboot' >/dev/null &
|
|
fi
|
|
SSH
|
|
|
|
}
|
|
|
|
main() {
|
|
parseArgs "$@"
|
|
|
|
if [[ ${vmTest} == y ]]; then
|
|
if [[ ${hardwareConfigBackend} != "none" ]]; then
|
|
abort "--vm-test is not supported with --generate-hardware-config. You need to generate the hardware configuration before you can run the VM test." >&2
|
|
fi
|
|
runVmTest
|
|
exit 0
|
|
fi
|
|
|
|
if [[ ${buildOn} == "auto" ]]; then
|
|
checkBuildLocally
|
|
fi
|
|
|
|
# parse flake nixos-install style syntax, get the system attr
|
|
if [[ -n ${flake} ]]; then
|
|
if [[ ${buildOn} == "local" ]] && [[ ${hardwareConfigBackend} == "none" ]]; then
|
|
if [[ ${phases[disko]} == 1 ]]; then
|
|
diskoScript=$(nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}")
|
|
fi
|
|
if [[ ${phases[install]} == 1 ]]; then
|
|
nixosSystem=$(nixBuild "${flake}#${flakeAttr}.system.build.toplevel")
|
|
fi
|
|
fi
|
|
elif [[ -n ${diskoScript} ]] && [[ -n ${nixosSystem} ]]; then
|
|
if [[ ! -e ${diskoScript} ]] || [[ ! -e ${nixosSystem} ]]; then
|
|
abort "${diskoScript} and ${nixosSystem} must be existing store-paths"
|
|
fi
|
|
else
|
|
abort "--flake or --store-paths must be set"
|
|
fi
|
|
|
|
if [[ -n ${SSH_PRIVATE_KEY} ]] && [[ -z ${sshPrivateKeyFile} ]]; then
|
|
# $sshKeyDir is getting deleted on trap EXIT
|
|
sshPrivateKeyFile="$sshKeyDir/from-env"
|
|
(
|
|
umask 077
|
|
printf '%s\n' "$SSH_PRIVATE_KEY" >"$sshPrivateKeyFile"
|
|
)
|
|
fi
|
|
|
|
sshSettings=$(ssh "${sshArgs[@]}" -G "${sshConnection}")
|
|
sshUser=$(echo "$sshSettings" | awk '/^user / { print $2 }')
|
|
sshHost=$(echo "$sshSettings" | awk '/^hostname / { print $2 }')
|
|
|
|
uploadSshKey
|
|
|
|
importFacts
|
|
|
|
if [[ ${hasTar-n} == "n" ]]; then
|
|
abort "no tar command found, but required to unpack kexec tarball"
|
|
fi
|
|
|
|
if [[ ${hasCpio-n} == "n" ]]; then
|
|
abort "no cpio command found, but required to build the new initrd"
|
|
fi
|
|
|
|
if [[ ${hasSetsid-n} == "n" ]]; then
|
|
abort "no setsid command found, but required to run the kexec script under a new session"
|
|
fi
|
|
|
|
maybeSudo=""
|
|
if [[ ${hasSudo-n} == "y" ]]; then
|
|
maybeSudo="sudo"
|
|
elif [[ ${hasDoas-n} == "y" ]]; then
|
|
maybeSudo="doas"
|
|
fi
|
|
|
|
if [[ ${isOs} != "Linux" ]]; then
|
|
abort "This script requires Linux as the operating system, but got $isOs"
|
|
fi
|
|
|
|
if [[ ${phases[kexec]} == 1 ]]; then
|
|
runKexec
|
|
fi
|
|
|
|
if [[ ${hardwareConfigBackend} != "none" ]]; then
|
|
generateHardwareConfig
|
|
fi
|
|
|
|
# Before we do not have a valid hardware configuration we don't know the machine system
|
|
if [[ ${buildOn} == "auto" ]]; then
|
|
local remoteSystem
|
|
remoteSystem=$(runSshNoTty -o ConnectTimeout=10 nix --extra-experimental-features nix-command config show system)
|
|
checkBuildLocally "${remoteSystem}"
|
|
# if we cannot figure it out at this point, we will build on the remote host
|
|
if [[ ${buildOn} == "auto" ]]; then
|
|
buildOn=remote
|
|
fi
|
|
fi
|
|
|
|
if [[ ${buildOn} != "remote" ]] && [[ -n ${flake} ]] && [[ -z ${diskoScript} ]]; then
|
|
if [[ ${phases[disko]} == 1 ]]; then
|
|
diskoScript=$(nixBuild "${flake}#${flakeAttr}.system.build.${diskoAttr}")
|
|
fi
|
|
if [[ ${phases[install]} == 1 ]]; then
|
|
nixosSystem=$(nixBuild "${flake}#${flakeAttr}.system.build.toplevel")
|
|
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
|
|
|
|
if [[ ${phases[install]} == 1 ]]; then
|
|
nixosInstall "$nixosSystem"
|
|
fi
|
|
|
|
if [[ ${phases[reboot]} == 1 ]]; then
|
|
step Waiting for the machine to become unreachable due to reboot
|
|
while runSshTimeout -- exit 0; do sleep 1; done
|
|
fi
|
|
|
|
step "Done!"
|
|
}
|
|
|
|
main "$@"
|