263a81e285
This adds support for deploying to remote hosts without being root: sudo nixos-rebuild --target-host non-root@host Without this change, only root@host is able to deploy. The idea is that if the local command is run with sudo, so should the remote one, thus there is no need for adding any CLI options.
409 lines
12 KiB
Bash
409 lines
12 KiB
Bash
#! @shell@
|
||
|
||
if [ -x "@shell@" ]; then export SHELL="@shell@"; fi;
|
||
|
||
set -e
|
||
|
||
showSyntax() {
|
||
exec man nixos-rebuild
|
||
exit 1
|
||
}
|
||
|
||
|
||
# Parse the command line.
|
||
origArgs=("$@")
|
||
extraBuildFlags=()
|
||
action=
|
||
buildNix=1
|
||
fast=
|
||
rollback=
|
||
upgrade=
|
||
repair=
|
||
profile=/nix/var/nix/profiles/system
|
||
buildHost=
|
||
targetHost=
|
||
maybeSudo=
|
||
|
||
while [ "$#" -gt 0 ]; do
|
||
i="$1"; shift 1
|
||
case "$i" in
|
||
--help)
|
||
showSyntax
|
||
;;
|
||
switch|boot|test|build|edit|dry-build|dry-run|dry-activate|build-vm|build-vm-with-bootloader)
|
||
if [ "$i" = dry-run ]; then i=dry-build; fi
|
||
action="$i"
|
||
;;
|
||
--install-grub)
|
||
echo "$0: --install-grub deprecated, use --install-bootloader instead" >&2
|
||
export NIXOS_INSTALL_BOOTLOADER=1
|
||
;;
|
||
--install-bootloader)
|
||
export NIXOS_INSTALL_BOOTLOADER=1
|
||
;;
|
||
--no-build-nix)
|
||
buildNix=
|
||
;;
|
||
--rollback)
|
||
rollback=1
|
||
;;
|
||
--upgrade)
|
||
upgrade=1
|
||
;;
|
||
--repair)
|
||
repair=1
|
||
extraBuildFlags+=("$i")
|
||
;;
|
||
--max-jobs|-j|--cores|-I|--builders)
|
||
j="$1"; shift 1
|
||
extraBuildFlags+=("$i" "$j")
|
||
;;
|
||
--show-trace|--keep-failed|-K|--keep-going|-k|--verbose|-v|-vv|-vvv|-vvvv|-vvvvv|--fallback|--repair|--no-build-output|-Q|-j*)
|
||
extraBuildFlags+=("$i")
|
||
;;
|
||
--option)
|
||
j="$1"; shift 1
|
||
k="$1"; shift 1
|
||
extraBuildFlags+=("$i" "$j" "$k")
|
||
;;
|
||
--fast)
|
||
buildNix=
|
||
fast=1
|
||
extraBuildFlags+=(--show-trace)
|
||
;;
|
||
--profile-name|-p)
|
||
if [ -z "$1" ]; then
|
||
echo "$0: ‘--profile-name’ requires an argument"
|
||
exit 1
|
||
fi
|
||
if [ "$1" != system ]; then
|
||
profile="/nix/var/nix/profiles/system-profiles/$1"
|
||
mkdir -p -m 0755 "$(dirname "$profile")"
|
||
fi
|
||
shift 1
|
||
;;
|
||
--build-host|h)
|
||
buildHost="$1"
|
||
shift 1
|
||
;;
|
||
--target-host|t)
|
||
targetHost="$1"
|
||
shift 1
|
||
;;
|
||
*)
|
||
echo "$0: unknown option \`$i'"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
if [ -n "$SUDO_USER" ]; then
|
||
maybeSudo="sudo "
|
||
fi
|
||
|
||
if [ -z "$buildHost" -a -n "$targetHost" ]; then
|
||
buildHost="$targetHost"
|
||
fi
|
||
if [ "$targetHost" = localhost ]; then
|
||
targetHost=
|
||
fi
|
||
if [ "$buildHost" = localhost ]; then
|
||
buildHost=
|
||
fi
|
||
|
||
buildHostCmd() {
|
||
if [ -z "$buildHost" ]; then
|
||
"$@"
|
||
elif [ -n "$remoteNix" ]; then
|
||
ssh $SSHOPTS "$buildHost" env PATH="$remoteNix:$PATH" "$maybeSudo$@"
|
||
else
|
||
ssh $SSHOPTS "$buildHost" "$maybeSudo$@"
|
||
fi
|
||
}
|
||
|
||
targetHostCmd() {
|
||
if [ -z "$targetHost" ]; then
|
||
"$@"
|
||
else
|
||
ssh $SSHOPTS "$targetHost" "$maybeSudo$@"
|
||
fi
|
||
}
|
||
|
||
copyToTarget() {
|
||
if ! [ "$targetHost" = "$buildHost" ]; then
|
||
if [ -z "$targetHost" ]; then
|
||
NIX_SSHOPTS=$SSHOPTS nix-copy-closure --from "$buildHost" "$1"
|
||
elif [ -z "$buildHost" ]; then
|
||
NIX_SSHOPTS=$SSHOPTS nix-copy-closure --to "$targetHost" "$1"
|
||
else
|
||
buildHostCmd nix-copy-closure --to "$targetHost" "$1"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
nixBuild() {
|
||
if [ -z "$buildHost" ]; then
|
||
nix-build "$@"
|
||
else
|
||
local instArgs=()
|
||
local buildArgs=()
|
||
|
||
while [ "$#" -gt 0 ]; do
|
||
local i="$1"; shift 1
|
||
case "$i" in
|
||
-o)
|
||
local out="$1"; shift 1
|
||
buildArgs+=("--add-root" "$out" "--indirect")
|
||
;;
|
||
-A)
|
||
local j="$1"; shift 1
|
||
instArgs+=("$i" "$j")
|
||
;;
|
||
-I) # We don't want this in buildArgs
|
||
shift 1
|
||
;;
|
||
--no-out-link) # We don't want this in buildArgs
|
||
;;
|
||
"<"*) # nix paths
|
||
instArgs+=("$i")
|
||
;;
|
||
*)
|
||
buildArgs+=("$i")
|
||
;;
|
||
esac
|
||
done
|
||
|
||
local drv="$(nix-instantiate "${instArgs[@]}" "${extraBuildFlags[@]}")"
|
||
if [ -a "$drv" ]; then
|
||
NIX_SSHOPTS=$SSHOPTS nix-copy-closure --to "$buildHost" "$drv"
|
||
buildHostCmd nix-store -r "$drv" "${buildArgs[@]}"
|
||
else
|
||
echo "nix-instantiate failed"
|
||
exit 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
if [ -z "$action" ]; then showSyntax; fi
|
||
|
||
# Only run shell scripts from the Nixpkgs tree if the action is
|
||
# "switch", "boot", or "test". With other actions (such as "build"),
|
||
# the user may reasonably expect that no code from the Nixpkgs tree is
|
||
# executed, so it's safe to run nixos-rebuild against a potentially
|
||
# untrusted tree.
|
||
canRun=
|
||
if [ "$action" = switch -o "$action" = boot -o "$action" = test ]; then
|
||
canRun=1
|
||
fi
|
||
|
||
|
||
# If ‘--upgrade’ is given, run ‘nix-channel --update nixos’.
|
||
if [ -n "$upgrade" -a -z "$_NIXOS_REBUILD_REEXEC" ]; then
|
||
nix-channel --update nixos
|
||
|
||
# If there are other channels that contain a file called
|
||
# ".update-on-nixos-rebuild", update them as well.
|
||
for channelpath in /nix/var/nix/profiles/per-user/root/channels/*; do
|
||
if [ -e "$channelpath/.update-on-nixos-rebuild" ]; then
|
||
nix-channel --update "$(basename "$channelpath")"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Make sure that we use the Nix package we depend on, not something
|
||
# else from the PATH for nix-{env,instantiate,build}. This is
|
||
# important, because NixOS defaults the architecture of the rebuilt
|
||
# system to the architecture of the nix-* binaries used. So if on an
|
||
# amd64 system the user has an i686 Nix package in her PATH, then we
|
||
# would silently downgrade the whole system to be i686 NixOS on the
|
||
# next reboot.
|
||
if [ -z "$_NIXOS_REBUILD_REEXEC" ]; then
|
||
export PATH=@nix@/bin:$PATH
|
||
fi
|
||
|
||
# Re-execute nixos-rebuild from the Nixpkgs tree.
|
||
if [ -z "$_NIXOS_REBUILD_REEXEC" -a -n "$canRun" -a -z "$fast" ]; then
|
||
if p=$(nix-build --no-out-link --expr 'with import <nixpkgs/nixos> {}; config.system.build.nixos-rebuild' "${extraBuildFlags[@]}"); then
|
||
export _NIXOS_REBUILD_REEXEC=1
|
||
exec $p/bin/nixos-rebuild "${origArgs[@]}"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# Find configuration.nix and open editor instead of building.
|
||
if [ "$action" = edit ]; then
|
||
NIXOS_CONFIG=${NIXOS_CONFIG:-$(nix-instantiate --find-file nixos-config)}
|
||
exec "${EDITOR:-nano}" "$NIXOS_CONFIG"
|
||
exit 1
|
||
fi
|
||
|
||
|
||
tmpDir=$(mktemp -t -d nixos-rebuild.XXXXXX)
|
||
SSHOPTS="$NIX_SSHOPTS -o ControlMaster=auto -o ControlPath=$tmpDir/ssh-%n -o ControlPersist=60"
|
||
|
||
cleanup() {
|
||
for ctrl in "$tmpDir"/ssh-*; do
|
||
ssh -o ControlPath="$ctrl" -O exit dummyhost 2>/dev/null || true
|
||
done
|
||
rm -rf "$tmpDir"
|
||
}
|
||
trap cleanup EXIT
|
||
|
||
|
||
|
||
# If the Nix daemon is running, then use it. This allows us to use
|
||
# the latest Nix from Nixpkgs (below) for expression evaluation, while
|
||
# still using the old Nix (via the daemon) for actual store access.
|
||
# This matters if the new Nix in Nixpkgs has a schema change. It
|
||
# would upgrade the schema, which should only happen once we actually
|
||
# switch to the new configuration.
|
||
# If --repair is given, don't try to use the Nix daemon, because the
|
||
# flag can only be used directly.
|
||
if [ -z "$repair" ] && systemctl show nix-daemon.socket nix-daemon.service | grep -q ActiveState=active; then
|
||
export NIX_REMOTE=${NIX_REMOTE-daemon}
|
||
fi
|
||
|
||
|
||
# First build Nix, since NixOS may require a newer version than the
|
||
# current one.
|
||
if [ -n "$rollback" -o "$action" = dry-build ]; then
|
||
buildNix=
|
||
fi
|
||
|
||
nixSystem() {
|
||
machine="$(uname -m)"
|
||
if [[ "$machine" =~ i.86 ]]; then
|
||
machine=i686
|
||
fi
|
||
echo $machine-linux
|
||
}
|
||
|
||
prebuiltNix() {
|
||
machine="$1"
|
||
if [ "$machine" = x86_64 ]; then
|
||
echo @nix_x86_64_linux@
|
||
elif [[ "$machine" =~ i.86 ]]; then
|
||
echo @nix_i686_linux@
|
||
else
|
||
echo "$0: unsupported platform"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
remotePATH=
|
||
|
||
if [ -n "$buildNix" ]; then
|
||
echo "building Nix..." >&2
|
||
nixDrv=
|
||
if ! nixDrv="$(nix-instantiate '<nixpkgs/nixos>' --add-root $tmpDir/nix.drv --indirect -A config.nix.package.out "${extraBuildFlags[@]}")"; then
|
||
if ! nixDrv="$(nix-instantiate '<nixpkgs>' --add-root $tmpDir/nix.drv --indirect -A nix "${extraBuildFlags[@]}")"; then
|
||
if ! nixStorePath="$(nix-instantiate --eval '<nixpkgs/nixos/modules/installer/tools/nix-fallback-paths.nix>' -A $(nixSystem) | sed -e 's/^"//' -e 's/"$//')"; then
|
||
nixStorePath="$(prebuiltNix "$(uname -m)")"
|
||
fi
|
||
if ! nix-store -r $nixStorePath --add-root $tmpDir/nix --indirect \
|
||
--option extra-binary-caches https://cache.nixos.org/; then
|
||
echo "warning: don't know how to get latest Nix" >&2
|
||
fi
|
||
# Older version of nix-store -r don't support --add-root.
|
||
[ -e $tmpDir/nix ] || ln -sf $nixStorePath $tmpDir/nix
|
||
if [ -n "$buildHost" ]; then
|
||
remoteNixStorePath="$(prebuiltNix "$(buildHostCmd uname -m)")"
|
||
remoteNix="$remoteNixStorePath/bin"
|
||
if ! buildHostCmd nix-store -r $remoteNixStorePath \
|
||
--option extra-binary-caches https://cache.nixos.org/ >/dev/null; then
|
||
remoteNix=
|
||
echo "warning: don't know how to get latest Nix" >&2
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
if [ -a "$nixDrv" ]; then
|
||
nix-store -r "$nixDrv"'!'"out" --add-root $tmpDir/nix --indirect >/dev/null
|
||
if [ -n "$buildHost" ]; then
|
||
nix-copy-closure --to "$buildHost" "$nixDrv"
|
||
# The nix build produces multiple outputs, we add them all to the remote path
|
||
for p in $(buildHostCmd nix-store -r "$(readlink "$nixDrv")" "${buildArgs[@]}"); do
|
||
remoteNix="$remoteNix${remoteNix:+:}$p/bin"
|
||
done
|
||
fi
|
||
fi
|
||
PATH="$tmpDir/nix/bin:$PATH"
|
||
fi
|
||
|
||
|
||
# Update the version suffix if we're building from Git (so that
|
||
# nixos-version shows something useful).
|
||
if [ -n "$canRun" ]; then
|
||
if nixpkgs=$(nix-instantiate --find-file nixpkgs "${extraBuildFlags[@]}"); then
|
||
suffix=$($SHELL $nixpkgs/nixos/modules/installer/tools/get-version-suffix "${extraBuildFlags[@]}" || true)
|
||
if [ -n "$suffix" ]; then
|
||
echo -n "$suffix" > "$nixpkgs/.version-suffix" || true
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
|
||
if [ "$action" = dry-build ]; then
|
||
extraBuildFlags+=(--dry-run)
|
||
fi
|
||
|
||
|
||
# Either upgrade the configuration in the system profile (for "switch"
|
||
# or "boot"), or just build it and create a symlink "result" in the
|
||
# current directory (for "build" and "test").
|
||
if [ -z "$rollback" ]; then
|
||
echo "building the system configuration..." >&2
|
||
if [ "$action" = switch -o "$action" = boot ]; then
|
||
pathToConfig="$(nixBuild '<nixpkgs/nixos>' --no-out-link -A system "${extraBuildFlags[@]}")"
|
||
copyToTarget "$pathToConfig"
|
||
targetHostCmd nix-env -p "$profile" --set "$pathToConfig"
|
||
elif [ "$action" = test -o "$action" = build -o "$action" = dry-build -o "$action" = dry-activate ]; then
|
||
pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A system -k "${extraBuildFlags[@]}")"
|
||
elif [ "$action" = build-vm ]; then
|
||
pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vm -k "${extraBuildFlags[@]}")"
|
||
elif [ "$action" = build-vm-with-bootloader ]; then
|
||
pathToConfig="$(nixBuild '<nixpkgs/nixos>' -A vmWithBootLoader -k "${extraBuildFlags[@]}")"
|
||
else
|
||
showSyntax
|
||
fi
|
||
# Copy build to target host if we haven't already done it
|
||
if ! [ "$action" = switch -o "$action" = boot ]; then
|
||
copyToTarget "$pathToConfig"
|
||
fi
|
||
else # [ -n "$rollback" ]
|
||
if [ "$action" = switch -o "$action" = boot ]; then
|
||
targetHostCmd nix-env --rollback -p "$profile"
|
||
pathToConfig="$profile"
|
||
elif [ "$action" = test -o "$action" = build ]; then
|
||
systemNumber=$(
|
||
targetHostCmd nix-env -p "$profile" --list-generations |
|
||
sed -n '/current/ {g; p;}; s/ *\([0-9]*\).*/\1/; h'
|
||
)
|
||
pathToConfig="$profile"-${systemNumber}-link
|
||
if [ -z "$targetHost" ]; then
|
||
ln -sT "$pathToConfig" ./result
|
||
fi
|
||
else
|
||
showSyntax
|
||
fi
|
||
fi
|
||
|
||
|
||
# If we're not just building, then make the new configuration the boot
|
||
# default and/or activate it now.
|
||
if [ "$action" = switch -o "$action" = boot -o "$action" = test -o "$action" = dry-activate ]; then
|
||
if ! targetHostCmd $pathToConfig/bin/switch-to-configuration "$action"; then
|
||
echo "warning: error(s) occurred while switching to the new configuration" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
|
||
if [ "$action" = build-vm ]; then
|
||
cat >&2 <<EOF
|
||
|
||
Done. The virtual machine can be started by running $(echo $pathToConfig/bin/run-*-vm)
|
||
EOF
|
||
fi
|