The ‘How’ #
As mentioned in my last post, I have changed my blogging setup as follows:
Blog site BEFORE: | Grav + Gantry5 + Helium theme |
---|---|
Blog site AFTER: | Obsidian + Hugo + Blowfish theme |
My last post covered why. See that if you want to know more on that front or to get an idea for when this kind of setup might make sense (and when it might not).
This post explains how I have things set up. The assumption is that you already got past why. I wrote this up should anyone else be interested in going down this path. The intention, as always, is to hopefully save others time. If you found your way here, I assume it is because you already have some interest.
Please do understand that I do not advocate this setup for everyone. Not even close.
For most folks, simply setting up a free WordPress.org site is likely enough. You don’t have to play web/system administrator, nor do anything but create content.
For those who prefer more control over their own content but still want a simpler setup, see my earlier posts on Grav. That setup served me well and would definitely be “easier” for many.
For developer types, you may be better served using other tutorials/videos that show how to manage your Hugo site using a setup of something like VSCode and GitHub.
But if you do not fall into those categories, and you are someone who would like to leverage their use of Obsidian along with Hugo, this may work for you. Or not.
Tutorial Settings #
For the purposes of this tutorial, please note the following:
- I am on macOS. Please adjust as needed if you are using MS Windows or Linux.
- My Hugo site will be located at
/Users/frank/MyHugoSite/
. - My Hugo site uses the Blowfish theme. Note that different Hugo themes use different approaches for how they are setup/configured, and this one is a bit more involved than some basic ones. So these steps are very theme-specific. YMMV if you choose to use another theme.
- My Obsidian vault will be located at
/Users/frank/MyVault/
. (This is not where it actually is. But typing out the path to my iCloud storage is a bit much and very OS-specific. This way it will hopefully be easier to search/replace if you are on another OS.)1 hugo
and the shell scripts are located in/Users/frank/bin/
1- My production webserver is
- Host:
www.remotesite.com
- User
myuser
- Website home directory:
/var/www/html/
- Host:
(Adjust these as necessary for your own setup. They are here purely for demonstration purposes.)
Hugo #
Starting with the end in mind, let us first setup a basic Hugo site locally. Here are the basic steps.
1. Download Hugo #
A few quick notes regarding Hugo:
- Hugo is written in Go. It consists of a single binary executable file for each OS/architecture combination (e.g., a Mac running macOS on Apple Silicon, a PC running MS Windows on an AMD/Intel x64 )
- On GitHub you will find the various Hugo OS/architecture files mostly in compressed form, either as
tar.gz
(for macOS/*nix) or.zip
(Windows) files. - Hugo currently comes in three (3) editions:
- Standard Edition (provides core functionality)
- Extended Edition
- Encode to the WebP format when processing images. You can decode WebP images with any edition.
- Transpile Sass to CSS using the embedded LibSass transpiler. You can use the Dart Sass transpiler with any edition.
- Extended/Deploy Edition
- All the features of Extended Edition plus…
- Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See details.
- As I type this, the filenames follow one of these patterns:
hugo_<version>_<os>_<CPUarchitecture>.extension
hugo_extended_<version>_<os>_<CPUarchitecture>.extension
hugo_extended_withdeploy_<version>_<os>_<CPUarchitecture>.extension
- For my use case, I typically download the Extended Edition. I don’t need the deploy version. YMMV.
With this in mind,
- Go to the latest release page of Hugo’s GitHub repo
(I keep this bookmarked in my browser. This way I can go and find the most up-to-date version whenever I like.) - Click on the latest version to bring up its page
- Scroll down, find, and download the appropriate file for whichever OS/architecture you use. For example, I am currently on an M4 MacBook Pro, so I would download the file
hugo_extended_<version>_darwin-universal.tar.gz
. - Decompress the downloaded file and put it somewhere that you can access from a terminal session.
For example, I am on macOS. So I might
- download the file to my Desktop
- double-click on the file to decompress it in place
- rename the decompressed executable to simply
hugo
- copy
hugo
to some location like/usr/local/bin/
or, as in my case, to another directory where I keep various scripts, a directory that I have added as part of myPATH
. This makes the commandhugo
available to me any time I bring up a Terminal session, regardless of what directory I am in
For a Windows user, you might do something similar (e.g., store the hugo
file in C:\Windows\system32
). The key thing is to make sure that you can execute the hugo
command from the Command Prompt or Powershell. If you place this executable into a directory that is not in your PATH
environment variable, you will have to put the full or relative path to the executable every time you wish to run it.
2. Create a basic Hugo site #
While I will not provide a tutorial on Hugo itself (you can find much better ones online that delve into the various aspects), nor will I go into the full-depth of my particular configuration of Hugo or the Blowfish theme, I will provide basic steps to get a site up. Again, I am using the paths listed for demonstration purposes at the top of this post.
2.1. Open a terminal #
2.2. Navigate to /Users/frank/
#
2.3. Run hugo new site MyHugoSite
#
This creates the basic scaffolding of a Hugo website. It includes
├── archetypes
├── assets
├── content <== This is where your website content in Markdown format goes
├── data
├── hugo.toml <== This is the default configuration file for a Hugo site
├── i18n
├── layouts
├── static
└── themes <== This is typically where themes are stored
Note that while most basic Hugo setups use this single hugo.toml
file for configuration, the particular theme that I chose, Blowfish, does not.
2.4. Run cd MyHugoSite
#
This places you in the root of the Hugo site.
2.5. Run git init
#
This is done so that we can install a theme.
2.6. Install Blowfish theme as Git submodule #
git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish
2.7. Run rm hugo.toml
#
The Blowfish theme does not use the default hugo.toml
file, instead relying on a set of TOML files.
2.8. Download Blowfish theme config files #
Using curl
, wget
, or your browser, download the theme’s config files from the Blowfish GitHub repo here and place the file in the Hugo site root directory.
(WARNING: As of this writing, do NOT use the link provided on the Blowfish website’s installation documentation page! It appears to point to an external site of some questionable nature.)
2.9. Run unzip config-default.zip
#
This will decompress this file. It should create the directories config/_default/
and place various config files in there. This makes the current Hugo site look like this:
├── archetypes
├── assets
├── config <== directories created by decompressing config-default.zip
│ └── _default
│ ├── hugo.toml
│ ├── languages.en.toml
│ ├── markup.toml
│ ├── menus.en.toml
│ ├── module.toml
│ └── params.toml
├── config-default.zip <== this file can be deleted once decompressed
├── content
├── data
├── i18n
├── layouts
├── static
└── themes
Please note that these configuration files provide a lot of insight into what you can do with the Blowfish Hugo theme. We will not go over everything here. Just be aware that much of what you need is likely already in those files and simply commented out.
2.10. Edit hugo.toml
#
Uncomment the theme
and baseURL
lines, setting the latter to whatever your site FQDN is. For this demo I will set it to
theme = "blowfish" # UNCOMMENT THIS LINE
baseURL = "https://frank.seesink.com/"
Save the file.
2.11. Run hugo server
#
The command hugo
executed in the root directory of a Hugo site will generate all of the necessary files for your website in the public
directory. The command hugo server
performs this task and also starts up a local webserver to host your Hugo site so that you can see it from your browser.
2.12. In your browser go to http://localhost:1313
#
You should now see a basic site complete with the Blowfish theme. We will NOT be going through all the customizations possible. As one of my professors liked to say, “The exercise is left to the reader.”
Obsidian #
I have a single Obsidian vault where I keep all of my notes. This lets me search one vault/directory structure for anything/everything. Here are the basic steps to get setup.
Download, install, and run Obsidian #
Download Obsidian for your particular OS (Windows, macOS, Linux), install it, and run it.
Create a vault #
For this demo, I created my vault as /Users/frank/MyVault
.
Create a directory called Blog site
#
At the root of the Obsidian vault, create a directory named Blog site
. (The name is arbitrary. I just chose this to keep things simple.) The key thing is that this directory will correspond to the Hugo content
directory.
Create a directory called Templates
#
At the root of the Obsidian vault, create a directory named Templates
. This will hold your Obsidian templates, which in this case you can then leverage to speed up the creation of each new blog post.
Create an Obsidian template named Blog Post
#
In the Templates
directory, create a file named Blog Post
. Set the contents of this file to the following:
---
title: "{{Title}}"
description:
date: "{{date:YYYY-MM-DD}}T{{time:HH:mm:ss}}+00:00"
draft: false
tags:
---
CONTENT HERE
Note I will not be explaining the full details of Obsidian templates here. But suffice it to say that when this template is applied to a new Obsidian note, it adds this content, replacing the various fields with their corresponding values (e.g., {{Title}}
is replaced by the name of the Obsidian note filename). Currently this template is pretty simple, only putting key basic information in place like the current date (in appropriate format) and setting the draft
setting to false
so that if you generate your Hugo site, this page will be rendered. This lets you later see the page when you host it locally.
In Obsidian, when you wish to apply a template to a note (assuming you have configured a template directory in the settings), you can simply enter [Cmd][P]
(on macOS), start typing “template” which should quickly bring up “Templates: Insert template”, hit [Enter]
, then select the template you wish you apply (Blog Post
in this case), and voila! All the content above is in your new note, complete with the title and date replaced.
Create a Hotkey to Insert template #
I take this a step further by setting up a hotkey in the Obsidian settings by doing the following:
- Go to
Obsidian | Settings...
or click the gear icon near the bottom right of the left navigation menu to bring up Obsidian’s settings - Select
Templates
on the left - Set
Template folder location
on the right to theTemlates
directory that you created - Select
Hotkeys
on the left - In the filter box in the upper right, type “template”
- Click the
(+)
to the right ofTemplates: Insert template
- Enter the hotkey you wish to use (e.g., I use
[Cmd][T]
) - Close the settings
Now my process for creating a new blog post is
- Press
[Cmd][N]
to create a new note - Name the note
- Press
[Cmd][T]
to bring up the templates - Select the
Blog Post
template and hit[Enter]
. Or, if it is the only template you have, it is already selected to simply hit[Enter]
Voila!
Create the following directory structure #
Inside the Blog site
directory, create the following sample directory/file structure:
├── Blog site <== corresponds to the Hugo site's "content" directory
│ ├── about <== corresponds to /about page
│ │ └── index.md
│ └── blog <== where all the blog posts are stored, one per directory
│ └── first-post <== this directory is the "slug" to your first blog post
│ └── index.md <== this file has your first blog post content
...
Please note that I am doing this for a specific reason. Hugo will take either a single file named first-post.md
or a directory named first-post
which has a file named index.md
inside and pretty much render them the same.
The reason that I do things this way is so that I can keep any images used in a blog post together with the blog post itself. That is, if I had a file named myphoto.jpg
, I would link to it in my blog post using the simple Markdown tag 
. In fact, ANY image links would take this form since the image would be in the same directory as the blog post.
By default, Obsidian offers to store any/all images that you drag/drop into a note into a single directory. You then reference those images from your blog posts with links that have to navigate to that directory. Within Obsidian this is simple. But Obsidian tends to mask this path unless you take specific and careful steps. That is, if you have the Obsidian setting for “New Link format” set to “Shortest path when possible”, and you drag/drop an image into an Obsidian note, Obsidian sets the link exactly as above, as Obsidian knows where that one directory is that it stores all the drag/dropped files. However, this causes issues when you export your Obsidian notes to your Hugo site, as that information isn’t magically added to your links.
There may well be alternative ways to deal with this. But my solution was to simply keep any images used in a blog post within the directory of that blog post. (This also happened to correspond to how Grav stores each page in its site, which made my migration from Grav to Hugo a little easier.)
Edit the two index.md
files
#
Applying the Obsidian Blost Post
template created earlier to each file, add some basic info below the last ---
. For example,
about/index.md
:
---
title: About
description:
date: 2025-01-04T17:08:10-05:00
draft: false
tags:
---
# About Page
Here I tell you my life story.
blog/first-post/index.md
:
---
title: First Post!
description:
date: 2025-01-04T17:07:30+00:00
draft: false
tags:
---
# First post!
This is the beginning of my blog site.
This is just so we have something to show for our work.
Turn off Wikilinks #
In the Obsidian settings under Files and Links
, the one change from default that I made is that I turned OFF Use [[Wikilinks]]
.
By default, Obsidian uses links of this form to connect different notes together. By turning this OFF, I have Obsidian using default Markdown style links (e.g., [text](url)
).
Mind you, there is nothing stopping me from using Wikilinks-formatted links in my other notes. Obsidian will honor either style. But by doing this, I do not have to perform any kind of link conversion when I export things to my Hugo site. Note that Hugo does not support Wikilinks. It only supports standard Markdown links.
Combining this with keeping images used by blog posts within the directory of that blog post, this means I can directly sync my entire blog site from within Obsidian directly over to my Hugo site directory under the content
folder, all without having to do any other machinations like running Python scripts/etc. to do conversions and such.
Install Obsidian plugins #
When you first create an Obsidian vault, plugins are disabled.
- Go into Obsidian settings
- Select
Community plugins
on the left - Click on
[Turn on community plugins]
- Click
[Browse]
- For each of the plugins mentioned below
- Search for them
- Select them
- Click on
[Install]
to install them - Click on
[Enable]
to enable them
In Obsidian, I installed the following plugins relevant to this:
- Shell commands Used to run shell commands from within Obsidian.
- I use this to run a script I named
blogupdate
that handles generating the Hugo site from the Obsidian vault files (see further below for details) - In Obsidian settings,
- Select
Shell commands
on the left - Select the
Shell commands
tab at the top - Click
[New shell command]
- Where it says “Enter your command”, put in
/Users/frank/bin/blogupdate
- Select
- I use this to run a script I named
- Commander Used to create a button on left ribbon to run previous shell command
- I use this to simplify triggering the
Shell commands: Execute: blogupdate
from the first plugin - In Obsidian settings,
- Select
Commander
on the left - Select the
Ribbon
tab at the top - Click
[Add command]
- Type “shell” in the “Choose a Command to add” field
- Select
Shell commands: Execute: /Users/frank/bin/blogupdate
- Select an icon for the button (I chose the ‘Hexagon’ since it’s part of the Hugo logo)
- It should now show
Shell commands: Execute: /Users/frank/bin/blogupdate
. Press[Enter]
to save - (optional) Adjust color of icon by clicking on black circle to the right, selecting a color, and closing the selector window by clicking outside of it
- Select
- I use this to simplify triggering the
At this point clicking on the icon created in the left ribbon bar would run the blogupdate
script if it existed. So let us tackle that next.
Create shell scripts #
In the previous section we configured a button on the Obsidian ribbon bar that fires off a shell script called blogupdate
. We will also be creating two more scripts–blog
and blogupload
–which run your Hugo site locally and push your Hugo site to your public server, respectively.
The only thing that really matters here is that you know the full path to where you store the shell scripts.
blogupdate #
/Users/frank/bin/blogupdate
:
#!/bin/bash
echo "Sync Obsidian blog to Hugo site..."
rsync -azP --delete "/Users/frank/MyVault/Blog site/" /Users/frank/MyHugoSite/content/
cd /Users/frank/MyHugoSite
echo "Run Hugo to generate site..."
/Users/frank/bin/hugo
NOTE:
- If you create your Obsidian vault in iCloud (e.g., you want to be able to sync your vault between your Mac and your iPhone versions of Obsidian), the path to your vault will be something like
/Users/<username>/Library/Mobile Documents/iCloud~md~obsidian/Documents/<vault name>/
blog #
/Users/frank/bin/blog
:
#!/bin/bash
/Users/frank/bin/blogupdate
cd /Users/frank/MyHugoSite
/Users/frank/bin/hugo
echo "Start local Hugo instance and open browser tab..."
open http://localhost:1313 && /Users/frank/bin/hugo server
The reason for that last line is that once hugo server
is running, it hangs in the Terminal session. So it needs to be the last line in the script since the idea here is that once you enter blog
, it continues running until you go back and hit [CTRL][C]
to end it. And most of the time, between opening the URL in my default browser and firing up Hugo, it tends to load the site fine. Worst case, I have to reload the browser page to see the site if the timing is a little off. But Hugo is so incredibly fast at generating the site contents that it is almost instant.
blogupload #
/Users/frank/bin/blogupload
:
#!/bin/bash
/Users/frank/bin/blogupdate
echo "Pushing website to production..."
rsync -azP --exclude presentations --delete /Users/frank/MyHugoSite/public/ myuser@remotesite.com:/var/www/html/
Here the rsync
command is specific to my setup. YMMV:
- The
-azP
flags simply set the syncing to archive mode, compress, and to handle partial progress (i.e., basically handle any interruptions). - The
--exclude
flag is there to preventrsync
from removing thepresentations
directory symlink if it already exists on my online site. (This is because that directory is setup in my actual webserver to allow file indexing, and the actual directory is where I store any/all presentation files that I reference from the/resources
page of my site. I left it here purely to show that while you’re pushing out your Hugo site, it is still possible to have other content on your site as well.) - The
--delete
flag makes sure that any files no longer existing in my local copy are removed from the online site directory as well (barring the previous--exclusion
). This guarantees the public site matches my local copy.
Permissions and PATH
#
A few quick notes here. In macOS/Linux, if you create shell script files and wish to execute them directly by simply typing their name, you must do two things:
- Set the execution bit in the permissions. For example,
chmod 755 /Users/frank/bin/blog
chmod 755 /Users/frank/bin/blogupdate
chmod 755 /Users/frank/bin/blogupload
- Add the directory where the scripts are to your
PATH
environment variable.
NOTE: All shell scripts above use full paths so that the scripts will work when run from any directory, and they assume nothing regarding the PATH
.
If, in this case, /Users/frank/bin/
is added to the PATH
environment variable, it would be possible to run these scripts simply by typing their names (e.g., simply enter blog
and hit [Retuen]
to fire up the local Hugo site).
Putting it All Together #
Now it is time to test things. At this point most of the pieces are in place. However, we still have a few tweaks we need to perform.
Run blog
#
Open Terminal/Command Prompt/Powershell, enter blog
and hit [Return]
. This should open a tab in your default browser to http://localhost:1313
and show you the local Hugo website.
But wait, it’s pretty empty. Didn’t we create an About page and one entry in our blog? Right. But we have not yet configured our Hugo site to show these. Let us leave this terminal window running as-is and we can see how changes are reflected.
Adjust Hugo config #
Open your favorite text editor (e.g., I use VSCode myself) and navigate to /Users/frank/MyHugoSite
. We will be working with files within this site to make some changes.
- Open
config/_default/menus.en.toml
. - Edit this file by either uncommenting and changing or simply adding these lines and then saving the file:
[[main]]
name = "About"
pageRef = "about"
weight = 10
[[main]]
name = "Blog"
pageRef = "blog"
weight = 20
- The moment you save this file, you might notice that your website in the browser updated, now showing two (2) links at the top:
About
andBlog
. - Click on
About
. Notice what you see. There is the information you entered! - Click on
Blog
. Nice! There is your first post! Click on it. - Hey, there is your first post! The slug in the address bar is correct. The Content is there. All good.
- Time to do some blogging. Open your Obsidian vault and navigate down to
Blog site/blog/
- RIGHT-click on
blog
and selectNew folder
from the popup. Name the foldersecond-post
- Go back to the browser and click on
Blog
near the top to go the Blog list page. Wait, nothing is there. - Oh right. We need to actually create a file to store the post’s content.
- Back in Obsidian, now create a
New note
insidesecond-post
, naming itindex
. - Using our predefined hotkey from earlier, apply the
Blog Post
template to this file. Look at that! Everything popped into place. - Modify the information on that page. Set the
title
to “Second Post” and replace “CONTENT HERE” with “Now we are cooking with gas!” - Click on the icon in the left ribbon bar that we created earlier. This should fire off
blogupdate
for us. - Immediately you should see the
/blog
page of your site reload, now showing the new post!
From here the sky is the limit.
Next Steps #
At this point take some time to play with the setup. I intentionally did not delve into either customizing Obsidian nor the Hugo theme because frankly that would take far too long. Besides, everyone needs to play with things for themselves and make it their own.
A few suggestions:
Go through Obsidian’s settings #
Note that there are ways to make things work even easier, depending on your own use case. For example, if you mostly use this setup to do blogging, you might want to adjust the Files and links/Default location for new attachments
to Same folder as current file
. This way when you drag/drop an image into your blog post, Obsidian not only copies the file to the same directory as your current blog post, but it also sets the Markdown image link to it right away.
Go through the Hugo Blowfish config files #
There is a wealth of information stored in those TOML files.
Read the Blowfish docs #
What you cannot find in the config files you should find in the docs. I will not lie. The docs, while extensive, are not the most intuitively setup. I went through it quite a bit to find what I needed. That said, in the end I was able to do everything that I wanted to do and then some.
Final Thoughts #
This blog post is not intended as an end-all, be-all setup. This simply covered the basics. And this setup is a bit simpler than my final site shows. But everything you see on my site can be done with this setup… and likely more.
Also, please note that this is all still a work in progress. At the moment this works for me, though I am sure I will be making adjustments over time. But it should be enough to get anyone else who might be interested in doing the same to get up and running just a little bit quicker. I hope that someone finds this useful.
-
As I noted in my previous post, along with other things I actually have my Obsidian vault and the directory that I keep my shell scripts in synchronized between different systems using Syncthing. But for demonstrations purposes I simplified things as most folks likely only use one computer anyway. ↩︎ ↩︎