Skip to main content
Version: 1.7

The install command

Run dispat install https://github.com/owner/repo to install a tool published as a GitHub release asset. You do not need a config file or a git repository. This command installs somebody else's binary the way self-update installs dispat's own, so a CI job that already has dispat has a release downloader as well.

dispat reads the repository's releases, picks the highest stable version, downloads the file you named, verifies the published size and checksum, and only then moves it into place. Anything already at the destination is kept beside it as <name>.backup and removed during a later run a week afterwards. Because nothing moves until every check passes, a failed download leaves the folder exactly as it found it.

$ dispat install https://github.com/acme/tool --asset 'tool-{os}-{arch}'
downloading tool-linux-amd64 (8.4 MiB) from acme/tool
installed tool 1.4.0 at /usr/local/bin/tool

Naming the repository

Name the repository however it is already at hand. dispat accepts the page URL, a page inside it, the clone URL, the SSH remote, and the owner/repo shorthand:

dispat install https://github.com/acme/tool
dispat install https://github.com/acme/tool/releases/tag/v1.4.0
dispat install git@github.com:acme/tool.git
dispat install acme/tool

A host that is not github.com is treated as a GitHub Enterprise install, and dispat derives its API endpoint as https://<host>/api/v3. Pass --api-url when your install serves the API from somewhere else.

dispat sends the conventional GITHUB_TOKEN only to github.com, because the host here comes from an argument rather than from a flag you set deliberately. To authenticate against another endpoint, name the variable yourself with --token-env.

A private repository

A private repository needs a token for everything: to read the releases, and to download the asset. dispat uses the same credential for both. When a token is present, the download goes to the asset's own API endpoint rather than to the public browser URL, because that endpoint is the only address that serves a private repository's file.

GITHUB_TOKEN=... dispat install acme/tool --asset 'tool-{os}-{arch}'

The token needs read access to the repository's contents. Give it no more than that: a token scoped to one private repository is enough to install from it.

Without a token, a private repository's public download URL answers with a sign-in page under a 200, which dispat reports as a size or checksum mismatch rather than as a refusal. If an install fails that way against a repository you know exists, the missing token is the likely reason.

The credential never leaves the API host. That endpoint redirects to object storage, and the redirect is followed without the Authorization header, because the storage host is a different host and has no business seeing it.

For a GitHub Enterprise install, name the variable explicitly. The endpoint there is derived from the repository you typed rather than set by a flag, so GITHUB_TOKEN alone is deliberately not sent to it:

DISPAT_TOKEN=... dispat install https://ghe.example.com/acme/tool --token-env DISPAT_TOKEN

Choosing the file

Most releases attach more than one file, and installing the wrong one globally is worse than typing its name. Use --asset to say which one you want. dispat expands {os}, {arch}, {version}, {tag} and {name} in the value, so one invocation keeps working as releases come and go:

dispat install acme/tool --asset 'tool-{os}-{arch}'
dispat install cli/cli --asset 'gh_{version}_{os}_{arch}.tar.gz'

The value also matches as a glob, which reaches an asset whose exact spelling nobody wants to write out:

dispat install acme/tool --asset '*linux-amd64'

An exact name always wins over a glob, and a pattern matching two files is refused with both listed.

Without --asset, dispat looks for the name most projects publish under: {name}-{os}-{arch}, the repository's own name and the platform, with .exe appended on Windows. That is the convention dispat's own releases follow, so installing dispat, or anything released the same way, needs no flag:

dispat install acme/tool # installs tool-linux-amd64

The default is matched exactly and never as a glob, so what a bare invocation installs is decided by the release rather than by which of several near-misses came first. A release carrying exactly one file needs no --asset either. Anything else is refused with the name dispat looked for and the files the release does carry, so the next invocation can name one.

Choosing the destination

--bin-dir says which folder the tool goes into, and --as says what it is called there. Without them, dispat installs into $DISPAT_BIN_DIR, then /usr/local/bin when that folder accepts a file, then ~/.local/bin, which is the same ladder the install script climbs. The name defaults to the repository's own, and on Windows a name with no extension gains .exe.

dispat creates the folder if it does not exist, and tells you when it is not on your PATH, because a tool your shell cannot find is a successful install that looks like a failed one.

Assets that are not binaries

Many projects ship their binary inside an archive, and some ship an install script. Use --pipe to hand the verified file to a command's standard input instead of installing it. The command runs in --bin-dir, so whatever it writes lands where a binary would have:

dispat install cli/cli --asset 'gh_{version}_{os}_{arch}.tar.gz' \
--pipe 'tar -xz --strip-components=2 --wildcards "*/bin/gh"'

dispat install acme/tool --asset install.sh --pipe sh

The verification is the same either way, so what reaches your command has already been checked against the size and the checksum the release published. dispat stages the file in a folder of its own and removes it afterwards, so a command that fails leaves nothing behind on your PATH. $DISPAT_ASSET names the same file by path, under the name the release published, for a command that has to seek rather than read a stream; $DISPAT_ASSET_NAME is that name on its own.

Running it more than once

dispat install is idempotent. It hashes the file already at the destination against the checksum the release published, so running the same line again costs no transfer and says so:

$ dispat install acme/tool --asset 'tool-{os}-{arch}'
tool at /usr/local/bin/tool is already v1.4.0
install it again anyway with --force

Use --check as a provisioning gate, because it changes nothing on disk and exits 1 when the destination does not already hold that exact file. Use --force to install anyway, which is how a damaged or tampered file is repaired. A release that publishes no checksum cannot be compared, and dispat says so and installs, rather than guessing that your machine is up to date.

Install manifests as shell scripts

The list of tools a machine needs is a shell script: one dispat install line per tool, run in order. No line takes a lock, reads a config file or touches a git repository, so the lines are independent of each other and of wherever the script runs.

#!/bin/sh
set -e

dispat install acme/tool --release 1.4.0 --asset 'tool-{os}-{arch}'
dispat install jqlang/jq --release 1.7.1 --asset 'jq-{os}-{arch}'
dispat install cli/cli --release 2.62.0 --asset 'gh_{version}_{os}_{arch}.tar.gz' --pipe 'tar -xz'

Pin every line with --release. A manifest whose versions float installs something different on each machine it runs on, which is the outcome a manifest exists to prevent.

set -e belongs at the top, because every line either does its work or stops the file. Running the manifest again costs no transfer, since each line compares what is already installed against the checksum the release published, so a provisioning script may run on every boot. A mistake in the command line exits 2 before any request is made, and a flag belonging to another dispat command is one of those mistakes:

$ dispat install acme/tool --tag 1.4.0
ERR --tag is not an install flag; it belongs to dispat commit; pin a version with --release command=install flag=--tag

Keep --check out of a manifest that runs under set -e. It exits 1 whenever the destination does not already hold that exact file, which is what makes it a gate and what would make it stop the script at the first tool that has something to install. Ask it in a CI gate of its own, and let the manifest install.

Versions and tags

--release <version> installs one named version, downgrades included. --prerelease considers the prereleases too, though ordering still decides, so a released 1.2.0 wins over 1.2.0-rc.1.

--tag-prefix says what the repository writes before the version in its tags. It defaults to v, which covers nearly every project. Pass an empty value for a repository tagging 1.2.3, or the module path for a monorepo publishing one release per package:

dispat install acme/tool --tag-prefix ''
dispat install yohimik/dispat --tag-prefix 'services/dispat/v' --asset 'dispat-{os}-{arch}'

Undoing it

Run with --rollback to restore the binary the last download replaced, without downloading anything. It rotates the two files rather than overwriting them, so the binary you replace becomes the new backup and a second --rollback returns you to where you started.

A rollback reads no releases, so it needs only to know which tool: name the repository as usual, or name the file with --as.

$ dispat install acme/tool --rollback
rolled back tool at /usr/local/bin/tool
the binary it replaced is now the backup, so another --rollback returns to it

Flags

These flags apply alongside the global flags:

--asset

Which of the release's files to install, by name or glob. {os}, {arch}, {version}, {tag} and {name} are expanded. Without it, dispat takes the release's {name}-{os}-{arch} file, .exe included on Windows, and a release carrying exactly one file needs none either way. See Choosing the file.

--bin-dir

The folder to install into. Without it, $DISPAT_BIN_DIR, then /usr/local/bin when it is writable, then ~/.local/bin.

--as

The default is the repository name. What to call the installed tool. It takes a file name, not a path.

--pipe

Hand the verified file to this command's standard input instead of installing it. The command runs in --bin-dir, with $DISPAT_ASSET and $DISPAT_ASSET_NAME set.

--tag-prefix

The default is v. What a release tag carries before its version. An empty value considers every tag whose whole name is a version.

--release

The default is the highest stable version. Install exactly this version, including downgrades. A leading v is fine.

--prerelease

Consider prereleases too. Standard ordering still decides, so a released 1.2.0 wins over 1.2.0-rc.1.

--check

Report only, change nothing, and exit 1 when the destination does not already hold that exact file. With --pipe, there is no destination to compare, so this always exits 1.

--force

Install even when the destination already carries that file, which repairs a damaged or tampered binary.

--rollback

Restore the binary the last download replaced, without downloading anything. This refuses to run alongside the flags that choose something to download, but it combines with --check.

--api-url, --token-env

The default is derived. Point dispat at another API endpoint, and name the variable a token is read from. GITHUB_TOKEN is sent to github.com alone. The token reads the releases and, for a private repository, downloads the asset as well. See A private repository.