Auto-updating software: Diving into oh-my-zsh

Published: October 12, 2016

Tags:

Recently I’ve been working on a little side project called pngarbage. It’s a command line tool for scanning webpages and identifying image bloat. The tool is written in Go which allows me to distribute a single binary with no dependencies. I’m just in the infancy of the project and plan on (ok…hope to be) adding a bunch of new features. With that in mind, one thing I’ve been thinking about recently is auto-updating strategies.

I spent some time thinking about tools I use daily that implement auto-updating. The first one that came to mind is oh-my-zsh, one of the most starred repos on Github. If you’ve used it before you’re probably pretty familiar with this screen…

oh-my-zsh auto-update prompt

I spent a bit of time reviewing how oh-my-zsh goes about auto-updating and thought it would be worthwhile to do a short write up of my findings.

How It Works

oh-my-zsh works by source-ing ~/.oh-my-zsh/oh-my-zsh.sh in ~/.zshrc.

export ZSH=$HOME/.oh-my-zsh
source $ZSH/oh-my-zsh.sh

If you look at that file you’ll see that the first thing it does is run ~/.oh-my-zsh/tools/check_for_upgrade.sh (unless you’ve disabled auto updating).

# Check for updates on initial load...
if [ "$DISABLE_AUTO_UPDATE" != "true" ]; then
  env ZSH=$ZSH DISABLE_UPDATE_PROMPT=$DISABLE_UPDATE_PROMPT zsh -f $ZSH/tools/check_for_upgrade.sh
fi

check_for_upgrade.sh checks for the presence of a file called ~/.zsh-update. If it is not there (e.g. the first time you launch a shell after installing oh-my-zsh) it executes _update_zsh_update.

if [ -f ~/.zsh-update ]
then
  # Code to execute if ~/.zsh-update is present
else
  # create the zsh file
  _update_zsh_update
fi

_update_zsh_update simply writes the current date (represented as the number of days since the epoch to the ~/.zsh-update file.

function _update_zsh_update() {
  echo "LAST_EPOCH=$(_current_epoch)" >! ~/.zsh-update
}

If ~/.zsh-update does exist it will be sourced which will set LAST_EPOCH as an environment variable.

if [ -f ~/.zsh-update ]
then
  . ~/.zsh-update
  # Rest of the code to execute
else
  # Code to execute if ~/.zsh-update isn't present
fi

It then gets the current epoch and checks if the difference in days between the last epoch is greater than $epoch_target.

epoch_diff=$(($(_current_epoch) - $LAST_EPOCH))
if [ $epoch_diff -gt $epoch_target ]
  # Oh boy, updating time
fi

$epoch_target either comes from the UPDATE_ZSH_DAYS environment variable (intended to be set in ~/.zshrc) or defaults to 13.

epoch_target=$UPDATE_ZSH_DAYS
if [[ -z "$epoch_target" ]]; then
  # Default to old behavior
  epoch_target=13
fi

If it’s time for an update the next condition it checks is whether or not it should prompt you. This is, again, done by checking an environment variable (DISABLE_UPDATE_PROMPT).

if [ "$DISABLE_UPDATE_PROMPT" = "true" ]
then
  # No need to prompt
else
  # Need to prompt
fi

If the prompt is disabled or you answer the prompt with “Y” it goes ahead and auto-updates which ultimately executes a git pull in ~/.oh-my-zsh/tools/upgrade.sh.

git pull --rebase --stat origin master

It then updates ~/.zsh-update to note the latest date the update happened.

If the update prompt shows up and you dismiss it with “n” it does not execute the auto-update, but still updates ~/.zsh-update with the current date, which will defer the prompt until $epoch_target has elapsed again.

if [ "$DISABLE_UPDATE_PROMPT" = "true" ]
then
  _upgrade_zsh
else
  echo "[Oh My Zsh] Would you like to check for updates? [Y/n]: \c"
  read line
  if [[ "$line" == Y* ]] || [[ "$line" == y* ]] || [ -z "$line" ]; then
    _upgrade_zsh
  else
    _update_zsh_update
  fi
fi

Default Behavior

By default, oh-my-zsh behaves like this…

My Thoughts

I chose to investigate oh-my-zsh because I feel like they got auto-updating right. Here’s what I like about it…

I’m looking to implement a similar auto-update strategy for pngarbage.

Conclusion

If you have any comments on auto-updating, feel free to drop a note comments below. Of course, as always, you can reach me on Twitter as well.

Max ChadwickHi, I'm Max!

I'm a software developer who mainly works in PHP, but also dabbles in Ruby and Go. Technical topics that interest me are monitoring, security and performance.

During the day I solve challenging technical problems at Something Digital where I mainly work with the Magento platform. I also blog about tech, work on open source and hunt for bugs.

If you'd like to get in touch with me the best way is on Twitter.