#!/usr/bin/env nix-shell #!nix-shell -i ysh -p nettools -p oils-for-unix var SELF = $(hostname) proc usage { echo "deploy: deploy a nix config to a remote machine, possibly activating it" echo "" echo "usage: deploy [options] [host] [host2 ...]" echo "options:" echo "- --build: only build; don't copy or deploy" 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 ..." echo ' if otherwise unspecified, implies `--variant all`' echo " if no host is specified, copies to all known hosts" 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
: deploy to the specific IP address" echo "- --deriv /nix/store/...: prebuilt store path (or .drv to realize) 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 } proc info (...stmts) { echo "[deploy]" @stmts } var action = "switch" var hosts = [] var ip = null var defaultHost = SELF var variants = [] var defaultVariant = "" # by default, don't ship builds to servo. i'd guess this can be overriden by passing --builders servo var nixArgs = ["--builders", ""] var doReboot = false var doRebootForce = false var dryRun = false var wireguard = "opportunistic" var storePath = null proc addHost (; host) { if (host === "all" ) { # order matters: call hosts->extend(["moby", "lappy", "desko", "servo"]) } else { call hosts->append(host) } } proc addVariant (; v) { if (v === "all") { call variants->extend(["-min", "-light", "", "-next-min", "-next-light", "-next"]) } elif (v === "") { # "full" variant call variants->append("") } else { call variants->append("-$v") } } proc parseArgs(;cur=null, next=null, ...rest) { if (cur === null) { # end : } else { case (cur) { --build | --copy | --switch | --test { setglobal action = cur[2:] parseArgs (next, ...rest) } --deriv { setglobal storePath = next parseArgs (...rest) } --dry-run { setglobal dryRun = true parseArgs (next, ...rest) } --help { usage parseArgs (next, ...rest) } --ip { setglobal ip = next parseArgs (...rest) } --pre { setglobal action = "copy" setglobal defaultVariant = "all" setglobal defaultHost = "all" parseArgs (next, ...rest) } --reboot { setglobal doReboot = true parseArgs (next, ...rest) } --reboot-force | --force-reboot { setglobal doReboot = true setglobal doRebootForce = true parseArgs (next, ...rest) } --variant { addVariant (next) parseArgs (...rest) } --wireguard { setglobal wireguard = next parseArgs (...rest) } all | crappy | desko | lappy | moby | servo { addHost (cur) parseArgs (next, ...rest) } (else) { call nixArgs->append(cur) parseArgs (next, ...rest) } } } if (hosts === [] and defaultHost) { addHost (defaultHost) } if (variants === []) { addVariant (defaultVariant) } } proc destructive (...cmd) { if (dryRun) { echo "dry-run:" @cmd } else { if (get(ENV, "ECHO_CMD")) { echo @cmd } @cmd } } # return "$1" or "$1-hn", based on if wireguard was requested or not func resolveHost (host) { if (ip) { return (ip) } else { case ("$wireguard-$host") { opportunistic-moby { return ("$host-hn") } opportunistic-* { return (host) } never-* { return (host) } always-* { return ("$host-hn") } (else) { return ("$host-hn") } } } } # 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 func timeoutFor (variant) { case (variant) { -min | -light | -next { return (3600) } -next-min | -next-light { return (1800) } (else) { # this catches the normal variant return (14400) } } } proc runOnTarget (host, ...cmd) { # 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 (host !== "" and host !== SELF) { info "running on remote ($host):" @cmd ssh "$host" @cmd } else { info "running locally ($SELF):" @cmd @cmd } } proc deployOneHost (; host, variant) { var timeout = timeoutFor(variant) # storePath is allowed to be either a realized derivation, # or the path to a .drv file itself var myStorePath = storePath if (not myStorePath) { # `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..." setvar myStorePath = $(nix eval --raw -f . "hosts.$host$variant.toplevel.drvPath") } if ("$myStorePath" => endsWith(".drv")) { info "building $host$variant ($myStorePath)" setvar myStorePath = $(destructive nix-store --realize "$myStorePath" @nixArgs) if (myStorePath === "") { false } info "built $host$variant -> $myStorePath" } # mimic `nixos-rebuild --target-host`, in effect: # - nix-copy-closure ... # - nix-env --set ... # - switch-to-configuration