I’d decided to give up on my free WordPress blog a while back, after having watched a SystemCrafters video on how to blog from Emacs (d’oh, I just have to do everything from Emacs). Of course, daviwil only covered the basics of writing and exporting using Emacs’ built-in packages and later for deploying it on GitHub / Sourcehut pages. And it took me a good year to get down to making the switch but apparently moving to a new place for work is the motivation it required. It felt like cheating to use Jekyll on GitHub and I wanted something that could be deployed to a Sourcehut site easily too (Sourcehut blocks any CDN-based CSS loaded into the site’s HTML) and I wanted something extremely minimal, like Drew Devault’s blog but with even fewer frills — no images anywhere except for if a blog post required them. Looking at his blog source, however, made me realise that there was a lot more to that minimalism than one could see.
Basic setup
This has come through fairly well, so far. I’ve got an org-capture setup for this that links up every new entry captured into a master posts file, adding all the relevant info.
(with-eval-after-load 'org-capture
(defun org-hugo-new-subtree-post-capture-template ()
"Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
(let* ((title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
(fname (org-hugo-slug title)))
(mapconcat #'identity
`(
,(concat "* TODO " title)
":PROPERTIES:"
,(concat ":EXPORT_HUGO_BUNDLE: " fname)
":EXPORT_FILE_NAME: index"
":EXPORT_HUGO_AUTO_SET_LASTMOD: t"
":END:"
"%?\n") ;Place the cursor here finally
"\n")))
(add-to-list 'org-capture-templates
'("h" ;`org-capture' binding + h
"Hugo blog post"
entry
(file+olp "~/my_gits/brihadeesh.github.io/content-org/blog/posts.org" "Posts")
(function org-hugo-new-subtree-post-capture-template))))
Exporting to markdown (Org just doesn’t have a good enough support
yet), tags and organisation of pages into bundles is handled by
ox-hugo 1, 2. The
header arguments in the capture template cover everything. With
Emacs’s .dir-locals.el
feature, a file of that name in the home
directory of the blog ensures every new entry or modification into the
master posts file gets auto-exported to markdown on save. The contents
are quite simple.
;; ~/.dir-locals.el
(("content-org/"
. ((org-mode . ((eval . (org-hugo-auto-export-mode)))))))
With Emacs’s Org mode, this posts file has subheadings under a
Posts header, each of which is a blog post and is exported to a
sub-directory under ~/content/posts/
as a lone index.md
keeping with
the page-bundle kind of organisation.
A tree
run for the content directory shows:
$ tree content
content
├── about
│ └── index.md
├── emacs
│ └── index.md
├── emacs-literate-configuration
│ └── index.md
├── _index.md
├── posts
│ ├── a-dark-side-to-pets
│ │ └── index.md
│ ├── introduction
│ │ └── index.md
│ ├── misunderstanding-evolution
│ │ └── index.md
│ └── pets-put-in-context
│ └── index.md
└── publications
└── index.md
where every sub-directory in the top-level directory has a page of its
own while the home-page is the sole _index.md
in the same. What I’ve
got going feels a little hacky but I’ll figure this out.
Automatic deployment
Hugo, being a static site generator, creates HTML exports into
~/public
and this is what the site uses. All major git hosting
services have configurable CI/CD for deploying these to the domain and
they’re run automatically if you have a specific file in either
- the root directory of the repo for Sourcehut called
.build.yml
~/.github/workflows/
for GitHub called anything you want with a.yml
extension.
Mine uses github-pages
and it looks like this:
name: github pages
on:
push:
branches:
- main # Set a branch that will trigger a deployment
pull_request:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: 'latest'
# extended: true
- name: Build
run: hugo --minify
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
Issues
There’s still a lot to fix
- heading anchors on top-level pages are superfluous
- maybe consider switching to a theme-agnostic setup like Drew’s
- get rid of unnecessary indentation like in the table of contents and headings
- add anchors even to lower level headers
- switch to a Sourcehut site (eventually and when I can afford it)
Further reading
This is but a blog post written, and edited, within half an hour so I likely haven’t covered a lot of important things. I’ll add some links to others’ blog posts that discuss using this or documentation as I come across them.
Have a comment on one of my posts? Drop me toot at @peregrinator@fosstodon.org or by starting a discussion on my public inbox by sending an email to ~peregrinator/public_inbox@lists.sr.ht. Make sure to go through sourcehut mailing list etiquette if you haven't already.