/blog-rekahsoft-ca

The code, templates and content for my Hakyll powered blog at blog.rekahsoft.ca

Primary LanguageHaskellOtherNOASSERTION

Source Code for #! Lambda Slang

Features

Tools

The creation of this website was made possible by the following open source tools and libraries:

  • Hakyll is used to generate site from static files
  • Clay is used for CSS pre-processing
  • Skeleton is used for CSS boilerplate
  • MathJax is used for rendering mathematics
  • JQuery is used for various DOM manipulations
  • Gnu Guix is used to manage development environments and packaging
  • Inkscape and the Gimp were used to create various images/artwork
  • Gnu Free Fonts, specifically FreeMono is used as main font
  • Gnu Emacs because there is no place like home; and no greater editor!

License

Simply put, you’re welcome to use the code used to generate this site though there are a few restrictions:

  • Any images and artwork that embody the likeness of “#! Lambda Slang” are not to be distributed or used and are strictly copyright
  • The content of pages and posts can be used with attribution, providing you aren’t making money off of it

Various licenses (GPLv3, Creative Commons BY-NC-SA License, and Creative Commons BY-NC-ND License) are deployed dependent on which part of the site is in question. Please see the LICENSE file for full details.

Repository Structure

blog-rekahsoft-ca.cabal
Cabal package definition.
bootstrap.sh
Generate development scripts, Makefile, and setup vendor links based on this literate configurationl
channels.scm
Guix Channel File.
clay/*.hs
Clay source files.
css/*.css
CSS source files (will be minified).
drafts/*.md
Draft posts.
files/images/
Folder for images used in posts.
files/source/
Folder for source code used in blog posts/pages.
fonts/*.{ttf,woff,...}
Font files
guix.scm
Guix package definition for this site-builder and its resulting site.
images/*
Folder for images used on pages or in templates.
images-src/*
Folder for the image source files (should be 1-to-1 with files in images/*).
infra/
Infrastructure folder; contains terraform based Infrastructure As Code (IAC).
infra/channels.scm
Symlink to ../channels.scm (can be independent if needed).
infra/*.tf
Terraform source files.
infra/*.tfvars
Terraform variables files for each environment (non-secrets).
infra/Makefile
Makefile used for terraform deployments.
infra/manifest.scm
Guix manifest that defines the necessary deployment environment.
js/*.js
Javascript files.
lib/*
Javascript libraries.
LICENSE
License file.
pages/*.markdown
Page content.
posts/*.markdown
Blog posts.
README.org
Org-mode documentation.
robots.txt
Robot Exclusion Protocol file.
Setup.hs
Cabal build script for this site.
src/*.hs
Hakyll powered site builder.
templates/
Folder for all template files.
templates/default.html
Entry point template (defines html document used for all pages).
templates/pages/*.html
Html page templates (correspond 1-to-1 with pages/*.markdown files).
templates/partials/*.html
Partial template files, for use within templates.
templates/tag-page.html
Template for creating pages about tags with a specific tag.

Guix Development Environment

Gnu Guix is used to package this project and manage its dependencies, as well as to provide reproducible development environments.

Prerequisites

The only prerequisite for starting a development environment for this project is GNU Guix. Optionally, direnv can be used to enable a non-containerized development environment that is abridged with your existing shell.

Quick Start

First run the bootstrap script, which uses this documentation to generate a Makefile that can be used for development.

./bootstrap.sh

Then run the development ‘auto-watching’ environment:

make

This starts a containerized local development environment that uses ghcid to watch haskell sources and restart hakyll’s site watch feature when changes occur. The site will be available at http://localhost:3000, and will automatically rebuild as site files change (templates, post, pages, etc..).

Start Development Environment

The development environment is defined by the following files:

channels.scm
Specifically defines a set of available software, their versions and their build recipe.
guix.scm
Defines the package for this site, blog-rekahsoft-ca.

To start a development environment, run the following:

guix time-machine -C channels.scm -- shell -CN -E '^LANG$' -E '^TERM$' -f guix.scm -Df guix.scm $@

This uses the guix time-machine feature to ensure the development environment is reproducible by supplying a set of guix channels, effectively pinning all software versions used. The guix shell command is used within the time-machine to start a development environment in a container (-C), which shares the hosts network namespace (-N). The environment variable LANG is passed into the container to ensure locales work as expected; without this, site building will fail! Additionally, the environment variable TERM is passed into the container to ensure the development shell behaves correctly. The option -f guix.scm loads the blog-rekahsoft-ca package, and -Df guix.scm indicates that development dependencies of the blog-rekahsoft-ca package should be included in the environment.

Deployment Environment

Gnu Guix is used, similar to in the previous section, to create environments with all tools necessary for deployments, with a notable difference being a guix.scm file is not provided or needed, as the deployment environment is used solely for its side effects.

infra/channels.scm
Symlink to ../channels.scm to make the guix cli workflow nicer when in the infra directory. Technically this doesn’t need to be a symlink, and could be a different set of channels or version of channels compared to the channels file at the top-level of the repository, however this would complicate Composing Site Development and Deployment Environments, so its preferred that all guix environments for the project, including the development and deployment environment use the same set of Guix channels.
infra/manifest.scm
Defines packages required for deployment of this site.

To start a deployment environment, run the following:

cd infra
guix time-machine -C channels.scm -- shell -CN -E '^LANG$' -E '^TERM$' -E '^AWS.*$'

Composing Site Development and Deployment Environments

guix time-machine -C channels.scm -- shell -CN -E '^LANG$' -E '^TERM$' -E '^AWS.*$' -f guix.scm -Df guix.scm -m infra/manifest.scm $@

Hakyll Site Commands

Build Site

This website is built from a collection of markdown files and templates that are processed by pandoc and are stitched together using Hakyll. To build the html/css/jss and all other assets required to deploy and distribute the site, the hakyll derived site-builder, blog-rekahsoft-ca must be invoked. For convenience, an alias site is provided for the site builder as part of its guix package. Here is it being used to build the site:

site build

Clean Site

Building the site has the side effect of writing a couple files/directories to disk as a result of the build process. In some cases, its useful to start of with a clean slate and remove any files that were generated for the site. To so so, the clean sub-command can be used:

site clean

Watch

During development of new content or adjustments to the site, it is useful to autocompile upon changes to any site files (templates, pages, posts, etc..). This functionality is provided by Hakyll.

site watch

site deploy command

site deploy

Clean up Guix Store

guix gc --list-dead | grep -e '^/gnu/store/.*-blog-rekahsoft-ca-.*' | xargs guix gc -D

Enhanced Watch

When making adjustments to the site builder itself, it is useful to have functionality similar to the site content watching feature of Hakyll, but for haskell source files. Luckily, ghcid can be used for this, and is included in the projects development dependencies, specified in the guix.scm file.

ghcid --test _devWatch

Building a Release

The software built that itself builds this blog is released as a Guix package. It is currently not, and is not ever expected to be distributed via a channel, as it provides little benefit to anyone except myself, and is meant to operate along with stateful data, including the site templates, content, pages, posts, etc..

To build a release, run the following command:

guix time-machine -C channels.scm -- build -f guix.scm

This will produce a guix package with the following three outputs:

out
The blog-rekahsoft-ca site builder (also available as site), and gencss css generator binaries
site
A build of the website made with the site builder, etc.. in the out output of this package, using the content at the same version
static
License file and any other file that should be distributed (eg manual)

Verifying a Release

To manually verify a release, any http webserver can be used to serve the site output of the guix build. For instance, this is how Python’s http.server builtin http server can be used.

guix shell python-wrapper -- python -m http.server -d $(guix time-machine -C channels.scm -- build -f guix.scm | grep -E '^.*-site') 3000

What is done with the release?

Deploying the Site

Terraform is used to deploy this site. Its configuration files are located in ./infra.

Under normal conditions, all deployments occur from my internal ci/cd system. This ensures that the deployment process is reliable, repeatable and quick. However, in the case of both development and emergency deployments, clear documentation surrounding the deployment process is necessary.

Setup a Particular Environment

Three environments (terraform workspaces) are currently available, including:

default
unused default terraform workspace
staging
https://www.blog.staging.rekahsoft.ca
production
https://www.blog.rekahsoft.ca
make setup ENV=<env>

From this point onward, any make target run will operate on the selected environment, unless its switched with the workspace or setup targets, or manually with terraform.

See What Infrastructure Will Change

Run a terraform plan to see how the selected environments infrastructure will change.

make plan

Deploy the Site

Run a terraform apply to deploy to the selected environment.

make deploy

Working with Terraform Directly

Within a development environment, terraform, its providers and all other dependencies are available. As such, its possible to directly leverage terraform and its various operations. This is particularly useful when debugging or adding make targets.

Writing a Blog Post

The most natural way to edit and preview a post is to use direnv along with this repository, which uses guix shell to transparently provide all necessary tools, including Hakyll Site Commands. When using direnv, a containerized environment will not be used, however for content development, this is not a concern.

guix time-machine -C channels.scm -- shell -CN -E LANG -E TERM -f guix.scm

DOING Vendor external libraries using Guix

Some …

[ -h lib/MathJax ] && rm lib/MathJax
[ -e lib/MathJax ] && echo "lib/MathJax exists, but not as a symlink; please manually remove it!" && exit 1
ln -s $(guix time-machine -C channels.scm -- shell -Df guix.scm -- bash -c 'echo $GUIX_ENVIRONMENT')/share/javascript/mathjax lib/MathJax

Makefile

In order to simplify running the various commands outlined throughout this document, a Makefile is defined below.

# THIS IS A GENERATED FILE, DO NOT EDIT!
# Instead modify README.org appropriately

.DEFAULT_GOAL := watch

.PHONY: bootstrap
bootstrap:
	<<bootstrap>>

.PHONY: dev
dev:
	./scripts/start-development-environment.sh

.PHONY: dev-deploy
dev-deploy:
	./scripts/start-deployment-environment.sh

.PHONY: dev-all
dev-all:
	./scripts/start-development-and-deployment-environment.sh

.PHONY: watch-all
watch-all:
	./scripts/watch-all.sh

.PHONY: watch
watch:
	./scripts/start-development-environment.sh -- <<watch-all>>

.PHONY: build
build-release:
	./scripts/build-release.sh

.PHONY: vendor
vendor:
	./scripts/vendor-deps.sh

.PHONY: clean
clean:
	./scripts/clean-guix-store.sh
	rm -rf scripts lib/MathJax Makefile

Continuous Integration & Delivery

Generate .drone.yaml

drone jsonnet --stream --format

Note: currently drone-cli is not packaged for Guix, so for the time being, it can be run with docker as follows, where <version> is the drone-cli version.

docker run -v ${PWD}:/tmp/app -w /tmp/app --rm -it drone/cli:<versin> jsonnet --stream --format

Known Issues

If you have an issue while browsing my blog please let me know via email.