This website is a collection of Markdown files compiled by ikiwiki, an old software for creating wikis. Until recently I was building this site and deploying by-hand; the frailty of this approach bothered me. Considering I already wrote a nix expression to build this site I decided to automate the rest of the process. Here were my requirements:

  • Build the website on every push to the main branch
  • Deploy the website to my home server on every push to the main branch
  • Stabilize creation and last edit times for ikiwiki

As I already host this repository on Github I settled on using Github Actions to handle both building and deployment of the static site.

Github Actions and Nix

Here is my full build.yml file which describes my Action.

name: Deploy Static Website
on:
  push:
    branches:
      - master
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout Repository
      uses: actions/checkout@v4
      with:
        fetch-depth: 0
        lfs: true
    - uses: cachix/install-nix-action@v20
      with:
        nix_path: nixpkgs=channel:nixos-23.05
    - uses: DeterminateSystems/magic-nix-cache-action@v2
    - name: Build Website
      run: |
        nix-build
    - name: Upload to Remote Server
      run: |
        mkdir -p ~/.ssh
        echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
        chmod 600 ~/.ssh/id_rsa
        rsync -acv --delete --chmod=ug=rwX -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" $(readlink result)/ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/srv/http/wesl.ee/

The cachix/install-nix-action will install nix on the Ubuntu runner in a very short amount of time with nixpkgs configured; using DeterminateSystems/magic-nix-cache-action I can leverage the built derivations stored in cachix to make for a very fast build. The next step simply does a nix-build which leaves the results in results.

Finally I use rsync to propagate the built website files to a remote server. This step involves creating a secure connection to the server using SSH so I need to set up the necessary keys for passwordless auth.

To accomplish this I create the ~/.ssh directory if it doesn't already exist using the mkdir -p ~/.ssh command. Inside this directory I make a file called id_rsa and write the value of a key I have already generated and stored as a Github secret. This private key will be used for authentication when connecting to the remote server.

Now that the SSH authentication is set up I can use the rsync command to transfer the built website files to the remote server. The source directory as $(readlink result)/ which points to the location where the nix-build command placed the built files.

To maintain synchronization between the source directory and the remote server the --delete option removes any files on the server that are not present in the source directory. This was a bigger problem when I first migrated but now is probably not needed. I use the --chmod=ug=rwX option to establish the correct file permissions on the transferred files, as otherwise the permissions are copied from the nix store where the site was built. This grants read and write access to the user and group.

The static website deployment to the remote server is now finished. When a push event happens on the master branch of the repository, this GitHub Action will promptly trigger, constructing the website and transferring the revised files to the server.

My nix derivation is a bit interesting. In addition to pulling in the weirdest Perl packages which ikiwiki uses, and even building one which is not in nixpkgs, I need to perform some git fuckery to make correct timestamps for ikiwiki. This is because ikiwiki relies on ctime and mtime of the source files for assigning “Created” and “Last Edited” times for pages. As I am now building this in an Action the source file timestamps reflect the datetime the repository was fetched (so, the current datetime) instead of when the article was actually written or edited.

In order to set the timestamps to the correct creation and edit times I wrote the following script which is run during the buildPhase of the derivation.

# Timestamps for ikiwiki
find src/ -name "*.mdwn" -exec bash -c '
    last_edit_timestamp=$(git log -1 --format=%ct -- "{}")
    created_timestamp=$(git log --reverse --format=%ct -- "{}" | tail -n 1)

    if [[ -n "$last_edit_timestamp" ]]; then
        touch -t "$(date -d @$last_edit_timestamp +"%Y%m%d%H%M.%S")" -c "{}"
    fi
' bash {} \;

Parsing the git log like this required me to include fetch-depth: 0 during the Actions checkout step.

Handling Timezones in a Nix Build

The envrionment during a nix-build is much like that of nix-shell --pure; it does not seem to export TZDIR which made every date in ikiwiki show as UTC. I found it necessary to add tzdata as a build input and to include this preCheck section to export this data.

preCheck = ''
  export TZDIR=${tzdata}/share/zoneinfo
'';

The timezone can then be set by simply exporting TZ=America/New_York or your favorite TZ. In my case I have TZ hardcoded into my ikiwiki.setup file.

To check out the complete derivation see here.