In this post, I go over the decisions made in writing the engine for this blog, and details over how I chose to host the server.
- The FAGBlog Stack
- Deployment
- Proxies and Cache
- Content Directory Structure
- Rendering
- On writing HTML
- Responsive CSS without a framework
- The table of content
- Conclusion
The vision for this website was to have a simple static blog that has a text based document vibe to it and matches my desktop in terms of theming and colour scheme. I also want the content and configuration to be entirely done by editing documents on the filesystem. This way I wouldn't need to fuck around with databases and can simply put the entire site on version control.
At first, I wanted to write a Markdown to HTML static site generator in rust. But then I thought about it, why
should I translate from one human-readable markup language (Markdown) to yet another markup language that is also
human-readable (HTML). Yeah sure HTML tags are kinda annoying to write but it's really not all that bad.
Instead of writing **bold text**
, is it really that much harder to write
<b>bold text</b>
?
The FAGBlog Stack
The server for this website is written in Go using its default net/html
package which is actually
really feature packed and is more than enough for something simple like this. The frontend is written in vanilla
HTML and CSS with minimal client side scripting. The server is then deployed on a $15 Raspberry Pi (the original
Zero W) under my desk and served behind an nginx reverse proxy, proxied again through Cloudflare to leverage their
caching services.
I will now attempt to walk you through my train of thought (which does multi track drifting so bear with me). The ideal solution for something like this would have been to statically generate the whole site and host it on some free static site host like GitHub Pages. However, since I was going to self host anyway, and since the pages are simple enough that rendering is super fast (<300 µs on my pc, haven't benchmarked on the server), pregenerating didn't seem worth it.
So the decision was made. The site would be server-side rendered. Since I decided to use HTML for writing the articles, rendering is as simple as putting in the article into some pre-defined slot in a template. Initially I did not plan on having anything more complex than that - although, that changed when I decided on a table of content for the blogs, more on that later - so vanilla PHP (not Laravel or anything) and Apache httpd have would unironically been a very good choice (I still think this is true). However, I've been wanting to learn Go for a while and wanted it on my resume, so I chose Go instead (this was actually my first time ever using Go and I really enjoyed it).
Deployment
Since Go can compile to a single static binary, deployment is piss easy. For cross compilation, all I have to do is
set the environment variables GOARCH=arm
and GOARM=6
to have it target the armv6l
architecture on my RPi. Then I just package the binary up along with the templates/
and
static/
directories into an xzipped tarball and scp
it over to the server. I automated this
along with installation in a simple Makefile so that I can just do a make package GOARCH=arm GOARM=6
to
have the package ready, and then a sudo make install GOARCH=arm GOARM=6
on the server to install.
I have a script to automate the deployment as well, but I didn't check it into version control since its kinda janky. Judge for yourself:
#!/bin/bash
TARGET='[redacted]'
make package GOARCH=arm GOARM=6
scp fagblog-linux-armv6.tar.xz $TARGET:~/
ssh $TARGET '
rm -rf ~/fagblog &&
mkdir -p ~/fagblog &&
tar -xf ~/fagblog-linux-armv6.tar.xz -C ~/fagblog &&
cd ~/fagblog &&
sudo make install GOARCH=arm GOARM=6 &&
sudo systemctl restart fagblog
'
If you paid attention to the deployment script you might have noticed a systemd service being restarted. That's because the server runs as a systemd unit for easy reboots and log management. Systemd also allows for granular control of filesystem permissions to set certain directories as readonly and whatnot. Who needs docker anyway?
Proxies and Cache
Instead of caching on the application level on the server, I chose to use Cloudflare's automatic caching services instead. This is because doing it this way takes zero effort (it's enabled by default even), and I'm using Cloudflare for DNS and proxying anyway.
Nginx as a reverse proxy handles passing requests from the publicly exposed HTTP and HTTPS ports to the internal server port of 8000, and also handles SSL encryption using free certificates from Let's Encrypt. I mostly just followed this article for the config.
Content Directory Structure
As you can tell from the name of the engine, both configuration and contents are accessed from the filesystem. The directory structure for the contents of this website is as follows:
/var/lib/fagblog/airkoala.lol/
|- meta.toml
|- assets/
| |- favicon.ico
| ...
-- blog/
|- fagblog-stack/
| |- meta.toml
| |- index.html
...
The root meta.toml
contains metadata for the site itself (author name, bio, links etc), while each
blog/*/meta.toml
is the metadata for the blogpost in question.
# ----- site meta.toml -----
Title = "Airkoala's Blog"
FaviconHref = "/assets/favicon.ico"
Hostname = "airkoala.lol"
[Author]
Name = "Airkoala"
AvatarHref = "/assets/avatar.gif"
Blurb = "..."
[[HeaderLinks]]
Name = "GitHub"
Href = "https://github.com/airkoala"
...
# ----- blog/1-fagblog-stack/meta.toml -----
Title = "..."
ThumbnailHref = "..."
Timestamp = ...
Summary = "..."
You can have a look at the contents in more detail in the blog's repository if you're a fucking nerd.
Rendering
The pages on this site are all rendered server-side using Go'shtml/template
library. There is no caching
mechanism on the server itself and everything is re-rendered on each request, which allows for rapid iteration during
development and writing. Since Cloudflare is doing the caching on the proxy level, I don't have to worry about the
performance cost on production due to this (and also because I'm lazy).
On writing HTML
Writing simple HTML by hand is really not as hard as web devs of today act like. Moreover, there are so many semantic elements in HTML that are just
unused. For example, the notes at the top of this article are marked up using the <aside>
tag.
This makes it super easy to write the CSS for it and then simply use the element rather than fucking around with CSS
classes like so:
<aside>
<p>
<em>Note to potential future employer:…</em>
</p>
</aside>
Some other semantic elements used in this blog are <code>
(only for inline, for blocks I used the
highlight.js library), <article>
and
<datetime>
.
Speaking of datetime, the timestamp for this blog at the top of the page is one of the only client side rendered elements, the other being the code blocks.
Responsive CSS without a framework
All styling for this site is done with handwritten (and vibe coded) vanilla CSS. You don't actually need a responsive framework like tailwind or bootstrap if all you want is a few very simple responsive elements. Some of the content have varying widths depending on screen size. This is done with basic CSS media queries:
@media only screen and (max-width: 700px) {
main {
max-width: 90%;
padding: 1rem;
}
}
@media only screen and (max-width: 700px) {
.profile {
align-items: center;
flex-direction: column;
}
}
The table of content
This is generated by parsing the post's HTML during render using Go's net/html
library and locating all
<h*>
nodes. If the nodes already have an id
property, then that is used for the
anchor link, otherwise a new one is generated from its content and injected.
Conclusion
In conclusion, no you don't need to have a dockerised MERN app on the cloud if all you want is a simple static site. I enjoyed writing this engine quite a lot actually. This was my first time using Go for anything and I can confirm that the enjoyability factor is definitely not overhyped. Everything simply just works.
If you managed to make it through the entirety of this barely coherent article then uhh... thanks? idk what to say honestly except that you have way too much time.
