Created
November 19, 2020 15:38
-
-
Save larkery/e5ef38ef2ece32287e810bcd051ce8e4 to your computer and use it in GitHub Desktop.
A nix file which makes scripts to deploy a system to digitalocean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# I have created a monster | |
# Given an input at networkPath that looks like | |
# { | |
# network.name = "test"; | |
# machine-1 = { | |
# size = "c-32"; | |
# region = "lon1" | |
# config = {config, pkgs, ...} : | |
# { | |
# YOUR CONFIG HERE | |
# }; | |
# }; | |
# machine-2 = { | |
# AND SO ON | |
# }; | |
# } | |
# it generates a collection of shell scripts which will provision | |
# and deploy machine-1 and machine2 if you run them. | |
# the scripts will be in ./result if you use nix-build | |
# and look like | |
# ./result/bin/activate.sh - provisions and deploys all machines | |
# ./result/bin/provision-machine-1.sh - provisions machine-1 | |
# ./result/bin/deploy-machine-1.sh - deploys the config into machine 1 | |
# (you have to provision first) | |
# similar for machine-2 and so on. | |
# | |
# The provisioning scripts check whether a machine exists, and create | |
# it if it doesn't. | |
{ | |
pkgs ? (import <nixpkgs> {}), | |
networkPath ? "network.nix", # the network file to make a deployer for | |
target, # the target tag name | |
fingerprint, # the ssh key fingerprint | |
image ? "nixos-20.09", # the base image to use with doctl | |
}: | |
let | |
evalConfig = import "${pkgs.path}/nixos/lib/eval-config.nix"; | |
network = import networkPath; | |
lib = pkgs.lib; | |
machines = lib.attrNames (removeAttrs network ["network"]); | |
libScript = pkgs.writeTextFile { | |
name = "lib.sh"; | |
text = '' | |
doctl () { | |
${pkgs.doctl}/bin/doctl "$@" | |
} | |
jq () { | |
${pkgs.jq}/bin/jq "$@" | |
} | |
find_droplet () { | |
machine=$1 | |
doctl compute droplet ls --tag-name ${target} -o json | | |
jq -r ' | |
.[] | | |
select(.name == "'$machine'") | | |
@sh | |
" | |
d_id=\(.id) | |
d_status=\(.status) | |
d_size=\(.size_slug) | |
d_region=\(.region.slug) | |
d_address=\(.networks.v4[]|select(.type=="public")|.ip_address) | |
"' | |
} | |
# todo doctl can create multiple droplets at once, so we could group-by for this! | |
create_droplet () { | |
machine=$1 | |
size=$2 | |
region=$3 | |
echo "create $machine $size $region in 2s" | |
sleep 2 | |
echo "starting..." | |
doctl compute droplet create $machine --wait --region $region --image ${image} --size $size --tag-name ${target} --ssh-keys ${fingerprint} | |
} | |
ensure_droplet () { | |
machine=$1 | |
size=$2 | |
region=$3 | |
source <( find_droplet "$machine" ) | |
if [[ -z $d_id ]]; then | |
create_droplet "$@" | |
elif [[ $d_size != $size ]] || [[ $d_region != $region ]]; then | |
echo "droplet exists, but has size/region $d_size $d_region not $size $region" | |
fi | |
echo "$machine = $d_id $d_status" | |
case $d_status in | |
active) | |
;; | |
off) | |
echo "powering on machine" | |
doctl compute droplet-action power-on $d_id --wait | |
;; | |
new) | |
while [[ $d_status != active ]]; do | |
echo "wait for droplet to become active" | |
sleep 5 | |
source <( find_droplet "$machine" ) | |
done | |
;; | |
archive) | |
echo "$machine is archived!" | |
;; | |
*) | |
;; | |
esac | |
} | |
''; | |
}; | |
provisionScript = | |
machine : | |
let | |
machineDef = network."${machine}"; | |
in | |
pkgs.writeScriptBin "provision-${machine}.sh" '' | |
#!${pkgs.bash}/bin/bash | |
. ${libScript} | |
ensure_droplet "${machine}" "${machineDef.size}" "${machineDef.region}" | |
''; | |
deployScript = | |
machine : | |
let | |
# TODO probably need to add a key for root | |
module = network."${machine}".module; | |
system = evalConfig { | |
modules = [ module ./digitalocean.nix ]; | |
}; | |
system-root = system.config.system.build.toplevel; | |
in | |
pkgs.writeScriptBin "deploy-${machine}.sh" '' | |
#!${pkgs.bash}/bin/bash | |
echo "deploy ${system-root} to ${machine} in ${target}" | |
. ${libScript} | |
source <( find_droplet "${machine}" ) | |
if [[ -z $d_address ]]; then | |
echo "can't find ${machine}" | |
exit 1 | |
fi | |
# copy stuff into machine | |
nix-copy-closure --to root@$d_address -s ${system-root} | |
ssh root@$d_address nix-env --profile /nix/var/nix/profiles/system --set ${system-root} | |
ssh root@$d_address ${system-root}/activate | |
''; | |
activateScript = | |
machine: pkgs.writeScript "activate-${machine}.sh" '' | |
#!${pkgs.bash}/bin/bash | |
set -eu | |
${provisionScript machine}/bin/provision-${machine}.sh | |
${deployScript machine}/bin/deploy-${machine}.sh | |
''; | |
deployScripts = map deployScript machines; | |
provisionScripts = map provisionScript machines; | |
activateScripts = map activateScript machines; | |
activateAll = pkgs.writeScriptBin "activate.sh" '' | |
#!${pkgs.bash}/bin/bash | |
# at the moment we run them all in parallel, which may not be sensible. | |
${builtins.concatStringsSep " & \n" activateScripts} | |
wait | |
''; | |
in | |
pkgs.symlinkJoin { | |
name = "${network.network.name}-${target}"; | |
paths = | |
[activateAll] ++ | |
provisionScripts ++ | |
deployScripts; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment