Automate hugo

· by Christoph Stoettner · Read in about 4 min · (768 words)

TL;DR

Mid 2018 I switched my blog from Wordpress to Hugo. Main reason was performance and that I can use Asciidoctor to write the posts.

What happened the last 18 months? I stayed with the theme I selected 2018, but I tweaked it a little bit. So I added lunr to implement searching, changed all scripts and fonts from CDN to local (privacy and tracking), updated Bootstrap 3 to 4.

Working with Bootstrap was quite fun, I haven’t done a lot of HTML or CSS the last years, but the grid and css classes from Bootstrap are working without checking each change on all browsers and are responsive.

Daily journal

After reading The Unicorn Project I started writing a daily journal with things I learned or figured out during the day. I tried this several times the last years, but never was happy with the result. This time I just use the same setup as my blog (Hugo, Theme and Asciidoctor), but deploy it to a local Docker container, which is started with podman.

Gitlab CI/CD

Hugo generates static html pages from Asciidoctor or Markdown sources. The first months I just ran the hugo binary on my local machine and did a rsync of the generated html to my blog and the container.

Updating Hugo, or reinstall my Notebook sometimes delayed a blog post. So I decided to automated the process through Gitlab CI/CD.

.gitmodules

I added the theme (which is stored as a seperate git repository) as a submodule (git add submodule …​) to the blog content. .gitmodules can handle complete repository URL, or relative ones. The relative URL are easier to checkout in the pipeline, so I changed the file to

.gitmodules
[submodule "themes/stoeps-theme"]
	path = themes/stoeps-theme
	url = ../stoeps-theme.git (1)
1Edit Url and replace with the relative path (I have the theme in the same git group)

Creating .gitlab-ci.yml

variables:
  GIT_SUBMODULE_STRATEGY: recursive   (1)

stages:                               (2)
  - build
  - deploy

build:
  stage: build
  image: "node:alpine"
  before_script:
    - 'which curl || ( apk update && apk add curl)'  (3)
    - 'which gem || (apk add ruby)'
    - curl -L https://github.com/gohugoio/hugo/releases/download/v0.61.0/hugo_0.61.0_Linux-64bit.tar.gz | tar -xz && mv hugo /usr/local/bin/hugo
    - npm install
    - gem install asciidoctor
  script:
    - npx gulp build                  (4)
  only:                               (5)
    refs:
      - master
    variables:
      - $BUILDHTML
    changes:
      - content/**/*
  artifacts:
    paths:
      - docs                          (6)
    expire_in: 2 hours

deploy:
  image: "debian:buster"
  stage: deploy
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | base64 --decode | tr -d '\r' | ssh-add - > /dev/null  (7)
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -t rsa home26617100.1and1-data.host >> ~/.ssh/known_hosts
  script:
    - rsync -az docs/ p7594620@home26617100.1and1-data.host:/kunden/homepages/13/d26617100/htdocs/stoeps.de --delete
  only:
    refs:
      - master
    variables:
      - $DEPLOYBLOG
    changes:
      - content/**/*
1Clone submodules with the main repository
2Two stages, one builds html, one deploys the code
3Check if curl is included in the container, if not install it
4Run build with Gulp (see below)
5Stage runs only for master-Branch, if variable BUILDHTML is set and changed files are in the content-tree
6Keep directory docs as artifacts for the next stage
7I created a seperate SSH key just for the synchronisation, it is stored as a variable in the Gitlab project

Store SSH-Key as variable

Create Key
ssh-keygen
base64 encode the key and copy to clipboard
base64 -i ~/.ssh/id_gitlab |tr -d '\n' | xclip
2019 12 30 14 09 26
Figure 1. Store as variable
2019 12 30 13 54 41
Figure 2. Gitlab Crontab setting

Syntax is equal to Linux cron, so the setting of this image will run the pipeline at 23:55 UTC every day. Other option could be hourly at 5 minutes past the full hour: 5 * * * *

I added a scheduler which runs hourly and sets the variables BUILDHTML and DEPLYBLOG, so I can check-in code to gitlab multiple times and the posts will only be build on a scheduled basis.

Just set the variables to true.

2019 12 30 13 52 40
Figure 3. Gitlab Scheduler variables

Gulp

The article Continuously Deploy a Hugo Site with GitLab CI added Gulp to minify the built HTML. Cool idea and saves me some megabytes. I added css-minify and build the hugo page with:

npx gulp build
gulpfile.js
var gulp = require("gulp");
var htmlmin = require("gulp-htmlmin");
var cssmin = require("gulp-cssmin");
var shell = require("gulp-shell");

gulp.task("hugo-build", shell.task(["hugo"]));

gulp.task("minify-html", () => {
    return gulp.src(["docs/**/*.html"])
    .pipe(htmlmin({
        collapseWhitespace: true,
        minifyCSS: true,
        minifyJS: true,
        removeComments: true,
        useShortDoctype: true,
    }))
    .pipe(gulp.dest("./docs"));
});

gulp.task('minify-css', () => {
    return gulp.src(["docs/**/*.css"])
    .pipe(cssmin())
    .pipe(gulp.dest("./docs"));
});

gulp.task("build", gulp.series("hugo-build", "minify-html", "minify-css"));