To get started, you will first need to sign up for an Equinox account.
Equinox helps you sign, package and distribute self-updating Go programs. Equinox is made up of three parts:
go build
The release tool is a small wrapper around go build
that
cross-compiles your app and digitally signs each binary before
uploading it to Equinox. The service packages your app into archives
and native installers for each target platform. All of these
packages are hosted for you with stable URLs.
Equinox automatically generates user-friendly
download pages
for each app.
Once you release an app via Equinox, you can enable self-updating to the latest version with a few lines of code that call the Equinox SDK.
First, download the equinox release tool.
After you extract it, you may want to move it into your $PATH
.
(/usr/local/bin
is a good choice)
The release tool is distributed with Equinox itself. Soon you'll have a download page just like this one for your own application!
The release tool will cross-compile your application with go build
then sign and upload each executable to Equinox
to be packaged and hosted for download.
There are a number of required inputs you must specify to create a release:
$ equinox release --help
OPTIONS:
--app publish release for this equinox app id
--channel "stable" publish release to this channel
--platforms platform list to build. e.g. 'linux_amd64 darwin_386'
--signing-key path to the ecdsa private key for signing releases
--token an equinox credential token
--version version string of the new release
The version
argument is the version string of your application.
And platforms
is a space separated list of platforms to build for.
We don't have app
, token
, and signing-key
yet
but we'll create them in the next sections.
All apps distributed via equinox must be signed with a private key. Cryptographically signing your releases with a private key allows untrusted third parties to distribute your updated code while providing end-to-end guarantees that updates come only from you, the developer, and no one else.
The Equinox release tool makes it easy to generate a public/private key pair. Make sure to save your private key securely, you'll need to sign all of your future releases with it. We'll include the public key in your app later to verify updates when we add the update code. Never give your private key to anyone.
$ equinox genkey
Private key file /Users/inconshreveable/equinox.key
Public key file /Users/inconshreveable/equinox.pub
On your account dashboard, the first thing you'll need to do is create a new app. In the Apps section, enter the name of your app and click New App. This generates the app ID we need to pass to the release tool.
Next on the dashboard, we need to create a credential token. This credential authenticates and authorizes the release tool to upload builds to your account.
In the Credential Tokens section, click New Credential to create a new credential. Equinox does not store your credential tokens for security reasons. Copy it and store it somewhere securely.
Now that we've generated a signing key and created a credential and an application, we're ready to invoke the release tool. The release tool will cross-compile your application, sign and then upload each executable to Equinox to be packaged and hosted for download.
The release tool builds your application by invoking go build
for
each target platform. The positional arguments you pass to the tool are passed
to go build
. More docs are in the release command
section.
The platforms
option is a space separated list of target platforms in the form
of $GOOS_$GOARCH
to compile for. If you are using Go 1.4 or earlier,
you must bootstrap the cross-compilation yourself. More docs are in the
cross compilation section.
This is an example invocation of the equinox release tool to build the application
github.com/example/tool
. You'll need to substitute in your own
values for app
, token
, signing-key
and
obviously the name of your application.
$ equinox release \
--version="1.0.0" \
--platforms="darwin_amd64 linux_amd64" \
--signing-key=/Users/inconshreveable/equinox.key \
--app="app_ja6WuaZgwsF" \
--token="4kj5ypgZj3QeGvGz7LckDGvcAcdmUozUNU8YhVhEg97r7dcmFgy" \
github.com/example/tool
Congratulations! Your first release is live and available to download.
Now that you've published your first release to equinox, we're ready to add the
code that will enable your app to update itself to the latest version.
Create a new function equinoxUpdate()
in your application:
package main
import (
"fmt"
"github.com/equinox-io/equinox"
)
// assigned when creating a new application in the dashboard
const appID = "app_ja6WuaZgwsF"
// public portion of signing key generated by `equinox genkey`
var publicKey = []byte(`
-----BEGIN ECDSA PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE62fHuaSP1asZmQ8ikYysNB2VxKd8cV5i
G6WZ7PPD9DKAGoebHzm1D/uT2VEWxtgoZvEkbyhGgBbU5z/aDwIa7YNAIZrrRakV
PTvTEyFY2QbNu96tBlVM78N7Rq2HI8nN
-----END ECDSA PUBLIC KEY-----
`)
func equinoxUpdate() error {
var opts equinox.Options
if err := opts.SetPublicKeyPEM(publicKey); err != nil {
return err
}
// check for the update
resp, err := equinox.Check(appID, opts)
switch {
case err == equinox.NotAvailableErr:
fmt.Println("No update available, already at the latest version!")
return nil
case err != nil:
fmt.Println("Update failed:", err)
return err
}
// fetch the update and apply it
err = resp.Apply()
if err != nil {
return err
}
fmt.Printf("Updated to new version: %s!\n", resp.ReleaseVersion)
return nil
}
You'll need to substitute in your own value for the appID
constant.
You must also substitute the contents of the public key file generated by
the earlier equinox genkey
command for the value of publicKey
.
Lastly, you'll need to modify your program code to call the equinoxUpdate()
function somewhere! How to do this is app-specific, but most folks like to
create an update
command, so that a user can run the update process explicitly.
If you just want to test out Equinox with an example program,
create a simple main()
function:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) == 2 && os.Args[1] == "update" {
equinoxUpdate()
}
fmt.Println("Hello example!")
}
Don't forget to create a new release with the updating code
by running the release process again. Notice that we increment the
version string to 1.0.1
.
$ equinox release \
--version="1.0.1" \
--platforms="darwin_amd64 linux_amd64" \
--signing-key=/Users/inconshreveable/equinox.key \
--app="app_ja6WuaZgwsF" \
--token="4kj5ypgZj3QeGvGz7LckDGvcAcdmUozUNU8YhVhEg97r7dcmFgy" \
github.com/example/tool
First, let's verify that our downloads page works. Go back to your dashboard and click on your app:
On your app page, you should see that the latest version you released was
1.0.1
. It will also show a link to the public download page
for your app. Click on it to check out your download page:
Download the released version of your app and then run the update functionality. For the example program we created above, it would look like this:
inconshreveable [~] ♡ : ./example update
No update available, already at the latest version!
Great! But we want to see some updating happen. Let's modify our program just a little so we can make sure that the update works as we expect:
- fmt.Println("Hello example!")
+ fmt.Println("Hello updated example!")
It's time to build and release the application again. Run equinox release
with a new version string. Following the example, it will be --version="1.0.2"
this time.
Finally, now that a new version is released, we can run the whole update process!
$ ./example
Hello example!
$ ./example update
Updated to new version: 1.0.2!
$ ./example
Hello updated example!
An app is a just a Go program which you want to distribute and manage updates for. Apps are independent and you can manage multiple apps with a single Equinox account.
A release is the set of packages for all platforms of a specific version of your app. Releases correspond one-to-one with versions of your app (not always - there may be versions that you choose not to release). In fact, the version string you specify when creating a release must be unique for that app.
When a release is published it is immutable. You may not add, remove, or change the uploaded binaries. You may, however, revoke a release, which will make it unavailable for any further downloads or updates.
When you upload a release to Equinox, you don't associate it with an app. Instead
your app has release channels. When you create a release, you
choose on which channel that release should be published. This allows you to create
a beta
channel where you can push new releases of experimental code and
a stable
channel for more production-ready releases.
Releases can be published to more than one channel. This can enable workflows where a release is promoted from one channel to another. For example, some projects use a workflow where, after enough time and testing, releases on a beta channel are then published to a stable channel.
The release tool wraps go build
. It compiles your application for
each target platform and digitally signs each binary before uploading to
Equinox.
You can download the latest version of the release tool from here. After you extract
it, you may want to move it into your $PATH
(/usr/local/bin
is a good choice). If you already have the tool installed, make sure you have the
most recent version by running equinox update
.
For each platform, the release tool takes the following steps:
go build
with the supplied command-line optionsIf your machine has multiple cores, platforms are built in parallel.
Let's see how this works in practice. Let's say you normally build your application with the following invocation of the go tool.
$ go build github.com/acme/rocket
To build and release this program with Equinox, replace go build
with
equinox release
. The Equinox-specific options are covered in the
publishing section below.
$ equinox release [options] github.com/acme/rocket
The go build
command accepts a wide range of additional build
options, such as build tags. If your build process takes advantage of these
features, you'll need to include a double-dash after the Equinox-specific
options.
$ equinox release [options] -- -tags release github.com/acme/rocket
Go has fantastic support for cross compilation. Equinox takes advantage of this support, making it easy to build for every target platform in one go.
If you're using Go 1.5+, cross compilation just works and you can skip the remainder of this section.
If you're using Go 1.4 or earlier, you need to build support for each
platform you intend to target. First, change into your $GOROOT/src
directory.
$ go env
...
GOROOT="/usr/local/go"
...
$ cd /usr/local/go/src
Here you'll find the make.bash
script to build each of the needed toolchains.
$ GOOS=windows GOARCH=amd64 ./make.bash --no-clean
$ GOOS=windows GOARCH=386 ./make.bash --no-clean
$ GOOS=linux GOARCH=amd64 ./make.bash --no-clean
$ GOOS=linux GOARCH=386 ./make.bash --no-clean
All apps distributed via Equinox must be signed with a private key. Cryptographically signing your releases with a private key allows untrusted third parties to distribute your updated code while providing end-to-end guarantees that updates come only from you, the developer, and no one else.
The Equinox release tool makes it easy to generate a public/private key pair. Make sure to save your private key securely, you'll need to sign all of your future releases with it. You'll include the public key in your app to verify updates when we add the update code.
$ equinox genkey
Private key file /Users/inconshreveable/equinox.key
Public key file /Users/inconshreveable/equinox.pub
Never give your private key to anyone.
Equinox generates ECDSA public and private keys using the P384 curve.
Specify the path to your private key with the --signing-key
option.
Alternatively you may set the environment variable EQUINOX_SIGNING_KEY
with a PEM-encoded ECDSA private key. This can be helpful when integrating equinox into
a CI environment.
Before you can publish your first release, you'll need to create an application and generate a credential token. With both of those and your private signing key, you can run the release command. This is an example release invocation:
$ equinox release \
--version="1.0.0" \
--platforms="darwin_amd64 linux_amd64" \
--signing-key=/Users/inconshreveable/equinox.key \
--app="app_ja6WuaZgwsF" \
--token="4kj5ypgZj3QeGvGz7LckDGvcAcdmUozUNU8YhVhEg97r7dcmFgy" \
github.com/acme/rocket
Equinox treats release versions (--version
) as opaque strings.
From Equinox's perspective, the newest version of your application is the most
recently published release, it has nothing to do with version strings.
The Updating section explains this in more detail.
platforms
is a comma (or space) separated list of build targets, defined as
$GOOS_$GOARCH
. A full list of supported operating systems and
architectures can be found on the Golang
website.
When the release command finishes, your release isn't immediately available. Equinox still needs to package up your binaries for release. The packagin process takes a few minutes at most.
Sometimes things go wrong (network blip, power outage, alien invasion) and the release process can fail. Equinox gracefully recovers from these interruptions: the release tool is designed to resume where it last left off.
If you encounter a failure, run the release
command again with the same arguments.
The tool will look for an existing, unpublished release. If one is found, the tool
won't upload any artifacts that successfully finished during the previous run.
The release tool can also publish a release that is already uploaded and published to another channel that it hasn't been published to. You'll get a helpful error message if you try to publish a release to channel that doesn't exist or one that it's already been published to.
By default, each application gets one channel named "stable". You can add additional channels to your application on your dashboard.
A common workflow involves having an additional beta channel. New versions are
first pushed to the beta channel for a small number of users to test out. Once
your testers confirm the release works as intended, they are published to the
stable channel. Here is an example of publishing the existing 0.1
release to the stable
channel:
$ equinox publish --token xxx --app app_xxx --channel stable --release 0.1
The release tool supports storing parameters in a configuration file so you can avoid passing them in via the command line. The configuration file has the following strucutre:
# saved in config.yaml
app: app_xxx
signing-key: ./secret.key
token: TOKEN
platforms: [
darwin_amd64, darwin_386,
linux_amd64, linux_386,
windows_amd64, windows_386
]
With the above file saved to config.yaml
, you only need to pass
three arguments to the release
command. You also no longer need
to enter your credential token on the command line.
$ equinox release --config ./config.yaml --channel stable --version 0.1 github.com/acme/rocket
The publish
command also accepts the --config
option.
$ equinox publish --config ./config.yaml --channel beta --release 0.1
Unlike web applications, getting new code to users of client or on-prem applications is a real challenge. Equinox strives to make this process seamless by allowing you to build experiences similar to the updating functionality in major web browsers that can help keep your users up to date and using the latest version of your software.
Equinox updates work by including a small SDK into your Go application. The SDK provides simple APIs to perform a check for new updates by consulting the Equinox service. If a new update is available, you can instruct the SDK to download and apply the update. The new code will run on the next invocation of the program.
The Equinox SDK is hosted on github. The SDK takes care of all the aspects of applying a safe cross-platform update including checksum and signature validation.
The SDK exposes two major APIs: Check
and Apply
. When your app calls
Check
, it will connect to the Equinox service
and ask for the most recently published release on a specified
release channel. If there is a more recent version, the call will return
information about the new version and a method to Apply
the new update. This allows you to build experiences where you
prompt the user for permission first before updating.
If you choose to apply the update, the SDK will fetch either the new version of the program or a suitable binary patch, download it, apply it, verify it and then replace the current executable contents with the newly updated code.
How does Equinox determine whether a client program is at the latest version? The following rules explain how the update check process works to find the latest version of a client program on a release channel.
By default the Check
API will look for the most recent version.
You can also configure it to look for a specific version instead of the lastest.
Updates can fail for many reasons. The most common cause of update failures is insufficient file system permissions to apply the update. For an update to be applied succesfully, the running code must have the following permissions:
These requirements are most often not satisfied when the binary is
copied to a privileged system directory like /usr/bin
but invoked by the user without root privileges. The equinox SDK
checks this for you and will return a helpful error message if that's
the case.
Of course, updates may still fail because of network problems, system resource exhaustion and other sources. If an error is returned by applying the update, that means the entire process failed and the old code is still there and running. Your program will never 'partially update'.
There is one pathological case where the update may fail in a way that the old binary is renamed but the new binary is not installed. This almost never happens in practice, and if it does, Equinox will return a helpful error message for the user. See this documentation for more details.
All possible updates will be computed as binary patches by Equinox. You don't have to enable anything! It's all handled automatically and greatly improves update speed. These binary patches often reduce the download size to just a few hundred kilobytes instead of the 10+ MB of a new Go binary.
Binary patches can only be computed if the updating program was previously published in an Equinox release. If it was not, Equinox will handle the update by returning a full copy of the latest program.
Please consult the relevant section of the getting started section and the API documentation for the Equinox SDK.
After the release tool uploads the binaries for a release, Equinox packages them
before it publishes the release. These packages are archives or native installers that make
it easy for users to start working with your software. Equinox currently packages
your binaries into zip
and tar.gz
archives for each platform.
The filename of your binary when it is packaged into an archive or installer is the app slug that you can configure on your Equinox dashboard.
Equinox packages your binaries into zip and tgz archives for all platforms.
These archives are configured so that the binary will extract into the current working
directory and will have the proper executable permission mode bits (0755
) set.
Equinox can automatically build .pkg
installers for all OS X (darwin) artifacts
in a release. The packages install your application into /usr/local/bin/
on the target system.
A known bug is that OS X .pkg
installers require root privileges to install.
The installed binary will be owned by root, it can still be successfully updated without root privileges.
OS X .pkg
installers are only available on the Business plan.
.pkg
installers are not signed.
When a .pkg
is not signed, OS X's Gatekeeper will refuse to install the application without
user opt-in and display a strong warning message discouraging installation.
Equinox can automatically publish OS X releases to a custom Homebrew tap. Once a user
"taps" your repository, they can install the application using the brew
command.
$ brew tap eqnxio/{account-slug}
$ brew install {app}
Users can also install the application directly without tapping the repository first.
$ brew install eqnxio/{account-slug}/{app}
Homebrew support is only available on the business plan.
Equinox can automatically build .msi
installers for all Windows binary artifacts in a release.
These are per-user installers (not machine-wide) so they do not require Administrator
privileges.
Your application will be installed into the user's local programs directory
(e.g. C:\Users\name\AppData\Local\Programs\yourapp
). The user's PATH environment variable
will be updated to find your application. An uninstaller shortcut will be placed in
the start menu.
Windows .msi
installers are only available on the Business plan.
.msi
installers are not signed. When a .msi
installer is not signed,
Windows may present a strong warning or fail to install without explicit user opt-in.
Equinox can automatically build .deb
installers for all Linux binary artifacts in a release.
The .deb
packages install your application into /usr/local/bin/
on the target system.
The installed files will be owned by the installing user (usually root), which means that only the root user
will be able to initiate an update.
Linux .deb
installers are only available on the Business plan.
Equinox can automatically build .rpm
installers for all Linux binary artifacts in a release.
The .rpm
packages install your application into /usr/local/bin/
on the target system.
The installed files will be owned by the installing user (usually root), which means that only the root user
will be able to initiate an update.
Linux .rpm
installers are only available on the Business plan.