= DIY bare bones CI server with Bash and Nix
:categories: ci
:sort: 2

:post-receive: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
:example-project: https://euandreh.xyz/remembering/ci.html

With a server with Nix installed (no need for NixOS), you can leverage its build
isolation for running CI jobs by adding a {post-receive}[post-receive] Git hook
to the server.

In most of my project I like to keep a `test` attribute which runs the test with
`nix-build -A test`.  This way, a post-receive hook could look like:

[source,sh]
----
#!/usr/bin/env bash
set -Eeuo pipefail
set -x

LOGS_DIR="/data/static/ci-logs/libedn"
mkdir -p "$LOGS_DIR"
LOGFILE="${LOGS_DIR}/$(date -Is)-$(git rev-parse master).log"
exec &> >(tee -a "${LOGFILE}")

unset GIT_DIR
CLONE="$(mktemp -d)"
git clone . "$CLONE"
pushd "$CLONE"

finish() {
  printf "\n\n>>> exit status was %s\n" "$?"
}
trap finish EXIT

nix-build -A test
----

We initially (lines #5 to #8) create a log file, named after _when_ the run is
running and for _which_ commit it is running for.  The `exec` and `tee` combo
allows the output of the script to go both to `stdout` _and_ the log file.  This
makes the logs output show up when you do a `git push`.

Lines #10 to #13 create a fresh clone of the repository and line #20 runs the
test command.

After using a similar post-receive hook for a while, I now even generate a
simple HTML file to make the logs available ({example-project}[example project])
through the browser.

== Upsides

No vendor lock-in, as all you need is a server with Nix installed.

And if you pin the Nixpkgs version you're using, this very simple setup yields
extremely sandboxed runs on a very hermetic environment.

== Downsides

Besides the many missing shiny features of this very simplistic CI, `nix-build`
can be very resource intensive.  Specifically, it consumes too much memory.  So
if it has to download too many things, or the build closure gets too big, the
server might very well run out of memory.
