A small Go utility that keeps locally-installed CLI binaries up-to-date with their GitHub releases. Tools are declared in a YAML file — global install directory, optional GitHub token, and per-tool entries with the repo, version-check command, and asset filename.
CLI is built on cobra, so --help, shell completions, and standard subcommand discovery work out of the box.
- status — shows installed vs available version for every managed tool at a glance, queried in parallel.
- Version detection — runs a configurable shell command; always checks the managed binary by absolute path, not whatever happens to be first on
$PATH. - Version pinning — lock a tool to a specific release, including pre-releases; binup reinstalls if the binary drifts.
- Flexible asset resolution — template (
{os},{arch},{version},{tag}) or explicit per-platform map;archive_pathsupports the same placeholders. - External CDN support —
download_urlfield for tools whose binaries are not attached to GitHub releases (e.g. Helm). - Archive handling — extracts
.tar.gz/.tgz/.zip; binary matched by basename so subdirectory paths inside archives (e.g.linux-amd64/helm) are handled automatically; raw binaries installed as-is. - Atomic install — writes to a temp file then renames, preserving
0755. - Configurable temp dir — downloads land in a unique subdirectory of
tmp_dir, cleaned up on success or failure. - GitHub token — set via
gh_tokenin config orGITHUB_TOKENenv var; env takes precedence.
make build # builds ./binup, version derived from git tag
make build VERSION=0.1.0
make clean
The Makefile uses -trimpath, -ldflags="-s -w", and CGO_ENABLED=0 for a slim, static binary.
Requires Go 1.21+. Dependencies are pulled in automatically via go mod.
binup status # show installed vs available version for all tools
binup status rtk helm # same, filtered to named tools
binup update # update every tool in the config
binup update rtk helm # update only the named tools
binup update --dry-run # show what would change, download nothing
binup list # show bin_dir and configured tools
binup version # print binup version
binup --version # same (cobra built-in flag)
binup -c path/to/config.yaml ... # use a non-default config file
binup completion bash # generate shell completions (bash/zsh/fish/powershell)
binup help [command] # detailed help for any command
binup update exits 0 when every selected tool succeeded, 1 if any failed (others are still processed).
binup looks for binup.yaml in the same directory as the binary. Copy binup_sample.yaml there as a starting point. Override the path with -c.
bin_dir: ~/bin
tmp_dir: /tmp
gh_token: ghp_xxxx # optional; GITHUB_TOKEN env var takes precedence
tools:
- name: binup
repo: https://github.com/jkandasa/binup
version_command: "binup version"
asset: "binup-{os}-{arch}.tar.gz"
- name: helm
repo: https://github.com/helm/helm
version_command: "helm version --short"
download_url: "https://get.helm.sh/helm-{tag}-{os}-{arch}.tar.gz"
- name: yq
repo: https://github.com/mikefarah/yq
version_command: "yq --version"
asset: "yq_{os}_{arch}.tar.gz"
archive_path: "yq_{os}_{arch}" # binary inside the archive is yq_linux_amd64, not yq| Field | Default | Description |
|---|---|---|
bin_dir |
— | Directory where managed binaries are installed. ~ is expanded. Required. |
tmp_dir |
/tmp |
Working directory for downloads. A unique subdirectory is created per install and removed on completion. |
gh_token |
— | GitHub personal access token. Raises the API rate limit from 60 to 5 000 req/hr. GITHUB_TOKEN env var takes precedence. |
| Field | Required | Description |
|---|---|---|
name |
yes | Logical name; used for CLI filtering and as the default binary filename. |
repo |
yes | GitHub repository URL — https://github.com/owner/repo or short owner/repo. Trailing .git and http:// are accepted. |
version |
no | Pin to a specific release e.g. 0.1.0 or v0.1.0. Empty or latest tracks the most recent release. When pinned, binup reinstalls if the local version drifts. Pre-release tags are fully supported when pinned. |
binary |
no | Output filename in bin_dir. Defaults to name. |
version_command |
yes | Shell command whose output contains the installed version. When the command starts with the bare binary name, it is automatically rewritten to the absolute managed path — so the right binary is always checked regardless of $PATH. Use {bin} for explicit control. |
version_regex |
no | Regex with one capture group to extract the version. Default: (\d+\.\d+\.\d+(?:[-+][\w.]+)?). |
asset |
— | GitHub release asset filename template. See placeholders below. |
assets |
— | Explicit GOOS/GOARCH → filename map for GitHub release assets. Used when filenames don't fit a template. Values may also use placeholders. |
download_url |
— | Direct download URL template. Use when binaries are hosted outside GitHub (e.g. Helm). Supports the same placeholders. Bypasses GitHub asset lookup entirely. |
archive_path |
no | Path of the binary inside the archive. Matched on basename, so subdirectory prefixes (e.g. linux-amd64/helm) are found automatically. Supports the same placeholders. Defaults to binary. |
One of asset, assets, or download_url is required. Resolution order: download_url → asset → assets[GOOS/GOARCH].
All four placeholders work in asset, assets values, download_url, and archive_path.
| Placeholder | Value | Example |
|---|---|---|
{os} |
runtime.GOOS |
linux, darwin, windows |
{arch} |
runtime.GOARCH |
amd64, arm64 |
{version} |
Release tag with v stripped |
3.20.2 |
{tag} |
Release tag as-is | v3.20.2 |
Use {version} when the filename omits the v prefix (e.g. yq_linux_amd64.tar.gz). Use {tag} when it includes it (e.g. helm-v3.20.2-linux-amd64.tar.gz).
Standard Go-style asset names:
- name: binup
repo: https://github.com/jkandasa/binup
version_command: "binup version"
asset: "binup-{os}-{arch}.tar.gz"Version embedded in asset name:
- name: gh
repo: https://github.com/cli/cli
version_command: "gh --version"
asset: "gh_{version}_{os}_{arch}.tar.gz"
archive_path: ghBinary name inside archive differs from tool name (yq):
- name: yq
repo: https://github.com/mikefarah/yq
version_command: "yq --version"
asset: "yq_{os}_{arch}.tar.gz"
archive_path: "yq_{os}_{arch}"Binaries on an external CDN (Helm):
- name: helm
repo: https://github.com/helm/helm
version_command: "helm version --short"
download_url: "https://get.helm.sh/helm-{tag}-{os}-{arch}.tar.gz"Non-standard naming (Rust target triples):
- name: rtk
repo: https://github.com/rtk-ai/rtk
version_command: "rtk --version"
assets:
linux/amd64: rtk-x86_64-unknown-linux-musl.tar.gz
linux/arm64: rtk-aarch64-unknown-linux-gnu.tar.gz
darwin/amd64: rtk-x86_64-apple-darwin.tar.gz
darwin/arm64: rtk-aarch64-apple-darwin.tar.gzPinned version (including pre-releases):
- name: gh
repo: https://github.com/cli/cli
version: 2.55.0
version_command: "gh --version"
asset: "gh_{version}_{os}_{arch}.tar.gz"
archive_path: ghmain.go # entry point
cmd.go # cobra commands: root, update, list, status, version
updater.go # config types, GitHub API, download/extract/install logic
binup_sample.yaml # sample config — copy to binup.yaml alongside the binary
Makefile # build / clean
.goreleaser.yaml # release pipeline config
.github/workflows/release.yml # CI: tagged release + rolling devel pre-release
Tagged releases (v*) are built by GoReleaser and published to GitHub Releases with a grouped changelog. Pushing to main republishes a rolling devel pre-release with a snapshot build and a commit log since the last tag.
Binaries are produced for:
| OS | amd64 | arm64 |
|---|---|---|
| Linux | binup-linux-amd64.tar.gz |
binup-linux-arm64.tar.gz |
| macOS | binup-darwin-amd64.tar.gz |
binup-darwin-arm64.tar.gz |
| Windows | binup-windows-amd64.zip |
binup-windows-arm64.zip |
A checksums.txt is included with every release.
- Only
releases/latestis queried for unpinned tools; draft releases and pre-releases are skipped unless explicitly pinned. - No checksum or signature verification yet.
- Version comparison is numeric on dotted segments; pre-release suffixes (
-rc1,-beta) are stripped before comparing unpinned versions. Pinned versions use exact string matching.