Making your Go CLI binaries self-updatable

A futuristic laptop showing some code which updates itself

At One2N we created Cloudlens. It is a Terminal User Interface (TUI) for accessing important resources of AWS. We recently made cloudlens self-updatable. i.e., with cloudlens update, you can easily update cloudlens to the latest version. This blog post covers how you can make your own Go binaries self-updatable.

Why make binaries self-updatable, really?

If you have worked on building Go CLIs, managing their version becomes problematic. How do you ensure your users are always running the latest binary? How do they even find out that there’s a new updated version of the binary?

You can solve this by making your binaries self-updatable. No need to depend on brew , apt-get, yum, or any other package managers. You can just run ./<your-app> update to update the app to the latest version.

This helps users update to the latest version of software easily. As part of the update process, you can even show release notes and other important info to your users.

Apart from user experience, this also becomes easy when you have a popular app with wide distributions such as different OSes (Ubuntu, RHEL, Mac, Windows) or CPU architectures (Intel, AMD, AWS Graviton). Creating OS-managed updates for your app (apt-get, deb package, .exe installer) can become too much work. We can use the self-update mechanism to handle the “Update” part of the process regardless of OS or CPU architecture.

What does the user experience for self-updatable binary look like?

As part of the normal CLI usage, you can find out whether the user is running the latest version. If not, then display some warning message to remind user to update.

This way, user knows they are running older version and can update the CLI with update option.

User can then run the ./<your-app> update to see something like this.

How to make binaries self-updatable?

We can break this down into two parts:

  1. Normal execution flow
    • Find out the current version of your local binary
    • Get the latest binary version info from the server
    • Compare versions and inform the user if they are running an outdated version
  2. Update execution flow
    • Download the latest binary for your OS & architecture
    • Replace the local binary with newer binary
    • Show release notes

As a developer, we use a lot of Command Line Interface (CLI) apps in our daily life. Some quick examples can be GitHub CLI, K9s, or even VIM. CLI apps provide a fast experience and increase productivity by eliminating the number of clicks. However, updating these apps can be challenging as these apps are most often self-contained binaries downloaded on users’ machines, and we have little control over that. Having developed many such CLI apps in Go before, we understand this problem, and in this blog post, we share an approach to solve it.

If you’ve built and distributed the app as a downloadable binary, asking the user to repeat the same process for a newer version is tedious and harms the user experience. We suggest you make your application “self-updatable”. This way, the application can check if any new releases are available and hint to the user that it can update the app whenever it wishes. Then the user can pass an additional CLI flag such as -u where the app will update itself correctly and let the user know. This provides a very smooth experience to the user as well.

Step 1: Get the Latest Release Information
view in text form
Step 2: Form an Executable Name

We will create an executable name based on the machine’s OS, ARCH and the latest tag name.

view in text form
Note: Formation of this executable name is completely depends on Github released assets.
Step 3: Lookup Executable in the Latest Release Information

Next, we need to find the executable in the latest release assets that matches the executable name we formed in Step 2. We will also get the corresponding asset ID and format (zip or tar).

view in text form
Step 4: Download the Latest Version

Using the asset ID in Step 3, we will download the latest version of the executable from GitHub.

view in text form
Note: The artifact downloaded from Github is a zip file.
Step 5: Extract the executable based on the format.

Using the AssetFormat(Zip or Tar) in Step 3, we will extract the executable using archive/zip and archive/tar libraries.

view in text form
Step 6: Apply the Self-Update

Now that we have the latest binary, we’ll use the github.com/minio/selfupdate library to apply the self-update.

view in text form

Reference to complete working example: https://github.com/one2nc/cloudlens/blob/main/cmd/update.go

Sitaram Shelke
Sitaram Shelke