Minimal Blogging Setup with Orgmode and ssg5

Published 2020-07-21 on Anjan's Homepage

1 My Old Setup

I prefer orgmode over markdown because already I use orgmode for appointments, reminders, notes, etc. However, most static website generators only support Markdown. Luckily, orgmode has an html export function: M-x org-publish. org-publish is much more powerful and reliable than any markdown converter I have used.

Since 2016, I have been using jekyll which is bloated and requires a lot of ruby dependencies. To make sure my website always built, I setup sourcehut builds to build my jekyll website. Each website build takes 3 minutes on sourcehut.

Building the website locally was also a terrible experience. I had hacked on a shell script that would take my org-publish output and make it usable for jekyll by setting the title and layout variables. To publish a new blog post, I had to: org-publish -> run my script -> commit html AND org file -> wait for jekyll to build on sourcehut. If I had made a mistake, I would repeat the entire process… Eventually this setup was so cumbersome, I stopped blogging.

2 My New Setup

I do not use most of the features of jekyll and needed something more minimal. In the past, I have dabbled with werc, hugo, and guile-haunt. Eventually, I stumbled upon ssg5 which is a static site generator written in 180 lines of posix compliant shell. If you do not use markdown, ssg5 will only use unix coreutils to make your website with a common header and footer. Perfect!

I decided I should go through the pain of porting my website over to ssg5 and documenting my experience. Here is what I had to pay attention to:

2.1 Openring

To generate the "Articles from blogs I follow around the net" at the bottom of each blog post, I use openring. Openring is a great way to support a more decentralized internet where everyone has their own website. A template for the blog post footer is saved as webring-in.html. My script downloads openring and prints to stdout the html to be included at the end of blog posts.

2.2 Changes to rssg

To generate my rss feed, I use rssg. However, rssg requires a list of posts on the homepage. Unfortunately, ssg5 does not generate a list of posts and I had to hack together Here is the output from

<li>2020-07-21 <a href="/posts/2020-07-21-ssg.html" title="2020-07-21">Minimal Blogging Setup with Orgmode and ssg5</a></li>
<li>2020-07-06 <a href="/posts/2020-07-06-crust.html" title="2020-07-06">Compiling crust for pinephone on Postmarketos</a></li>
<li>2016-04-26 <a href="/posts/2016-04-26-Hello-World.html" title="2016-04-26">Hello World</a></li>

My fork of rssg is located here. Gnu coreutils' date command does not have a -j flag as required by the date_rfc_822() function in rssg. The dates output by are in YYYY-MM-DD format. To convert to rfc_882, I used the following date command in the date_rfc_822 function:

date_rfc_822() {
    date -R -d "$1"

2.3 Changes to ssg5

My fork of ssg5 is located here. I modified ssg5 to run openring, run rssg, and change the footer depending on the page. Running openring takes alot of time which is inconvenient when editing a page locally so I implemented a -f flag to ssg5. If -f is the fifth argument into ssg5, the script doesn't generate the webring.

In the main function on line 52, I load in the "Articles from blogs I follow" html into an environmental variable called $WEBRING:

[ "$5" != "-f" ] && WEBRING="$(./" && export WEBRING

On line 53, I generate the blogfeed to be included at the bottom of the homepage:

FEED="$(./" && export FEED

Depending on whether the page is homepage or a blog post, I need to include a different footer. If the page is a blog post, I must include the openring output and if the page is the homepage I must include the blog post log (generated by As such, I changed the render_html_files() function to:

render_html_files() {
  while read -r f
     echo "$1/$f" | grep "index.html" > /dev/null && EXTRAFOOT="$FEED" && export EXTRAFOOT
     echo "$1/$f" | grep "posts" > /dev/null && EXTRAFOOT="$WEBRING" && export EXTRAFOOT
     render_html_file "$3" < "$1/$f" > "$2/$f"
     EXTRAFOOT="" && export EXTRAFOOT

If the file name matches the regular expression index.html, it gets $FEED as the extra footer. If the file name is matches the posts regular expression, $WEBRING is the extra footer.

Finally, I modified the render_html_file function to include my environmental variable $EXTRAFOOT before $FOOTER:

    print body
    print ENVIRON["FOOTER"]

At the end of the main function in my ssg script, I use rssg to generate my site's rss feed:

[ "$5" != "-f" ] && ./rssg dst/index.html 'Anjan Momi Homepage' > dst/feed.xml

2.4 Orgmode

In my .spacemacs, I added the following lines:

(setq org-publish-project-alist
         ;; Path to org files.
         :base-directory "~/code/"
         :base-extension "org"

         ;; Path to ssg osts
         :publishing-directory "~/code/"
         :recursive t
         :publishing-function org-html-publish-to-html
         :body-only t ;ssg will not add header and footer if an <HTML> tag exists
         :link-up /index.html
         :html-postamble t
         :toc nil

Open an org file from your website and run C-c C-e P p to generate the html for your blog files.

If you edit the org-publish-project-alist variable and want to regenerate all files (even unmodified) run: C-u 1 M-x org-publish.

2.5 Deployment

To simplify the deployment of my website, I added the following aliases to my zshrc:

alias makesite="rm dst/.files; ./ssg5 src dst \"Anjandev\'s Homepage\""
alias deploysite="rsync -rvPz --delete dst/ deploy@homeserver:/"

In the makesite alias, rm dst/.files ensures ssg5 will rebuild all the website's pages.

3 Conclusion

To check mistakes with my website's rss and html, I used w3c's validation service for html and rss.

Finally, I had to go back in time and convert all my Markdown posts to orgmode.

My website now generates in under a second for a clean build compared to 3 minutes with jekyll. I don't have to use jekyll, jekyll's dependencies, and sourcehut to build my website. Expect more blog posts going into the future!

Have a comment on one of my posts? Start a discussion in my public inbox by sending an email to ~anjan/ [mailing list etiquette]

Articles from blogs I follow around the net

These articles/blogs do not represent my own opinions or views.

Web analytics should at least meet the standards of informed consent

Research conducted on human beings, at least outside of the domain of technology, has to meet a minimum standard of ethical reasoning called informed consent. Details vary, but the general elements of informed consent are: Disclosure of the nature and purpose…

via Drew DeVault's blog December 4, 2020

How to Make Biomass Energy Sustainable Again

From the Neolithic to the beginning of the twentieth century, coppiced woodlands, pollarded trees, and hedgerows provided people with a sustainable supply of energy, materials, and food.

via LOW←TECH MAGAZINE September 20, 2020

Eshell versus M-x shell

I’ve used and defended Eshell for years. Sadly, Eshell has some long standing issues that I grew tired of in the long run. So I’ve decided to switch to M-x shell and see how much of my Eshell workflow I could port. Language and the underlying shell pr…

via Pierre Neidhardt's homepage June 26, 2020

Generated by openring