Blog deployment via git-receive hooks
While quickly going through the options for hosting this blog I decided to just do it myself.. Serving some static content (for a small audience) isn’t that hard and I’m running a few (mostly idle) machines anyway.
My requirements were just to have IPv6, some way to tune the webserver config and some kind of “CI” that converts the website from markdown to HTML (preferably with Nix).
Actually pushing the content to a server is trivial if you already have SSH
access. So I could use either rsync or SSH to do it. Since the blog is already
in a git repo I wanted to treat the target server as a git remote. Git has a
bunch of built-in hooks
that can be used for many things. In this case I was interested in the
post-receive
hook.
Pushing the content (in it’s raw format) to a server is already trivial if you have SSH access. I would just commit and push the latest version to another remote.
For building the static assets I could be using git post-receive
hooks that
just call nix-build … -o $documentRoot
.
And that is what I did.
All that was required to make this happen was adding a new file to the NixOS configuration of the server this is running on:
{ config, pkgs, ... }:
let
home = "/var/lib/blog";
env = pkgs.buildEnv {
name = "post-receive-env";
paths = [
pkgs.git
pkgs.nix
pkgs.coreutils
pkgs.gnutar
pkgs.xz
];
};
post-receive = pkgs.writeShellScript "post-receive" ''
export PATH=${env}/bin
set -ex
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
if [ -z "$GIT_DIR" ]; then
echo >&2 "fatal: post-receive: GIT_DIR not set"
exit 1
fi
TMPDIR=$(mktemp -d)
function cleanup() {
rm -rf "$TMPDIR"
}
trap cleanup EXIT
git clone "$GIT_DIR" "$TMPDIR"
unset GIT_DIR
cd "$TMPDIR"
exec nix-build "$TMPDIR" -A build -o ${home}/current
'';
in
{
config = {
services.nginx.virtualHosts."andreas.rammhold.de" = {
enableACME = true;
forceSSL = true;
root = "/var/lib/blog/current";
};
users.users.blog = {
inherit home;
createHome = true;
isNormalUser = true;
openssh = config.users.users.andi.openssh;
};
systemd.services."blog-prepare-git-repo" = {
wantedBy = [ "multi-user.target" ];
path = [
pkgs.git
];
script = ''
set -ex
cd ${home}
chmod +rX ${home} # no secrets in that home dir, everyone can read my public blog
test -e repo || git init --bare repo
ln -nsf ${post-receive} repo/hooks/post-receive
'';
serviceConfig = {
Kind = "one-shot";
User = "blog";
};
};
};
}