Shell Scripts are Awesome

I don’t know about you, but I love writing code that automates my work. I finally had the chance to do just that, and now I’m the proud owner of a shell script that builds and deploys my Hugo site without any extra help from me - and it even works with my git submodule setup!

So why do this? Well, when I began working with Hugo, I ran across a rudimentary deploy script in their documentation. Since I was still familiarizing myself with Hugo, it hadn’t occurred to me yet to automate the build process, and I immediately fell in love with the idea. However, I could see the script would need major improvements to do what I wanted, and I was completely new to shell.

At first, I struggled a bit with writing shell code (especially keeping things POSIX-compliant for portability!), but I stumbled upon the shellscript.sh tutorials and dove straight in. Thanks to those fantastic tutorials, I now have a sweet script to show off and perhaps even a newfound love for the shell. Apart from variable expansion struggles, it’s really quite elegant!

I find my hugo-deploy.sh script does help keep builds following a standard process, but I think I’ll really see it shine when I get around to scheduling automated builds on a remote server. Exciting!

full script at end

Deploy Script Functionality

Be sure to place this script in your site’s root directory if you intend to use it. It’s built with my git submodule setup in mind. But I don’t think it would be too hard to modify it for something different.

Order of Operations

From start to finish, here’s what the script does.

  1. Update the automatic build number (included in the git commit)
  2. Check for any user options (see below)
  3. Check if your active repo branches match your intended deploy branches
    • Production and development branches can both be set
  4. Check if your local repo is up to date (no merge conflicts allowed!)
  5. (Optional) Clear out the site output folder before the Hugo build
  6. Run Hugo build command
  7. Git add, commit, and push to your specified remotes

If any point in this process fails, the script exits with an error message detailing where the issue occurred. You’ll have to fix the issue before running the script again. Thankfully, most of the issues are quick fixes. If you have to do a merge, that’s on you!

‘Settings’ Variables

There are some ‘settings’ variables at the top of the script that you may need to modify to match your project.

# BUILD/DEPLOY SETTINGS - edit as needed for your use case

PUB_SUBMODULE="public"         # name of output folder where git submodule is located
IGNORE_FILES=". .. .git CNAME" # space-delimited array of files to protect when 'fresh' option is used
DEV_BRANCHES="dev dev"         # development branches to build on and push to, 1-root, 2-pubmodule
PROD_BRANCHES="master master"  # production branches to build on and push to, 1-root, 2-pubmodule

PUB_SUBMODULE holds the name of the folder where your public site submodule is located. The default output folder is public, which is where I have my submodule for the live site. If you’ve changed your output folder in the site config, you’ll need to update this to match it. The variable value is relative to the root directory, so if you’ve buried your output folder then set the path accordingly (e.g. my/public/build).

IGNORE_FILES is used with the ‘fresh’ option (see below). It acts as an array of filenames that will not be deleted from the output folder if you select the ‘fresh’ option. It’s mostly to protect non-Hugo files.

PROD_BRANCHES are the two production branches on which you intend to build and push your site data. The first branch name matches the preferred deploy branch for your site source data (in your root directory), and the second branch name matches the preferred deploy branch for your live site (the supposed submodule). These are the values the script uses to check your active branches in step 3 above. It’s a nice safety check.

DEV_BRANCHES works the same way for a development build, which is enabled through the user options.

User Options

Sometimes you need a little customization! That’s where my included script user options come into play. Hopefully these options can help you out a bit.

# Script Options
./hugo-deploy.sh [-d|-f] [ -m "COMMIT_MESSAGE" ] [ -o "HUGO_OPTIONS" ]
  • -d ‘dev’ => deploys to development branches set in DEV_BRANCHES list (default is PROD_BRANCHES)
  • -f ‘fresh’ => deletes public directory data before rebuild (skips files in IGNORE_FILES list)
  • -m ‘message’ => appends a custom commit message to the auto-build string, works exactly like git -m
  • -o ‘options’ (for Hugo build) => includes Hugo build options during deploy process (default none), all hugo build options are valid
  • -h => ‘help’
# Usage Example
./hugo-deploy.sh -d -f -m "Deploying like a rockstar!" -o "--cleanDestinationDir"

Fun Stuff: If you rename the script file to hugo-deploy.command, you can run the script with a double click! I haven’t actually tested this, but I think it just runs without options. Try it out!

Disclaimers / Seeking Shell Wisdom

I ran into some issues with this script that I couldn’t totally avoid. Please be aware of them if you try out my script.

  • Variable expansion - I’m aware that the variables I use in my various git commands are not double-quoted. (And just in case I didn’t notice, ShellCheck yelled about them in bright colors.) However, adding quotes or modifying the expansion caused the commands to be incorrectly formatted and resulted in failure. They work as they are now, but I’m curious to know if there’s a way to improve this. I’d love to get advice from experienced shell coders.

  • Limited Testing - I work on Mac OS, so I know this script works on that system - at least on my machine! I have no idea about Windows or Linux. If you try it out, please let me know!

The Full Script

View on Github