289 lines
7.3 KiB
Plaintext
Executable File
289 lines
7.3 KiB
Plaintext
Executable File
#!/usr/bin/env nix-shell
|
|
#!nix-shell -i bash -p nettools
|
|
|
|
SELF=$(hostname)
|
|
|
|
usage() {
|
|
echo "deploy: deploy a nix config to a remote machine, possibly activating it"
|
|
echo ""
|
|
echo "usage: deploy [options] [host] [host2 ...]"
|
|
echo "options:"
|
|
echo "- --copy: only build + copy files, nothing more"
|
|
echo "- --switch (default)"
|
|
echo "- --test: switch to the new configuration, but do not make it bootable"
|
|
echo "- --dry-run: show what would be done without actually doing it"
|
|
echo "- --pre: alias for --action copy --variant all all"
|
|
echo "- --reboot: reboot the target machine after deploying (if deployed with no errors)"
|
|
echo "- --reboot-force: reboot the target machine after deploying (even on error)"
|
|
echo "- --variant light|min|''|all (default: '')"
|
|
echo "- --wireguard always|never|opportunistic: deploy over wireguard"
|
|
echo "- --ip <address>: deploy to the specific IP address"
|
|
echo "- --deriv /nix/store/...: prebuilt store path to deploy instead of (re-)building the default target"
|
|
echo ""
|
|
echo "common idioms:"
|
|
echo "- deploy all: deploy all hosts, sequentially"
|
|
echo "- deploy --pre: build and copy all hosts"
|
|
echo "- deploy desko lappy: build and deploy just those hosts"
|
|
echo "- deploy: deploy the local host"
|
|
exit 1
|
|
}
|
|
|
|
info() {
|
|
echo "[deploy]" "$@"
|
|
}
|
|
|
|
action=switch
|
|
hosts=()
|
|
ip=
|
|
defaultHost="$SELF"
|
|
variants=()
|
|
defaultVariant=
|
|
# by default, don't ship builds to servo. i'd guess this can be overriden by passing --builders servo
|
|
nixArgs=(--builders "")
|
|
doReboot=
|
|
doRebootForce=
|
|
dryRun=
|
|
wireguard=opportunistic
|
|
storePath=
|
|
addHost() {
|
|
if [ "$1" = all ]; then
|
|
# order matters:
|
|
hosts+=(moby lappy desko servo crappy)
|
|
else
|
|
hosts+=("$1")
|
|
fi
|
|
}
|
|
addVariant() {
|
|
if [ "$1" = all ]; then
|
|
variants+=("-min" "-light" "" "-min-next" "-light-next" "-next")
|
|
elif [ -n "$1" ]; then
|
|
variants+=("-$1")
|
|
else
|
|
# "full" variant
|
|
variants+=("")
|
|
fi
|
|
}
|
|
parseArgs() {
|
|
while [ "$#" -ne 0 ]; do
|
|
local arg=$1
|
|
shift
|
|
case "$arg" in
|
|
(--copy|--switch|--test)
|
|
action=${arg/--/}
|
|
;;
|
|
(--deriv)
|
|
storePath="$1"
|
|
shift
|
|
;;
|
|
(--dry-run)
|
|
dryRun=1
|
|
;;
|
|
(--help)
|
|
usage
|
|
;;
|
|
(--ip)
|
|
ip="$1"
|
|
shift
|
|
;;
|
|
(--pre)
|
|
action=copy
|
|
defaultVariant=all
|
|
defaultHost=all
|
|
;;
|
|
(--reboot)
|
|
doReboot=1
|
|
;;
|
|
(--reboot-force)
|
|
doReboot=1
|
|
doRebootForce=1
|
|
;;
|
|
(--variant)
|
|
addVariant "$1"
|
|
shift
|
|
;;
|
|
(--wireguard)
|
|
wireguard="$1"
|
|
shift
|
|
;;
|
|
(all|crappy|desko|lappy|moby|servo)
|
|
addHost "$arg"
|
|
;;
|
|
(*)
|
|
nixArgs+=("$arg")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "${#hosts[@]}" -eq 0 ] && [ -n "$defaultHost" ]; then
|
|
addHost "$defaultHost"
|
|
fi
|
|
if [ "${#variants[@]}" -eq 0 ]; then
|
|
addVariant "$defaultVariant"
|
|
fi
|
|
}
|
|
|
|
destructive() {
|
|
if [ -z "$dryRun" ]; then
|
|
if [ -n "$ECHO_CMD" ]; then
|
|
echo "$@"
|
|
fi
|
|
"$@"
|
|
else
|
|
echo "dry-run: $@"
|
|
fi
|
|
}
|
|
|
|
# return "$1" or "$1-hn", based on if wireguard was requested or not
|
|
resolveHost() {
|
|
if [ -n "$ip" ]; then
|
|
echo "$ip"
|
|
else
|
|
case "$wireguard-$1" in
|
|
(opportunistic-moby)
|
|
echo "$1-hn"
|
|
;;
|
|
(opportunistic-*)
|
|
echo "$1"
|
|
;;
|
|
(never-*)
|
|
echo "$1"
|
|
;;
|
|
(always-*)
|
|
echo "$1-hn"
|
|
;;
|
|
(*)
|
|
echo "$1-hn"
|
|
;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
# return the number of seconds to allot to `nix copy` when copying the given variant.
|
|
# this is done to avoid stalled copies from blocking the entire process, while hopefully not breaking the copies that are actually important
|
|
timeoutFor() {
|
|
case $1 in
|
|
(-min|-light|-next)
|
|
echo 3600
|
|
;;
|
|
(-min-next|-light-next)
|
|
echo 1800
|
|
;;
|
|
(*)
|
|
# this catches the normal variant
|
|
echo 14400
|
|
;;
|
|
esac
|
|
}
|
|
|
|
runOnTarget() {
|
|
local host="$1"
|
|
shift
|
|
# run the command ($@) on the machine we're deploying to.
|
|
# if that's a remote machine, then do it via ssh, else local shell.
|
|
if [ -n "$host" ] && [ "$host" != "$SELF" ]; then
|
|
info "running on remote ($host):" "$@"
|
|
ssh "$host" "$@"
|
|
else
|
|
info "running locally ($SELF):" "$@"
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
# deployOneHost $host $variant
|
|
deployOneHost() {
|
|
local host="$1"
|
|
local variant="$2"
|
|
|
|
local timeout=$(timeoutFor "$variant")
|
|
|
|
local myStorePath="$storePath"
|
|
if [ -z "$myStorePath" ]; then
|
|
# `nix-build -A foo` evals and then realizes foo, but it never unloads the memory used to eval foo.
|
|
# my exprs are heavyweight, we need that memory for building, so do the evals separately from the realizations:
|
|
info "evaluating $host$variant..."
|
|
local drvPath=$(nix eval --raw -f . "hosts.$host$variant.toplevel.drvPath")
|
|
info "building $host$variant ($drvPath)"
|
|
myStorePath=$(destructive nix-store --realize "$drvPath" "${nixArgs[@]}")
|
|
if [ -z "$myStorePath" ]; then
|
|
return 1
|
|
fi
|
|
info "built $host$variant -> $myStorePath"
|
|
fi
|
|
|
|
# mimic `nixos-rebuild --target-host`, in effect:
|
|
# - nix-copy-closure ...
|
|
# - nix-env --set ...
|
|
# - switch-to-configuration <boot|dry-activate|switch|test|>
|
|
# avoid the actual `nixos-rebuild` for a few reasons:
|
|
# - fewer nix evals
|
|
# - more introspectability and debuggability
|
|
# - sandbox friendliness (especially: `git` doesn't have to be run as root)
|
|
|
|
local netHost=$(resolveHost "$host")
|
|
|
|
if [ -n "$host" ] && [ "$host" != "$SELF" ]; then
|
|
if [ -e /run/secrets/nix_signing_key ]; then
|
|
info "signing store paths ..."
|
|
destructive sudo nix store sign -r -k /run/secrets/nix_signing_key "$myStorePath"
|
|
else
|
|
info "not signing store paths: /run/secrets/nix_signing_key does not exist"
|
|
fi
|
|
# add more `-v` for more verbosity (up to 5).
|
|
# builders-use-substitutes false: optimizes so that the remote machine doesn't try to get paths from its substituters.
|
|
# we already have all paths here, and the remote substitution is slow to check and SERIOUSLY flaky on moby in particular.
|
|
ECHO_CMD=1 destructive timeout "$timeout" nix copy -vv --option builders-use-substitutes false --to "ssh-ng://$netHost" "$myStorePath" || return 1
|
|
fi
|
|
|
|
if [ -n "$action" ] && [ "$action" != "copy" ]; then
|
|
info "activating profile... "
|
|
destructive runOnTarget "$netHost" sudo nix-env -p /nix/var/nix/profiles/system --set "$myStorePath" || return 1
|
|
destructive runOnTarget "$netHost" sudo "$myStorePath/bin/switch-to-configuration" "$action"
|
|
local rc=$?
|
|
if [[ -n "$doReboot" && ("$rc" -eq 0 || -n "$doRebootForce") ]]; then
|
|
info "rebooting $host"
|
|
destructive runOnTarget "$netHost" sane-reboot "$host" || return 1
|
|
fi
|
|
return $rc
|
|
fi
|
|
}
|
|
|
|
failedDeploys=()
|
|
deployHosts() {
|
|
local hosts=("$@")
|
|
for v in "${variants[@]}"; do
|
|
for h in "${hosts[@]}"; do
|
|
deployOneHost "$h" "$v" || \
|
|
failedDeploys+=("$h$v")
|
|
done
|
|
done
|
|
}
|
|
|
|
parseArgs "$@"
|
|
|
|
# i care e.g. that full moby is deployed before crappy:
|
|
earlyHosts=()
|
|
lateHosts=()
|
|
for host in "${hosts[@]}"; do
|
|
case $host in
|
|
(crappy)
|
|
lateHosts+=("$host")
|
|
;;
|
|
(*)
|
|
earlyHosts+=("$host")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
deployHosts "${earlyHosts[@]}"
|
|
deployHosts "${lateHosts[@]}"
|
|
|
|
if [ "${#failedDeploys[@]}" -ne 0 ]; then
|
|
echo "FAILED DEPLOYMENT:"
|
|
for d in "${failedDeploys[@]}"; do
|
|
echo "- $d"
|
|
done
|
|
exit 1
|
|
else
|
|
echo "SUCCESS"
|
|
fi
|