#+TITLE: Creating Self-Hosted Hugo Blog on OpenBSD #+DATE: 2020-06-17T21:03:26-04:00 #+DRAFT: true #+DESCRIPTION: #+TAGS[]: hugo openbsd #+KEYWORDS[]: #+SLUG: #+SUMMARY: When creating this blog, there were a couple of factors I kept in mind while trying to figure out how I was going to set it up. Here's an approximate list: - Simple - Version controlled - Easy to host on OpenBSD - Minimal maintenance - Good integration with Emacs That's how I came up with what I currently use. Let's walk through how it works. * Framework The blog engine is [[https://gohugo.io/][hugo]], a static site generator. I chose this over something dynamic like wordpress for several reasons. First of all, it's very easy to manage, blog posts are just simple files written in one of the markup languages hugo supports. Being statically generated is also a massive advantage in terms of maintenance. With something like wordpress, you have to be careful to keep your site and plugins up to date. Since there's no dynamic content generation or database involved with hugo, the attack surface is dramatically decreased. No possibility for SQL injection, PHP RATs, accidental shell access, or hacked credentials. The entire site is generated using a single command after a new post is created, and then moved to the web server's root directory. Being all flat files also means the entire thing can very easily be tracked using =git= (or maybe [[https://gameoftrees.org/][got]], eventually), in fact that's the recommended way to use hugo. There's no fear I'll accidentally delete something, as I can always go back to a previous commit and restore it. Since hugo is written in go, it's trivial to compile on OpenBSD, and is actually available directly from the OpenBSD package manager right out of the gate. Maybe the most important thing to be however, is that hugo natively supports org-mode markup. I write all my notes, both personal and work related, in org-mode. It makes converting my notes into blog posts really easy. It also lets me leverage my existing Emacs setup, which comes in handy often. While it's not very well documented, since org-mode markup is a bit of a second class citizen in the hugo world, it's pretty easy to figure out. * Prerequisites This can be hosted on a very cheap VPS since it only has to serve static pages. For OpenBSD hosting I would recommend either [[https://www.vultr.com/][Vultr]] or [[https://openbsd.amsterdam/][OpenBSD Amsterdam]]. ** Server The only thing that's required on the host server is =git=, although you could even get away without that if you chose to host your git repository elsewhere, like on github. #+BEGIN_SRC shell pkg_add git #+END_SRC ** Local machine On the local machine you'll need both =git= and =rsync=. Both might already be installed depending on the system you're on. If not, check your package manager for details on how to install them. In the case of Ubuntu or Debian it would be #+BEGIN_SRC shell sudo apt install git rsync #+END_SRC or for Fedora #+BEGIN_SRC shell sudo dnf install git rsync #+END_SRC * Version Control I wanted to try to keep things as simple as possible for this. The "origin" for the blog is simply a bare git repository in the =blog= user's home directory. ** Setting up the blog user First I set up the blog user #+BEGIN_SRC shell useradd -m blog #+END_SRC I then placed my public SSH key in its =authorized_keys= file #+BEGIN_SRC shell mkdir -m 700 /home/blog/.ssh cp /root/.ssh/authorized_keys /home/blog/.ssh/ chown -R blog:blog /home/blog #+END_SRC I then logged in as the blog user and initialize the bare git repository. #+BEGIN_SRC shell su blog cd # cd with no arguments goes to home directory git init --bare blog.git #+END_SRC ** Cloning the repository Cloning the repository onto my local machine is very easy at this point. As long as my private keys are in the =blog= user's =authorized_keys=, git will take care of the rest. #+BEGIN_SRC shell # on my local machine git clone blog@lambda.cx:blog.git #+END_SRC I can now work on the blog as I would any other git repository, pulling with =git pull= and pushing with =git push=. * Web Server Since this blog is going to be hosted on OpenBSD, we don't need to install a web server, as it already comes with one built in. Setting up =httpd= couldn't be easier, the configuration syntax is very straight forward. If you would like to see a full breakdown of the options available, check out the man page with =man httpd.conf=. The example configuration in =/etc/examples/httpd.conf= is also a good starting point. In this case the simplest configuration would be as follows # js chosen for prism.js syntax highlighting #+BEGIN_SRC js server "blog.lambda.cx" { listen on * port 80 root "/htdocs/blog.lambda.cx" } #+END_SRC Despite how it looks, the =htdocs= folder doesn't reside in the system root (=/=) directory. It actually lives in =/var/www/htdocs=, and only appears that way because =httpd= gets automatically =chroot='ed in =/var/www/= for security reasons. For more information about how to set up SSL with Let's Encrypt, check out [[{{< ref "post/letsencrypt-on-openbsd" >}}][this post]]. * Using Hugo There's not much to say here. Hugo's [[https://gohugo.io/][website]] has good documentation on how to get started creating a blog. I'll be covering the intricacies of using org-mode with hugo in the future. * Deployment The system used to deploy this blog is incredibly simple, involving only =rsync=, =hugo=, and a small shell script. ** Server First you have to allow the =blog= user to write to the website root. We'll do this by making it the owner of =/var/www/htdocs/blog.lambda.cx=. #+BEGIN_SRC shell chown -R blog /var/www/htdocs/blog.lambda.cx #+END_SRC ** Local machine This is the script used to deploy the website. It's placed in the root of the hugo git repository. #+BEGIN_SRC shell #!/bin/sh set -e cd '$(dirname "$0")' hugo rsync -va --progress --rsync-path="/usr/bin/openrsync" public/ blog@lambda.cx:/var/www/htdocs/lambda.cx/blog #+END_SRC Going through it line by line: - ~set -e~ Tells the shell to exit if any commands fail instead of continuing execution. If ~hugo~ has a problem and exits with an error, don't sync with the server. - ~cd '$(dirname "$0")'~ Changes to the script's directory. This is used in case you're running it from somewhere else. - ~hugo~ Compile the website into static files located in the =public= directory. - ~rsync -va --progress --rsync-path="/usr/bin/openrsync" public/ blog@lambda.cx:/var/www/htdocs/lambda.cx/blog~ This one is bigger so I'll break it down. - =rsync= A command that synchronizes files between two directories - =-v= Be verbose, this is optional but I like it - =-a= Stands for "archive": copy recursively, keep permissions, etc. See the =rsync= man page if you're curious. - =--progress= Show progress, also optional - ~--rsync-path="/usr/bin/openrsync"~ This line is very important for OpenBSD servers. OpenBSD has its own =rsync= implementation called =openrsync=. Without this argument, =rsync= will connect to the server, see that the =rsync= command doesn't exist, and fail. - =public/= Specify which folder we want to sync. The trailing =/= is important. Without it =rsync= will copy the folder instead of the folder's contents, which is what we want. - =blog@lambda.cx:/var/www/htdocs/lambda.cx/blog= Login to user =blog= on server =lambda.cx=, syncing files with the =/var/www/htdocs/lambda.cx/blog= directory. The reason to use =rsync= here instead of something like =scp= is that =rsync= won't upload files it doesn't need to, so if 3/4 of the files didn't change when you updated the blog, it won't waste time re-uploading them.