Skip to content

Instantly share code, notes, and snippets.

@larkery
Created November 19, 2020 15:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save larkery/e5ef38ef2ece32287e810bcd051ce8e4 to your computer and use it in GitHub Desktop.
Save larkery/e5ef38ef2ece32287e810bcd051ce8e4 to your computer and use it in GitHub Desktop.
A nix file which makes scripts to deploy a system to digitalocean.
# 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