Skip to content

haskell

1 post with the tag “haskell”

Build a blog with Nix

Vision

I have been learning nix for a while. Overall, I am really happy with Nix ecosystem. With Nix, home-manager, cachix, you have setup a reliable and efficient development environment and CI. For demonstration, I want to show you how I use nix to publish my blog:

  • compile a Hakyll program
  • generate the static site using the Hayll program
  • Upload the site to Firebase

If you are new to Nix, I hope you can find something useful in this blog.

Why Nix ecosystem

Hopefully, at this point you are already sold on nix. If not, maybe checkout build with nix or nix.dev. For me, the biggest selling point of is: you can use a declarative language to set up a reproducible environment for your local development and CI.

Why Hakyll

This is probably a tough sell if you don’t care about Haskell. Nowadays, you use tools like Hugo, WordPress, or Jekyll to effortless setup a static site. I love Haskell, and Hakyll gives me an opportunity to use Haskell.

Why Firebase

There are lots of decent alternative to Firebase for hosting a static site. Just to list few

  • GitHub page
  • aws s3
  • netlify seems really nice. probably worth trying out
  • Google cloud storage can host a static site, but currently it does not support SSL.

Required Tools

  • nix (either single user or multi-user installation would work)
  • direnv or nix-direnv recommend using home-manager to get it)
  • lorri (optional, only if direnv by itself doesn’t work well enough for you)
  • cachix (optional)

Consider using nix project template

Both templates are very opinionated. getting-started-nix-template is a more generic nix template. hakyll-nix-template is Hakyll specified, it has sitemap, tag support.

Terminal window
.
├── content
| ├── about.org
| ├── contact.org
| ├── css
| ├── images
| ├── index.html
| ├── posts
| └── templates
├── default.nix
├── nix
| ├── default.nix
| ├── sources.json
| └── sources.nix
├── shell.nix
└── src

scaffold hakyll project using hakyll-init

Hakyll comes with a command line too haskyll-init to scaffold a Hakyll project. Since we only need haskell-init once, normal nix workflow would be

Terminal window
nix-shell -p pkgs.haskellPackages.hakyll

but depend on which nix channel is your default channel, you might get an error about pkgs.haskellPackages.hakyll is broken. We could use -I flag to pin our one-time shell package to stable version. The pattern is -I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/{rev}.tar.gz~

Terminal window
~nix-shell -p pkgs.haskellPackages.hakyll -I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/14006b724f3d1f25ecf38238ee723d38b0c2f4ce.tar.gz~
Terminal window
hakyll-init . #scaffold at current directory
hakyll-init content #scaffold at ./content directory

The project is just a plain cabal project. We can edit project or executable name. We can use normal Nix Haskell development workflow.

Nix Haskell Workflow

Currently, there are two major Nix Haskell workflows: iohk-haskell.nix and your traditional nixpgs. People usually recommend iohk one for complex project, I haven’t used it at all.

Terminal window
nix-shell -p cabal2nix # unless you already installed cabal2nix globally
cabal2nix . > blog.nix
{ project ? import ./nix {}
}:
let
haskellPackages = project.pkgs.haskellPackages;
packages = haskellPackages.callCabal2nix "blog" ./blog.cabal {};
in
haskellPackages.shellFor {
withHoogle = true;
packages = p: [ packages ];
buildInputs = builtins.attrValues project.devTools;
shellHook = ''
${project.ci.pre-commit-check.shellHook}
'';
}

https://discourse.nixos.org/t/nix-haskell-development-2020/6170 hoogle server --local -p 3000 -n

How to find certain (Haskell) package’s version

Terminal window
nix repl
nix-repl> sources = import ./nix/sources.nix
nix-repl> pkgs = import sources.nixpkgs {}
nix-repl> pkgs.haskellPackages.hakyll.version
"4.13.0.1"
nix-repl> :q

How to customize Hakyll

This is probably beyond the scope of this blog, Robert Pearce has an on-going serial on the topic. https://robertwpearce.com/hakyll-pt-1-setup-and-initial-customization.html

Here is a list of Hakyll projects I often check

Hakyll website has a more comphersive list

GitHub Action

Build Step

Most of the YAML configuration is copied from getting-started-nix-template. My default.nix only build the Hakyll program, it doesn’t generate the site. So I added result/bin/site build in run command. (site is the name of my Hakyll executable). We need pass generated site directory as artifacts between build steps

- name: Archive Production Artifact
uses: actions/upload-artifact@master
with:
name: dist
path: dist

dist is the directory name for the generated site, by default Hakyll uses _site.

Publish to Firebase

I use w9jds firebase action to publish the generated static site directory to Firebase. There are publish actions for netlify and Github Page. Of course, we have to store our Firebase token as encrypted secret and pass them as environment variables into the build step.

Enable cachix cache (Optional)

Current version of GitHub Action YAML

name: CI
on:
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: cachix/install-nix-action@v12
- uses: cachix/cachix-action@v8
with:
name: yuanw-blog
signingKey: ${{ secrets.CACHIX_SIGNING_KEY }}
- name: Nix build
run: |
nix-build
result/bin/site build
- name: Archive Production Artifact
uses: actions/upload-artifact@master
with:
name: dist
path: dist
deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v2.3.2
- name: Download Artifact
uses: actions/download-artifact@master
with:
name: dist
path: dist
- name: Deploy to Firebase
uses: w9jds/firebase-action@v1.5.0
with:
args: deploy --message '${{github.event.head_commit.message}}' --only hosting
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
PROJECT_ID: ${{secrets.FIREBASE_PROJECT_ID}}

Result

Right now, the whole CI steps averagely takes 4 min to run. I am pretty happy with the setup.

References

About Nix in general

Nix Haskell development

Hakyll

Github Action