Deploying Hakyll Blog with Nix and Github Actions
1 Motivation
My blog was originally running on hugo with nginx on a digitalocean server. But, it's a static site! I shouldn't need to pay to host it on a server when I could just be deploying to github pages for free.
I recently tried to pick up haskell, and wanted to configure my blog in haskell. This can be done through hakyll. I get the same benefits that hugo provides (e.g. static site generation from markdown files, except with pandoc) ; the configuration part is a bit easier and more powerful since it's in haskell instead of a yaml file.
My vision was to have hakyll be able to convert my markdown, css/html templates, and haskell config file into a static website. I also didn't want to do this every time I had a modification. The workflow I envisioned went like:
- push modified markdown, css/html template, haskell config to github
- github would grab
nixnixon a build server and use nix flakes to convert to a static website. - that static website would be pushed to a separate branch of the repo that is deployed to github pages and link with my CNAME (justin.restivo.me)
2 Building as a flake
The first thing to do was figure out how to generate the blog using
nixFlake
nixFlake
. Luckily, this guy had already done this in a readable manner. However, he was hosting on a nginx instance, which I didn't want, so I cut some of what he did. The barebones is just in my
flake.nix
flake.nix
file.
This flake builds hakyll, then the static site (
builder
builder
) then copies the generated site (plus
CNAME
CNAME
) to
$out
$out
. I was then able to get
nix
nix
to deploy using the process described here and here. Note that I didn't realize this template existed until after I had put together this tooling. The template's nix tooling is quite nice.
The CI steps are:
- Use the checkout action to clone the repo
- Use the
cachix/install-nix-actioncachix/install-nix-actionto install nix flakes. - Run the
nix build . -o sitenix build . -o siteto build the flake and put the result in thesitesitesubdirectory. - Use crazy-max deploy to github pages action. This requires a
GITHUB_TOKENGITHUB_TOKENwhich is already autogenerated by github. It looks at thebuild_dirbuild_dirfield to figure out what to deploy, then pushes that folder to thetarget_branchtarget_branchfield (gh-pagesgh-pages). This is then read in by github pages and deployed to the website insite/CNAMEsite/CNAME(which was generated with theflake.nixflake.nix).
3 Configuration
Generating the blog content is done by first
nix develop
nix develop
-ing, then running
hakyll-init
hakyll-init
in the root directory; this generates the template to modify. Then building is as easy as:
nix build .
nix build .
The website workflow is easy to control from the
Main.hs
Main.hs
file, and the template's css and html is easy enough to modify as well.
As with all static website generators, live refresh upon modification of css is a must for rapid prototyping. Hakyll allows this via:
nix develop
site --watch
nix develop
site --watch
4 Google Analytics
I'm also interested in data, and one way to get data is to hook the site into google analytics. Google gives your site a script to put in the
HEAD
HEAD
of each page, and it will provide you with analytics based on the requests to the page. I've done this and it's pretty nifty (though I'm the only user). Hakyll makes this easy, since all I need do is modify the template used to include the analytics script and boom it's on all webpages.
5 Adding style
To make the site look a little less outdated, I'm using
nerdfonts
nerdfonts
. This was as easy as including a line in the
HEAD
HEAD
section and a little bit of
css
css
per nerdfont README.
6 Adding the Resume
So, my resume is built with latex. I would open source (and perhaps will) the repo I'm using for it. However, there's personally identifying information like my mail address and phone number in the git history, and I'm too lazy to scrub that.
In any case: my resume is in latex and built with xelatex and latexmk. I recently turned it into a flake by modifying the default template to look like:
{
description = "Justin Restivo Resume";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-20.09";
outputs = { self, nixpkgs }: {
defaultPackage.x86_64-linux =
with import nixpkgs { system = "x86_64-linux"; };
stdenv.mkDerivation {
name = "justinrestivo_resume";
src = self;
buildPhase = "latexmk -pdf";
buildInputs = with nixpkgs;
[
(texlive.combine {
inherit (texlive) scheme-medium lipsum fmtcount datetime;
})
];
installPhase = "mkdir -p $out/; mv *.pdf $out/";
};
};
}
{
description = "Justin Restivo Resume";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-20.09";
outputs = { self, nixpkgs }: {
defaultPackage.x86_64-linux =
with import nixpkgs { system = "x86_64-linux"; };
stdenv.mkDerivation {
name = "justinrestivo_resume";
src = self;
buildPhase = "latexmk -pdf";
buildInputs = with nixpkgs;
[
(texlive.combine {
inherit (texlive) scheme-medium lipsum fmtcount datetime;
})
];
installPhase = "mkdir -p $out/; mv *.pdf $out/";
};
};
}
This is a very simple flake that basically defines the latex dependencies and latexmk build command.
Now, if I want to build this into my website flake, I can list the resume repo as an input to the flake:
inputs.justinrestivo_resume = {
url = "git+ssh://git@github.com/DieracDelta/resume";
flake = true;
};
inputs.justinrestivo_resume = {
url = "git+ssh://git@github.com/DieracDelta/resume";
flake = true;
};
Then, in the build phase of
justinrestivo-me
justinrestivo-me
, I can just copy the pdf output of my blog's flake into the output site directory and have it be accessible (and linked to by the blog):
cp ${justinrestivo_resume}/justin_restivo_resume.pdf $out
cp ${justinrestivo_resume}/justin_restivo_resume.pdf $out
In theory this is great, but I still needed it to play nicely with CI. This all built fine locally, but did not with github actions. It couldn't grab the flake since github CI didn't have permissions to clone the private repo that contained my resume flake. To get around this, I had to add in another github action that started ssh-agent with a custom private ssh key. The relevant piece of the yaml is:
- uses: webfactory/ssh-agent@v0.4.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- uses: webfactory/ssh-agent@v0.4.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
So first I had to generate a private ssh key, add it to my githubs, and then as a repo secret with the label
secrets.SSH_PRIVATE_KEY
secrets.SSH_PRIVATE_KEY
.
Still, after I had ssh-agent start like that, everything worked smoothly. Now, nix builds my resume as a dependency, then builds my website with the resulting resume pdf included.