I've wanted to write about how I manage my blog since last year, when I wrote my first blog review. Quoting directly myself from that blog post:
I won't go into the technical details of this setup right now, but I can't stress enough just how good this setup is for someone who already likes Emacs a lot (such as myself). With this setup I'm able to focus exactly on the only thing that matters: writing.
Of course, wanting to write something is a thing, actually getting down to writing it is a completely different thing. Between those two moments, life happens, and everything that it entails. But since life contains everything, it also contains the few, brief and special moments when I sit down to write these blog posts. And now, finally, here we are: in this blog post I will describe in details the environemnt I've found for writing and managing my blog.
Enjoy the read!
#Part 0 – General Overview
The managing of a blogging platform such as mine makes use of the following components:
-
hugo
, a static site generator written ingo
that takes in input resource files such as markdown files, image files, and other sort of resources and produces as output a complete static site that can then be directly exposed to the internet. -
emacs
, as the main text editor for writing. -
org-mode
, as the markup language used to write and manage the content of my blog within emacs. -
ox-hugo
, an emacs package that is used to export org-mode files (.org
) into a hugo-compatible markdown (.md
) format.
What this all means, essentially, is that I work within a pipeline of three stages:
-
I write and manage my blog posts in a single org-mode file using
emacs
. -
Whenever I modify the content of this org-file,
ox-hugo
works in the background to export the updated content into an appropriate markdown file. -
When I'm ready to publish the updated content, I use
hugo
to generate the new static site, which is then pushed to my server.
Graphically,
\[\text{org-mode} \xrightarrow{\boldsymbol{\text{ox-hugo}}} \text{markdown} \xrightarrow{\boldsymbol{\text{hugo}}} \text{html, css, js}\]
To explain all the pieces of this puzzle, I will start by first explaining what is a static site generator, then I will briefly mention how to start using hugo, and finally I will show how to manage the blog's content directly in org-mode within emacs by using ox-hugo.
#Part 0.5 – Why a static site?
Suppose, for the sake of discussion, that you are interested in writing about movies because you are very passionate about the film industry, you have a lot to say about the various movies you see and, out of this passion, you're thinking of building an online platform to share your passion with, hopefully, other like-minded people. To fulfil your desire in a meaningful way it is important to first ask yourself the following questions:
-
What kind of end-format do I wish to have for my content?
-
How do I want to publish it?
-
How will I manage it?
-
What kind of shape will take the act of creating new content or modifying the content that already exists?
Since the rise of the Web, one of the simplest and most effective way to share content on the internet has been through the usage of web servers. I've already written something about the web in the article related to web scraping, a technique which aims to extract structured information from web pages. In this blog post I just want to mention the fact that web servers allow anyone to share content online with limited effort.
Now, when talking about web servers and web content, it is crucial to mention that there are different types of web servers each of which is used to service a specific kind of web content. Simplifying matters quite a bit, we find the following two main types of web servers:
-
Those that serve the same content to all the entities that request for it.
-
Those that serve specialized content depending on the entity that requests it.
The first type we call static web servers
, because they serve
static content
, which is content that does not change once it has
been exposed as a public resource on the Web. The second type
instead we call dynamic web servers
, because they serve dynamic content
, which is content that can potentially change everytime a
new entitiy makes a request for it.
The second type of web servers are clearly more powerful for the simple fact that they can generate specific, individual content depending on the client's request. Historically, dynamic web servers came after static web servers. Given that we can now choose dynamic web servers, why would anyone still use static web servers?
While the second type of web servers might seem better because they are more powerful, it is important to be aware that, as someone's uncle wants to remind us.
Indeed, this extra power comes from the fact that dynamic web
servers internally make use of specific interpreters that execute
code for every request made by the clients. An example of such an
interpreter is php
. The php interpreter gets the client's request
data from the web server, and uses such data to generate the
response, that is then sent by the web server back to the client.
\[\text{browser} \longleftrightarrow \text{web server} \longleftrightarrow \text{php interpreter}\]
Given that dynamic web servers introduce components which, by
construction, execute code on the server depending on the client's
request, it is no wonder that there can be significant security
concerns regarding the implementation of such tecnology, especially
if the code of the website has not been written with security in
mind, which, sadly, is too often the case. Wordpress
is the typical
example of a blogging platform that makes use of php
in order to
generate dynamic content.
Thus, to avoid any sort of code execution, the simplest thing to do
is to skip altogether this dynamic generation of content and to
stick with old school static sites that only serve static assets
such as html
, css
and js
files. By not having any extra code being
executed on our servers, we are reducing the attack surface
area. While this makes sense, we might still be wondering:
-
Will this return to static sites bring unwanted consequences?
-
Are we forced to write directly
html
code if we want a static site?
It could be cumbersome to write directly in html
, as it is
moderately verbose and we might want a better, more clean
alternative. To lessen the pain, blogging platform such as
wordpress implement internal text editors using the power of
dynamic execution. What happens then if we lose such power?
Static site generators are built to solve this problem. They are tools that allow one to manage the creation and subsequent modification of static content. They can be seen as pipelines that take in various form of resources and, at the end, produce a sort of "public folder" which contains the static site generated and which can be put within a web server public's folder to expose the generated site to the internet for all to see.
Of course, as in most things in computer science and technology,
there are many different static site generators, and hugo
is only
a single implementation of such idea. I'm not sure how it could be
compared with others like-minded tools, as it is the first ever
static site generator that I use, but so far I've found no deal
breakers with it.
So, let's continue with some details about making hugo
work.
#Part 1 – A brief introduction to Hugo
Hugo is an open-source effort aimed at building an efficient and powerful static site generator. The first thing to do if we're interested in learning about hugo is to download and install it. To this end, let us assume to be working on the following ubuntu version
lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 20.04 LTS
Release: 20.04
Codename: focal
To install hugo we can use the package manager apt
as follows
sudo apt update
...sudo apt install hugo
Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: git git-man liberror-perl libsass1 Suggested packages: git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui gitk gitweb git-cvs git-mediawiki git-svn The following NEW packages will be installed: git git-man hugo liberror-perl libsass1 0 upgraded, 5 newly installed, 0 to remove and 555 not upgraded. Need to get 16.0 MB of archives. After this operation, 86.0 MB of additional disk space will be used. Do you want to continue? [Y/n] Y
After having installed it, we can check if the binary was installed correctly by printing out the version
which hugo
/usr/bin/hugohugo version
Hugo Static Site Generator v0.68.3/extended linux/amd64 BuildDate: 2020-03-25T06:15:45Z
To create a new site we can use the command hugo new site <folder>
. This command will create a new folder within the
directory we're currently in. Say that we are in
/home/leo/Desktop/
, then we execute
hugo new site movie-reviews
Congratulations! Your new Hugo site is created in /home/leo/Desktop/movie-reviews.
Just a few more steps and you're ready to go:
1. Download a theme into the same-named folder.
Choose a theme from https://themes.gohugo.io/ or
create your own with the "hugo new theme THEMENAME" command.
2. Perhaps you want to add some content. You can add single files with "hugo new SECTIONNAME/FILENAME.FORMAT".
3. Start the built-in live server via "hugo server".
Visit https://gohugo.io/ for quickstart guide and full documentation.
this creates a new folder, which in our case we have named movie-reviews
since we that is the argument we used when calling the command.
ls
movie-reviews
By going in that folder we can see the default structure of a hugo project
cd movie-reviews
tree .
. ├── archetypes │ └── default.md ├── config.toml ├── content ├── data ├── layouts ├── static └── themes 6 directories, 2 files
the most important file is the config.toml
, which contains the
basic configuration for our site
baseURL = "http://example.org/"
languageCode = "en-us"
title = "My New Hugo Site"
Let's change the title to a more appropriate one, say "Moview Reviews"
baseURL = "http://example.org/"
languageCode = "en-us"
title = "Movie Reviews"
If we now want to see how our site would look like when exported
and exposed on the internet, we can use the command hugo server -D
. This command starts a local web server which serves the
content of the site generated by hugo.
hugo server -D
Building sites … WARN 2022/09/01 18:03:01 found no layout file for "HTML" for kind "home": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2022/09/01 18:03:01 found no layout file for "HTML" for kind "taxonomyTerm": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2022/09/01 18:03:01 found no layout file for "HTML" for kind "taxonomyTerm": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
| EN
-------------------+-----
Pages | 3
Paginator pages | 0
Non-page files | 0
Static files | 0
Processed images | 0
Aliases | 0
Sitemaps | 1
Cleaned | 0
Built in 6 ms
Watching for changes in /home/leo/Desktop/movie-reviews/{archetypes,content,data,layouts,static}
Watching for config changes in /home/leo/Desktop/movie-reviews/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
To actually see the website we have to visit the local url
http://localhost:1313/
using any browser.
Surprise surprise, right now it is a bit too much empty!
To start seeing something beautiful without too much effort the idea is to use pre-defined themes, which can be explored from hugo's official site in the themes section: https://themes.gohugo.io/.
The one I personally use is the hugo-theme-terminal.
reading the documentation we can see that in order to use it we
have to clone the repository of the theme within the themes
folder
of the hugo site as well as update the config.toml
file with the
one they have in the documentation. Here I will showcase a simple
configuration for our blog about movie reviews
baseurl = "/"
languageCode = "en-us"
theme = "terminal"
paginate = 5
[params]
# dir name of your main content (default is `content/posts`).
# the list of set content will show up on your index page (baseurl).
contentTypeName = "posts"
# ["orange", "blue", "red", "green", "pink"]
themeColor = "orange"
# if you set this to 0, only submenu trigger will be visible
showMenuItems = 2
# show selector to switch language
showLanguageSelector = false
# set theme to full screen width
fullWidthTheme = false
# center theme with default width
centerTheme = true
# if your resource directory contains an image called `cover.(jpg|png|webp)`,
# then the file will be used as a cover automatically.
# With this option you don't have to put the `cover` param in a front-matter.
autoCover = true
# set post to show the last updated
# If you use git, you can set `enableGitInfo` to `true` and then post will automatically get the last updated
showLastUpdated = false
[languages]
[languages.en]
languageName = "English"
title = "Movie Review"
owner = "Leonardo Tamiano"
keywords = "movies reviews"
copyright = ""
menuMore = "Show more"
readMore = "Read more"
readOtherPosts = "Read other posts"
newerPosts = "Newer posts"
olderPosts = "Older posts"
missingContentMessage = "Page not found..."
missingBackButtonLabel = "Back to home page"
[languages.en.params.logo]
logoText = "Menu"
logoHomeLink = "/"
[languages.en.menu]
[[languages.en.menu.main]]
identifier = "about"
name = "About"
url = "/about"
Now if we execute again hugo server -D
we get the following result
Let's now write our about
page. This can be done by going in the
./content
folder and writin an about.md
file with the following
content
+++
title = "About"
lastmod = 2022-09-02T03:24:10+02:00
draft = false
+++
Hello, stranger on the internet, this page is all about me!
My name is **Leonardo Tamiano**, and I am really passionate about movies and all things related to cinema. I want to use this platform as a means to share this passion with like-minded people.
I hope you enjoy my writings.
What's your favorite movie?
Notice how at the top of the file we find a series of attributes
such as title
, lastmod
and draft
. These are properties of the file
which are accessible from hugo's internal engine during
exporting. After these attributes we find the content itself,
which is written according to blackfriday, a specific
implementation of markdown in go (remember that hugo is also
written in go).
By saving this file as ./content/about.md
we can directly see the
new exported content in the browser, because when using the hugo server -D
command any change we make to the content of the blog is
directly exported and refreshed in the background.
Now, it's to mention that when using external themes, the specific way in which the site is generated heavily depends on the particular implementation of the theme we used. This is because hugo is a framework for generating static sites, and its interface can be used differently by different themes.
In our case to make things work what matters is that in the
config.toml
file we have specified as the url the value url = "/about"
for the about entry in the menu and that the file of the
about page was called either about.md
or About.md
. If, for
example, we had called the about page file something like test.md
,
then things would not have worked anymore.
This is all to say that the abstraction at which we are working here is not trivial, and one must discover all these little ways in which hugo interacts with the specific themes we've choosen and how we can obtain the result we want. Said in another way, it is necessary to spend some time tinkering away in order to understand how this generation process works.
Before discussing how to integrate org-mode in all of this, let's
suppose we want to write our first movie review. The idea is to
create a new .md
file within the ./content/posts
directory, as the
contentTypeName
property in the config.toml
has the value "posts"
.
# ...
[params]
# dir name of your main content (default is `content/posts`).
# the list of set content will show up on your index page (baseurl).
contentTypeName = "posts"
# ...
Given that recently I have re-watched with my brother "2001: A
Space Odyssey", the well known film by Stanley Kubrick
, let's
write a brief review about it in the file
./content/posts/2001_a_space_odyssey.md
+++
title = "2001: A Space Odyssey"
lastmod = 2022-09-02T03:24:10+02:00
draft = false
+++
In "2001: A Space Odyssey", Kubrick does not merely
tells a sci-fi story of space exploration,
starhips, and other things of the sort.
In this film, Kubrick talks about the
entire history of humans, from our ancient origins,
to a most fascinating possible future.
...
Having saved it we can view it exported in the home page section. If we click on the title we also get the single page view of the post.
Once we are satisfied with our writings, we can export all the
assets within a single folder by simply calling hugo
with no extra
parameter.
ls
archetypes config.toml content data layouts resources static themeshugo
| EN -------------------+----- Pages | 11 Paginator pages | 0 Non-page files | 0 Static files | 15 Processed images | 0 Aliases | 2 Sitemaps | 1 Cleaned | 0 Total in 68 msls
archetypes config.toml content data layouts public resources static themes
Observe how the public
folder that has been created represents our final static site.
root@ubuntu:/home/leo/Desktop/movie-reviews/public# tree -L 1
.
├── 404.html
├── about
├── assets
├── categories
├── img
├── index.html
├── index.xml
├── page
├── posts
├── sitemap.xml
└── tags
7 directories, 4 files
We can then take this folder, move it under the root directory of
a web server, and expose it to the internet. Of course, to
actually share this to the internet we need to handle a bunch of
other things, such as the issue of hosting
, which is too complex
to discuss it in here.
For the sake of example, suppose we want to test out our static site using a local python web server. We can copy the public folder somewhere else and start a local python web server within it.
cp -r public/ /home/leo/
cd /home/leo/public
python3 -m http.server 1337
Serving HTTP on 0.0.0.0 port 1337 (http://0.0.0.0:1337/) ...
When we go to http://127.0.0.1:1337
with our browser we get the
same view as before. This means that if we copy this directory
anyhwere else, we get back the same content.
#Part 2 – Write directly in org-mode with ox-hugo
Now that we are able to write posts using markdown
and export
those posts into html and other assets using hugo
, we can start to
understand how to integrate the final pieces, emacs
, org-mode
and
ox-hugo
in all of this.
First of all, why would anyone want to add emacs to the mix? As always, with emacs it is a matter of integration. To someone who's used with emacs, it is simply a joy to work within emacs. Especially considering the power of org-mode when dealing with textual content, the idea of being able to manage within org-mode an entire blog made up of various blog posts is a pretty cool one, at least to me.
To make all of this work, we first have to install emacs as well
as the ox-hugo
package. To bootstrap the process of initializing
emacs, the following small configuration should suffice. It has to
be saved in the home directory of the user under the name
.emacs
. It is also necessary to have cloned the package
use-package on the file system, also in the home directory of the
user.
;; Added by Package.el. This must come before configurations of
;; installed packages. Don't delete this line. If you don't want it,
;; just comment it out by adding a semicolon to the start of the line.
;; You may delete these explanatory comments.
(package-initialize)
;; NOTE: execute 'git pull https://github.com/jwiegley/use-package'
;; from within the home directory of the user
(add-to-list 'load-path "~/use-package")
(require 'use-package)
(use-package package
:config
;; Set priorities of using melpa-stable.
;; The higher the number, the higher the priority.
(setq package-archive-priorities
'(("melpa-stable" . 2)
("MELPA" . 1)
("gnu" . 0)))
;; Add to list melpa stable
(setq package-archives
`(("melpa-stable" . "https://stable.melpa.org/packages/")
("MELPA" . "https://melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
))
)
(use-package ox-hugo
:ensure t
:after ox
)
Once we have all of this, we can create a new file named
content.org
within the movie-reviews
folder previously created by
hugo. In it, we can put the content of the only post we had
previously written.
Notice how the information is structured in the org-file. The most
important thing, in order, is the presence of the STARTUP
and
HUGO_BASE_DIR
variables at the top of the file, then we find the
org entry blog posts which contains the property
EXPORT_HUGO_SECTION
, which we gave it the value "posts" because in
our hugo projet the various posts are written to the
./content/posts
folder (remember the value of the contentTypeName
property in config.toml
?), and then for each outline within this
section we write the EXPORT_FILE_NAME
to represent that the
outline contains a new hugo post. The value of the property
represents the final name of the .md
post which should later be
processed by ox-hugo
.
After having written it, we have to execute M-x org-hugo-auto-export-mode ENTER
and then everytime we save a post
it will automatically be exported as an .md
file in the
filesystem. If we combine this with the previously shown hugo server -D
in the background, we can watch our changes live.
The power of this setup is that now we can posts easily by just writing another org outline with a different content.
Now, having a single org file for all the content is my
choice. Other people might prefer having different org files for
different entries. The idea however is that you can manager this
however you want. What ox-hugo
does, essentialy, is it provides an
emacs-friendly interface for generating the markdown content
required by hugo, since what's important for hugo to work properly
is the presence of the markdown files.
Having discussed the various components, as well as how they connect with eachothers, we can conclude with some final words.
#Part 3 – Conclusion
First of all, I hope this explanation was clear to everyone, but if there still some doubt about how it all works, a comment down here is welcomed, or also an email. I'll try to explain better.
Then, some more general thoughts. Sometimes I wonder if such setup is simply too complex, as it requires a lot of different components. I feel that the issue of complexity in software is often not given enough space. I mean, yes, everything talks about complexity, but the systems we use are becoming ever-increasing complex, as they require more and more components and as the interation between those components become ever more sophisticated.
Right now what matter to me is that this setup has worked for the last \(2\) years surprisingly well, requiring little to no maintenance whatsoever. As I have briefly mentioned at the start of the blog post, with this setup I'm able to focus entirely on the only thing that actually matters: the writing. Thus, for now, this complexity seems to be worth the price, at least for me.
I hope reading this has somehow helped you!
L.T.