dirk.ndrvn.nl

Auto-updating Hugo blog with Syncthing and systemd

You’re looking at a static site built with Hugo. It is hosted from the Raspberry Pi 3b somewhere in the back of a cupboard in my living room. To update this site, I wanted a solution that

  1. does not require me to SSH into my RPi
  2. does not require me to be connected to the Internet
  3. does not require me to remember to update the site

For now, I think I accomplished this thusly:

Prerequisites:

I gently assume that

  1. you have Syncthing working on the Raspberry Pi and other computer(s)
  2. you have a webserver running on the RPi

Hugo installation

As Hugo is a Go program, installation was fairly simple:

  1. Find the _linux-arm.tar.gz from the latest release at https://github.com/gohugoio/hugo/releases, copy the link, and download the file to the RPi:
    cd ~/hugo;
    wget -O hugo.tgz "https://github.com/gohugoio/hugo/releases/download/v${LATEST_VERSION}/hugo_${LATEST_VERSION}_linux-arm.tar.gz"
    
  2. Untar the archive. Be sure to do it right the first time:
    tar xzf hugo.tgz #eXtract Zhe File
    
  3. Link the binary to somewhere in your $PATH
    cd ~/bin;
    ln -s $HOME/hugo/hugo
    
  4. Verify that it works:
    hugo version # should say something like hugo v$LATEST_VERSION-
    

Create a Hugo site

Basically: follow the Hugo Quick-start

  1. Create a new site:

    cd ~;
    hugo new site dirk.ndrvn.nl
    
  2. Install a shiny theme:

    cd ~/dirk.ndrvn.nl;
    git clone https://github.com/janraasch/hugo-bearblog.git themes/hugo-bearblog;
    echo "theme = 'hugo-bearblog'" >> hugo.toml
    
  3. Create content that people want to read:

    hugo new content tech/hugo-blog-syncthing-systemd.md
    

    (Actually writing the post is left as an exercise for the reader)

  4. I publish the public/ directory with Nginx:

    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name dirk.ndrvn.nl;
    
        ssl_certificate /etc/letsencrypt/live/dirk.ndrvn.nl/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/dirk.ndrvn.nl/privkey.pem;
    
        location / {
            root /home/pi/dirk.ndrvn.nl/public;
            index index.html;
        }
    }
    

    And reload nginx to actually start serving out content:

    nginx -T && nginx -s reload
    

Syncthing the site

Create a new Synchting share. To avoid passing around the built site, I added these things to .stignore:

public
.hugo_build.lock

Do the auto-update

To auto-update the site, I added a systemd unit in /etc/systemd/system/hugo-watch.service to run hugo whenever a file in ~/dirk.ndrvn.nl is changed :

[Unit]
Description=Hugo Watcher for dirk.ndrvn.nl - Rebuild on file change
After=network.target

[Service]
User=pi
WorkingDirectory=/home/pi/dirk.ndrvn.nl
ExecStart=/bin/bash -c "inotifywait -m -e modify,create,delete,move -r --exclude '(^/home/pi/dirk.ndrvn.nl/(public/|build.log|\..*)|\.syncthing\.)' /home/pi/dirk.ndrvn.nl | while read -r FILE; do echo "$FILE"; /home/pi/bin/hugo --panicOnWarning --logLevel debug --verbose | tee /home/pi/dirk.ndrvn.nl/build.log; done"
Restart=always

[Install]
WantedBy=multi-user.target

This does the following things:

  1. as user pi, in the directory /home/pi/dirk.ndrvn.nl
  2. watch for changes in /home/pi/dirk.ndrvn.nl and all subdirectories, excluding public/, build.log, dotfiles, and .syncthing.*, with inotifywait
  3. pipe those events (which are emitted line-based) to a while read loop
  4. for each change, run /home/pi/bin/hugo --panicOnWarning --logLevel debug --verbose | tee /home/pi/dirk.ndrvn.nl/build.log

This of course requires inotifywait to be installed:

sudo apt install inotify-tools

After this, I was able to start the hugo-watch service, and enable it to start at boot:

sudo systemctl start hugo-watch.service;
sudo systemctl enable hugo-watch.service;

Conclusion

With this setup, I’ll be able to edit this website from all computers I have shared the Syncthing folder. After saving hugo-blog-synchting-systemd.md on a computer, it is synchronised to the Raspberry Pi, and a new version of the site is automatically updated.

To verify everything is working, you can tail the systemd journal:

sudo journalctl -f -u hugo-watch

Which yields output similar to this:

Sep 06 14:15:27 pi bash[25826]: /home/pi/dirk.ndrvn.nl/content/tech/ MOVED_FROM hugo-blog-syncthing-systemd.md
Sep 06 14:15:29 pi bash[7808]: Start building sites …
Sep 06 14:15:29 pi bash[7808]: hugo VendorInfo=gohugoio
Sep 06 14:15:29 pi bash[7808]: INFO  static: syncing static files to / duration 7.095755ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step process substep collect files 2 files_total 2 pages_total 2 resources_total 0 duration 24.874519ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step process duration 32.150691ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step assemble duration 9.580869ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step render substep pages site en outputFormat html duration 125.902392ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step render substep pages site en outputFormat rss duration 10.562434ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step render pages 12 content 6 duration 137.051391ms
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step render deferred count 0 duration 8.75µs
Sep 06 14:15:29 pi bash[7808]: INFO  build:  step postProcess duration 116.303µs
Sep 06 14:15:29 pi bash[7808]: INFO  build:  duration 183.931825ms
Sep 06 14:15:29 pi bash[7808]:                    | EN
Sep 06 14:15:29 pi bash[7808]: -------------------+-----
Sep 06 14:15:29 pi bash[7808]:   Pages            | 12
Sep 06 14:15:29 pi bash[7808]:   Paginator pages  |  0
Sep 06 14:15:29 pi bash[7808]:   Non-page files   |  0
Sep 06 14:15:29 pi bash[7808]:   Static files     |  0
Sep 06 14:15:29 pi bash[7808]:   Processed images |  0
Sep 06 14:15:29 pi bash[7808]:   Aliases          |  0
Sep 06 14:15:29 pi bash[7808]:   Cleaned          |  0
Sep 06 14:15:29 pi bash[7808]: Total in 374 ms
Sep 06 14:15:29 pi bash[25826]: /home/pi/dirk.ndrvn.nl/content/tech/ MOVED_TO hugo-blog-syncthing-systemd.md
Sep 06 14:15:29 pi bash[7819]: Start building sites …
Sep 06 14:15:29 pi bash[7819]: hugo VendorInfo=gohugoio
Sep 06 14:15:29 pi bash[7819]: INFO  static: syncing static files to / duration 937.712µs
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step process substep collect files 2 files_total 2 pages_total 2 resources_total 0 duration 2.636156ms
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step process duration 10.975353ms
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step assemble duration 4.143088ms
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step render substep pages site en outputFormat html duration 138.364677ms
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step render substep pages site en outputFormat rss duration 9.479462ms
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step render pages 12 content 6 duration 150.339878ms
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step render deferred count 0 duration 11.563µs
Sep 06 14:15:29 pi bash[7819]: INFO  build:  step postProcess duration 65.052µs
Sep 06 14:15:29 pi bash[7819]: INFO  build:  duration 167.015356ms
Sep 06 14:15:29 pi bash[7819]:                    | EN
Sep 06 14:15:29 pi bash[7819]: -------------------+-----
Sep 06 14:15:29 pi bash[7819]:   Pages            | 12
Sep 06 14:15:29 pi bash[7819]:   Paginator pages  |  0
Sep 06 14:15:29 pi bash[7819]:   Non-page files   |  0
Sep 06 14:15:29 pi bash[7819]:   Static files     |  0
Sep 06 14:15:29 pi bash[7819]:   Processed images |  0
Sep 06 14:15:29 pi bash[7819]:   Aliases          |  0
Sep 06 14:15:29 pi bash[7819]:   Cleaned          |  0
Sep 06 14:15:29 pi bash[7819]: Total in 247 ms

#Tech #Hugo