Nicholas von Klitzing

Blogging with Emacs, Org-Mode, and Codeberg

There are serveral neat posts and pieces of documentation that explain how to setup org-mode to publish a website. For example, this post takes great inspiration from System Crafters and the Org-mode manual. I recommend you read both for more in depth information.

I found, however, that a lot of the information required to setup a homepage and blog in emacs so that it can compete with static site generators like Hugo, is unnecessarily spread out across several not-so-SEO-optimized resources like mailing lists. I therefore wanted to combine some of the resources and thought processes that I went through as an emacs noobs while creating my site. If you want something more advanced, you should have a look at ox-hugo.

I will also focus on some more niche use cases like Codeberg Pages and using webhook to automatically redeploy your site on push, similar to Netlify. Suprisingly, although there are several examples about how to use webhook with Github, I couldn't find a sinlge one for Gitea (might also be because of the amazing name webhook). The Gitea documentation for working with webhooks is also very outdated. It's actually quite difficult to understand how to use gitea webhooks without browsing through various pull requests.

Warning: This is a low-effort WIP blog post primarily intended collate information. It is probably full of mistakes und undiomatic code ;)

TLDR;

  1. Clone my repo.
  2. Install the required packages.
  3. Customize
  4. emacs -Q --script build-site.el
  5. serve public/
  6. profit

Setting up Emacs and Org

We begin by install all the required packages:

guix install emacs emacs-simple-httpd emacs-magit emacs-htmlize

Recommended but not required:

guix install emacs-paredit emacs-rainbow-delimiters emacs-pinentry

Please refer to the documentation of your distribution and of the packages for how to enable in your configuration since this deviates quite siginificantly depending on your setup.

Next we create a repository to house our project and pull the respective repo.

git pull git@codeberg.org:nickvk/homepage.git

We then create build-site.el in the root of our repo which will tell org-publish how export our site from the org files we create:

(require 'ox-publish)
;; Define the publishing project
(setq org-publish-project-alist
      '(("pages"
	 :recursive t
	 :base-directory "./content"
	 :publishing-directory "./public"
	 :exclude ".*/blog/.*"
	 :publishing-function org-html-publish-to-html
	 :org-html-preamble t
	 :html-preamble "<header><h1 class=\"title\">Nicholas von Klitzing</h1><nav><a href=\"/\">Home</a> / <a href=\"/contact.html\">Contact</a> / <a href=\"/blog/blog.html\">Blog</a> / <a href=\"/pay.html\">Pay</a></nav></header>"
	 :with-author nil
	 :with-creator nil
	 :with-title nil
	 :with-subtitle nil
	 :with-toc nil
	 :section-numbers nil
	 :with-date nil
	 :time-stamp-file nil)
	("blog"
	 :recursive t
	 :base-directory "./content/blog"
	 :publishing-directory "./public/blog"
	 :publishing-function org-html-publish-to-html
	 :org-html-preamble t
	 :html-preamble "<header><h1 class=\"title\">Nicholas von Klitzing</h1><nav><a href=\"/\">Home</a> / <a href=\"/contact.html\">Contact</a> / <a href=\"/blog/blog.html\">Blog</a> / <a href=\"/pay.html\">Pay</a></nav></header>"
	 :time-stamp-file nil
	 :section-numbers nil
	 :with-toc nil
	 :with-author nil
	 :with-creator nil
	 ;; for some reason subtitles are still showing
	 :with-subtitle nil
	 :auto-sitemap t
	 ;; This is quite the hack. The relative links of the site map
	 ;; are generated incorrectly if I name the sitemap
	 ;; index.org. I guess this works for now.
	 :sitemap-filename "blog.org"
	 :sitemap-title ""
	 :sitemap-sort-files anti-chronologically)
	("static"
	 :base-directory "."
	 :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|asc\\|woff\\|woff2"
	 :publishing-directory "./public"
	 :recursive t
	 :publishing-function org-publish-attachment)
	("nvk-homepage" :components ("pages" "blog" "static"))))

(setq org-html-validation-link nil            ;; Don't show validation link
      org-html-head-include-scripts nil       ;; Use our own scripts
      org-html-head-include-default-style nil ;; Use our own styles
      org-html-head "<link rel=\"stylesheet\" href=\"/css/new.min.css\"><link rel=\"stylesheet\" href=\"/css/inter.css\">")

;; Generate the site output
(org-publish-all t)
(message "Build complete!")

This will be the center piece of your site.

We also populate the repositories as follows

~/homepage/     <--- this is the root of your repository
   |- css/      <---\ These two folders are optional but recommended
   |- fonts/    <---/ if you want to serve CSS and fonts yourself
   |- static/   <--- for static files (images, etc.)
   |- content/  <-- the content of your site will be here
   |    - blog/ <-- your blog posts will be here

Before we add any content to our site, we will first style our site and explain some of the build-site.el you saw earlier!

Custom CSS and Fonts

Now…there are two ways to approach styling your site. Both have the same outcome, but one is slightly more difficult to setup in return for not harassing your viewer with requests to insert tracking company's CDN.

In both cases we will take advantage of org-html-head.

I recommend you use a classless CSS framework. That way you everything will just work out of the box (mostly) and you can just focus on the content. A great list of such frameworks can be found here. Pick one and continue.

My personal favorites are:

Option 1: Serve CSS and fonts yourself

Download and place the css files of your chosen framework in the css folder.

Check if your chosen framework relies on any external fonts (or add one if your heart desires) and download these as well. You will need to be careful here though…

For example, the new.css framework recommends the Inter font. If you check inter.css you will find the following:

...
@font-face {
  font-family: 'Inter';
  src: url('src/inter/Inter-MediumItalic.woff2') format('woff2'),
      url('src/inter/Inter-MediumItalic.woff') format('woff');
  font-weight: 500;
  font-style: italic;
  font-display: swap;
}
...

As you can see the relative paths to locate the fonts in the CSS don't line up to how we are serving them.

Luckily emacs comes to the rescue…

M-x replace-string RET src/inter/ RET /fonts/ RET

The "static" component of the the org project defined in build-site.el will copy these files to /public/css/yourframework.css and /public/fonts/yourfont.woff, respectively, because they match the regular expression css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|asc\\|woff\\|woff2 defined in your build-site.el. Now we just need to reference them from the <head> of our site.

Back in build-site.el we add the following

...
(setq org-html-validation-link              ;; Don't show validation link
    org-html-head-include-scripts nil       ;; Use our own scripts
    org-html-head-include-default-style nil ;; Use our own styles
    org-html-head "<link rel=\"stylesheet\" href=\"/css/yourframework.css\"><link rel=\"stylesheet\" href=\"/css/yourfont.css\">")
...

And now your serving your own CSS and fonts. Wasn't that difficult right…? ;)

Option 2: Let a CDN serve your CSS and fonts :'(

Back in build-site.el we add the following

...
(setq org-html-validation-link              ;; Don't show validation link
    org-html-head-include-scripts nil       ;; Use our own scripts
    org-html-head-include-default-style nil ;; Use our own styles
    org-html-head "<link rel=\"stylesheet\" href=\"https://cdn.trackingcompany.biz/yourframework.css\"><link rel=\"stylesheet\" href=\"https://cdn.trackingcompany.biz/yourfont.css\">")
...

Regardless of what option you chose, if you now check your site, you should be able to see your styles applied.

Adding content

Since we're creating a personal homepage + blog combo, we'll begin by adding some pages that cannot be missing a on a personal page. For the sake of this tutorial we will only add an about and a contact page. Feel free to add as many pages as you like.

~/homepage/
   |- content/
   |  - index.org   <-- The "landing" page. We'll make this the "about" section
   |  - contact.org <-- How to reach you

My index.org

#+title: Nicholas von Klitzing
#+author: Nicholas von Klitzing
#+date: 2020-10-27

I'm currently studying Economics and Computer Science at the Wharton School of the University of Pennsylvania expecting to graduate in 2024.
Based in Zurich and Philadelphia.

This page is deliberately sparse but don't hesitate to reach out.

My contact.org

#+title: Contact
#+subtitle: Send me a message
#+author: Nicholas von Klitzing
#+date: 2020-10-18

- [[https://matrix.org/][Matrix]]: [[https://matrix.to/#/@nicholas:nvk.pm][@nicholas:nvk.pm]]
- [[https://xmpp.org/getting-started/)][XMPP]]: nicholas@nvk.pm
- E-Mail: [[mailto:nicholas@nvk.pm][nicholas@nvk.pm]] [[file:static/pubkey.asc][pgp]]
- [[https://joinmastodon.org/][Mastodon]]: [[https://pleroma.nvk.pm/nicholas][nicholas@pleroma.nvk.pm]]

/[[https://keyoxide.org/hkp/e46f87ba9f72242eae557122d217c554f45f448e][verify]]/

Adding blog posts

We can now also begin adding content to our blog by creating org files in the blog/ folder.

If we again look into our build-site.el

("blog"
       :recursive t
       :base-directory "./content/blog"
       :publishing-directory "./public/blog"
       :publishing-function org-html-publish-to-html
       ...
       :auto-sitemap t
       :sitemap-filename "blog.org"
       :sitemap-title ""
       :sitemap-sort-files anti-chronologically)

… we see that we're generating a sitemap only for the org files in the blog/ directory and saving it as blog.org which then gets exported to public/blog/blog.html. This sitemap will automatically update every time you add a post.

For example, adding helloworld.org to blog/

#+title: Hello World!
# +subtitle: My first hello -- have to comment this out for now
#+date: 2021-11-20
Enjoy your stay!
Hopefully more posts are to come...

will be shown as Hello World! on the sitemap.

Adding a nice site header

If you are using a css framework which has special styling for a <header> element (like new.css), then you might want org-publish to automatically add such a header to your published pages. We will also create a <nav> element to navigate our page, which I find preferable to the automatically generate org-publish sitemap.

We will use :html-preamble to do this. Again in our build-site.el

  :html-preamble "<header>
  <h1 class=\"title\">Nicholas von Klitzing</h1>
  <nav>
  <a href=\"/\">Home</a> /
  <a href=\"/contact.html\">Contact</a> /
  <a href=\"/blog/blog.html\">Blog</a> /
  <a href=\"/pay.html\">Pay</a>
  </nav>
</header>"

Now you should have a nice header on all of your pages and be able to navigate with ease.

If you are interested in more detail about the various options I am glossing over, please refer to the org manual and this tutorial which outlines other very useful export options.

Hosting the site

Again, we are faced with two options. An easier one where we use codeberg pages to host our site for us, and a more involved one where we host the site on our own server.

Option 1a: Self-Host with webhooks

  1. Repeat the emacs and org setup on your server
  2. Install webhook on your server. Refer to the project documentation as to how to do this.
  3. Go to your Codeberg repository add a webhook: https://codeberg.org/yourusername/yourrepo/settings/hooks.

    Be sure to use a long and secure secret (such as one generated by pwgen 32 1).

    You can choose any endpoint so long as you setup webhook and your reverse proxy accordingly. I will use https://nvk.pm/hooks/redeploy.

    Leave the remaining settings at their default.

  4. Enable the webhook on your server using the configuration provided in my repo in webhook.conf. Refer to the official documentation for how to do this. On Debian, you can simply run ln -s hompage/webhook.conf /etc/ followed by systemctl restart webhook.
  5. Setup your reverse proxy for webhook. By default webhook runs on port 9000. To match the endpoint given during the Codeberg webhook creation, I will use the following nginx block:
location /hooks/ {
	 proxy_pass http://127.0.0.1:9000/hooks/;
}

Option 1b: Self-Host with TRAMP

If you wish to automatically deploy your site upon building but aren't bothered to setup webhooks, you can also use TRAMP to copy the files to your server directly. To do so, just simply change :publishing-directory from ./public to /ssh:root@nvk.pm:/var/www/html and /ssh:root@nvk.pm/var/www/html/blog, respectively (obviously you need to adjust the path to your webserver).

Now when you build your site, you will be prompted to authenticate with SSH and TRAMP will copy over the file. Be aware that there might be permission issues with your webserver, depending on what user you use to SSH into your server. If you can, you should consider using Option 1a.

Option 2: Codeberg Pages - WIP

Conclusions

I hope this mess of notes was at least somewhat helpful, given that I didn't even proof read it. Hopefully I'll have the time to revise this draft over the coming weeks. Feel free to contact me if you have any suggestions or comments.

Date: 2021-11-21