This post is published by just typing into Obsidian

As the title says!

I used to run a WordPress blog. Then I switched to using a static site generator (Hugo). This blog is deployed from a Git repo which runs on Hugo.

And it’s fine! For the last however many posts you can see underneath this one on the homepage, I’d just go to the repo, cd to content/posts, nvim name-of-new-post.md, and start writing there.

With time, I started remembering with some nostalgia the time when there was even less friction, when I would just log in somewhere, write my words, and hit “Publish”.

And also, I like keeping everything in one place. So I asked myself: “How hard would it be to build some system where I can just write to Obsidian, and automatically deploy my Hugo site?”

So I built my own little publishing pipeline, which is essentially just a single post-commit hook in the Git repo where I keep my Obsidian vault.

Here’s the hook, just 30ish lines of bash:

#!/bin/bash

export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
set -e

extract_image_name() {
  stripped_prefix=${1##*\[}
  stripped_suffix=${stripped_prefix%%]]*}
  echo $stripped_suffix
}

HUGO_BLOG="$HOME/repos/sunday-stopwatch"
CHANGED_FILES=$(git diff-tree --no-commit-id -r --name-only HEAD)

while IFS= read -r file; do

  if [[ "$file" == blog/* ]]; then

    cp $file $HUGO_BLOG/content/posts/

    if grep -q "\!\[\[" $file; then
      IMAGE_LINES=$(grep "\!\[\[" $file)
      while IFS= read -r image_line; do
        image_name=$(extract_image_name "$image_line")
        encoded_name=$(echo "$image_name" | sed 's/ /-/g')
        cp blog/"$image_name" $HUGO_BLOG/static/"$encoded_name"
        perl -i -pe "s/!\\[\\[${image_name}\\]\\]/![](\/${encoded_name})/g" $HUGO_BLOG/content/posts/$(basename "$file")
      done <<< "$IMAGE_LINES"
    fi

    if [[ $(yq --front-matter=extract .draft $file) == "false" ]]; then
      git -C $HUGO_BLOG add content/ static/
      git -C $HUGO_BLOG commit -m "Autopublish from vault"
      git -C $HUGO_BLOG push
    fi
  fi
done <<< "$CHANGED_FILES"

To translate:

  1. Whenever there is a commit in the Obsidian vault (and there is always one relatively often because I use the Obsidian Git plugin), this script is run.
  2. It checks all the files that have been changed.
  3. If any of the files start with “blog/”, that means that a file in the blog directory has changed, which means that there is a new post (or a changed post).
  4. This means that it’s copied to the Hugo repo.
  5. If there are any embedded image wikilinks, they are translated into embedded Markdown images, and copied into the static directory in the Hugo repo.
  6. Finally, if the frontmatter of the file has draft set to false, this also triggers the publishing action in the Hugo repo.

Really simple, really silly, but works fine.

Also, proof that screenshots work:

This is just a screenshot directly pasted into the Obsidian file.

Anyway, not the slickest, not the cleanest, not the most engineered approach, but it works nicely for me and that’s all I need.

Oh, and, of course, the CI/CD on the origin for the Hugo blog actually then builds and deploys the site.