#!/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 "- --action copy|switch|test (default: 'switch')" echo "- --variant light|min|''|all (default: '')" echo "- --pre: alias for --action copy --variant all all" echo "" echo "common idioms:" echo "- deploy all: deploy all hosts, sequentially" echo "- deploy --pre: build and copy all hosts" exho "- deploy desko lappy: build and deploy just those hosts" echo "- deploy: deploy the local host" exit 1 } info() { echo "[deploy]" "$@" } action=switch hosts=() defaultHost="$SELF" variants=() defaultVariant= nixArgs=() 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 (--action) action=$1 shift ;; (--help) usage ;; (--variant) addVariant "$1" shift ;; (all|crappy|desko|lappy|moby|servo) addHost "$arg" ;; (--pre) action=copy defaultVariant=all defaultHost=all ;; (*) nixArgs+=("$arg") ;; esac done if [ "${#hosts[@]}" -eq 0 ] && [ -n "$defaultHost" ]; then addHost "$defaultHost" fi if [ "${#variants[@]}" -eq 0 ]; then addVariant "$defaultVariant" fi } runOnTarget() { # 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:" "$@" ssh "$host" "$@" else info "running locally:" "$@" "$@" fi } # deployOneHost $host $variant deployOneHost() { local host="$1" local variant="$2" info "building $host$variant ..." nix-build -A "hosts.$host$variant.toplevel" --out-link "./build/result-$host$variant" "${nixArgs[@]}" || return 1 storePath="$(readlink ./build/result-$host$variant)" info "build $host$variant -> $storePath" # mimic `nixos-rebuild --target-host`, in effect: # - nix-copy-closure ... # - nix-env --set ... # - switch-to-configuration # 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) if [ -n "$host" ] && [ "$host" != "$SELF" ]; then if [ -e /run/secrets/nix_signing_key ]; then info "signing store paths ..." sudo nix store sign -r -k /run/secrets/nix_signing_key "$storePath" 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. nix copy -vv --option builders-use-substitutes false --to "ssh-ng://$host" "$storePath" || return 1 fi if [ -n "$action" ] && [ "$action" != "copy" ]; then info "activating profile... " runOnTarget sudo nix-env -p /nix/var/nix/profiles/system --set "$storePath" || return 1 runOnTarget sudo "$storePath/bin/switch-to-configuration" "$action" || return 1 fi } parseArgs "$@" failedDeploys=() for h in "${hosts[@]}"; do for v in "${variants[@]}"; do deployOneHost "$h" "$v" || \ failedDeploys+=("$h$v") done done if [ "${#failedDeploys[@]}" -ne 0 ]; then echo "FAILED DEPLOYMENT:" for d in "${failedDeploys[@]}"; do echo "- $d" done exit 1 else echo "SUCCESS" fi