MDWiki

How a !webdev built a personal website

Note: This is a quickly written and poorly structured post, however there’s still some information that may help you get a website up-and running with Hugo without having to spend as much time as me.

Background

For some years I’ve been running my blog on blogger.com owned and operated by Google.

Feeling bad about being part of tracking everyone have been looking for something to spin up behind an NGINX or Apache web server as that is fast and easy to do in a (relatively) secure way. Also wanted the site to be static for the same reason.

Played with MDWiki which is nice, but wanted something a bit more modern/fancy looking. Read about Hugo somewhere, but the first theme I tried required Node (WTF). Mistaking that for a requirement for Hugo i started playing with other stuff, only later brain said “That can’t be true Ya ol' fool, look again!” Played with the Ananke Theme, but then stumbled across the Hero Theme which looked kinda neat to me (Beauty is in the eye of the beholder).

Glossing over the multiple stupid mistakes made during the “build” of this website, here’s the basic steps.

1) Followed the Hugo Quick Start

2) Installed the Hero Theme instead of the Ananke theme used in the quickstart

3) Converted blogger to Hugo with BloggerToHugo ¹

4) Spun up a Debian 11 Server on DigitalOcean ²

5) Setup DNS A and AAAA RRs pointing to www and blog.infosecworrier.dk

6) Installed NGINX

7) Installed Lets Encrypts Certbot

8) Requested certificates:

1
 certbot run -n --agree-tos --nginx -m webmaster@infosecworrier.dk --domains www.infosecworrier.dk --domains blog.infosecworrier.dk --domains infosecworrier.dk

9) Uploaded the public directory of the local site to the servers /var/www/ directory³ with rsync. More on how that was built later.

Bam, the web site is now runnning, but read below for some of the learnings i had.

10) Installed Crowdsec, massive participative IPS for extra protection.

11) Added redirects to NGINX configuration to avoid 404 errors on changed blog post directories.

DigitalOcean Referral Badge

¹ This required a decent amount of manual adjustment to get the blogs in the location and to have the look I wanted.

² The link to DigitalOcean above is a referral link: “Everyone you refer gets $200 in credit over 60 days. Once they’ve spent $25 with us, you’ll get $25.

³ This directory likely should be linked to a location with the space required if your website grows big size-wise.


Building the site

I wanted a real simple site, so only kept parts of the example site and added blog pages (see below), then created a favicon as well as logos for computer and mobile with Inkscape (admittedly they need to be redone). All three are under static/ - use the same pixelsettings as the originals and they will work fine on all devices.

During the fail fast phase(s) of the development of the site, the following command was used a lot:

HUGO Server on localhost ⁴
1
 hugo server --disableFastRender --buildDrafts

This allowed me to break everything and immediately see (and sometimes understand) what stupid mistakes I’d made, by browsing to http://localhost:1313/

⁴ --buildDrafts mainly for having a look at the blog before publishing the site.


The blog part of the site

I liked the general look of the services part of the example site, so used that as a template for the blog part, copying everything in layout/services to layout/blog and content/services to content/blog then changed them to my liking.

The same is true for the Useful Stuff part of the site btw, however the icons used there are the ones provided with the example site, whereas (somewhat) relevant pictures for the individual blogpost.


Date published and wordcount

I wanted to show date published and word count at the top of every blog post, so added the following to single.html (layouts/blog)

Published date and wordcount
1
2
3
4
<div>
            <font size="1"> Published:  {{ .Date.Format "2006-01-02" }},
            {{ .WordCount }} Words </font> 
          </div>

So now the “main” section of that file now look like this. I chose a small font for that.

single.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{ define "main" }}
{{ partial "hero-image-setheight.html" (dict "background" .Params.heroBackground "heading" .Params.heroHeading "subheading" .Params.heroSubHeading "section" .Section "content" .)}}
<div class="container pt-4 pt-md-10 pb-4 pb-md-10">
  <div class="row justify-content-start">
    <div class="col-12 col-md-8">
          <div>
            <font size="1"> Published:  {{ .Date.Format "2006-01-02" }},
            {{ .WordCount }} Words </font> 
          </div>
          <div class="service service-single">
        <div class="content">{{.Content}}</div>
      </div>
    </div>
  </div>
</div>
{{ end }}

An example blogpost

The frontmatter⁵ for this blogpost, as shown below, describe metadata of the post itself, including if it is a draft. The critical metadata below settings below are: a) date is the date that will be shown at the top of the post, b) url is used to locate the posts in a year/month structure within /blog/. c) draft when true hugo will not add the post (unless using --buildDrafts) this post had this state from the day I started writing it and until it was published.

The rest should be self-explanatory, but there’s good examples to be found on web.

Front Matter for this blogpost
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
---
title: 'Personal Website with Hugo'
date: 2023-01-07T16:05:00.001+02:00
draft: true
url: /blog/2023/01/Personal-website-hugo.html
tags: 
- Website
- Hugo
- NGINX
- Hero Theme
- Static
- Digital Ocean
icon: 'images/hugo-logo-wide.svg'
featured: true
heroHeading: 'Blog Posts'
heroSubHeading: 'Building a personal website with Hugo'
heroBackground: 'images/hugo-logo-wide.svg'
---

⁵ Front matter allows you to keep metadata attached to an instance of a content type, i.e., embedded inside a content file, and is one of the many features that gives Hugo its strength.


Shortcodes

Found out about shortcodes when needing to (re)add Youtube videos to two old blog posts, so created a file called youtube.html with the following content under layout/shortcodes/

youtube.html
1
2
3
4
5
6
7
8
9
{{ $id := .Get "id" | default (.Get 0) }}
{{ $start := .Get "start" | default 0 }}
{{ $title := .Get "title" | default "YouTube Video" }}

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube-nocookie.com/embed/{{ $id }}?rel=0&start={{ $start }}"
          style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;"
          allowfullscreen="" title="{{ $title }}"></iframe>
</div>

Then inserting a Youtube video is as easy as adding this with the id of the video of choice (example below).

shortcode to use
1
 { {<youtube id="-_IlNbsILLE"> } }

Adding the verification rel=“me” for mastodon to the site

Of course we all want to be verified by adding it to the footer.html file under layouts/partials. Below is a snippet of that file with the modification in place,

youtube.html
1
2
3
4
5
6
<ul class="footer-menu">
            <li><a href="/">Home</a></li>
            <li><a href="/contact">Contact</a></li>
            <a rel="me" href="https://infosec.exchange/@itisiboller"></a>
            <li class="copyright">© 2023 infosecworrier.dk</li>
          </ul>

RSYNC’ing the site

Now it is just a matter of copying the contents of the yoursite/public directory to the webserver.

To make sure everything is exactly as the source, use rsync with --delete:

1
 rsync -e 'ssh -p 12222' -aAXv ./infosecworrier.dk/public/ user@www.infosecworrier.dk:/var/www/ --delete

Crowdsec to help protect the server

To add an extra layer of protection, crowdsec was used.

Crowdsec Logo

Installation required the following steps:

1a) curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | tee -a crowdsec.sh

1b) remember to verify that the script looks “right”

2) chmod 755 crowdsec.sh

3) ./crowdsec.sh

4) apt install crowdsec

5) apt install crowdsec-firewall-bouncer-nftables

6) cscli collections install crowdsecurity/nginx

7) cscli collections install crowdsecurity/base-http-scenarios

8) cscli collections install crowdsecurity/nginx-log-parser

9) cscli collections install crowdsecurity/nginx-logs

10) cscli collections install crowdsecurity/linux

11) cscli collections install crowdsecurity/http-cve

NGINX 301 Redirects

Blogger.com creates a /year/month directory structure for all blog posts. For the migration the choice was to do the same, however locate that in the /blog/ directory, creating this structure /blog/year/month. To ensure that old links still worked and didn’t end up with the dreaded 404 - Page not found, simple return statements were added to the sites configuration file (/etc/nginx/sites-available/default). Below is a simplified example using a single year:

301 redirect snippet
1
2
3
    location ~ ^/2018/(.*) {
        return 301 $scheme://$host/blog/2018/$1;
    }