Skip to content

jkandasa/binup

Repository files navigation

binup

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.

Features

  • 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_path supports the same placeholders.
  • External CDN supportdownload_url field 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_token in config or GITHUB_TOKEN env var; env takes precedence.

Build

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.

Commands

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).

Configuration

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

Global fields

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.

Per-tool fields

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_urlassetassets[GOOS/GOARCH].

Template placeholders

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).

Config examples by pattern

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: gh

Binary 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.gz

Pinned 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: gh

Project layout

main.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

Releases

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.

Limitations

  • Only releases/latest is 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.

About

A small Go utility that keeps locally installed CLI binaries up-to-date with their GitHub releases

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors