native cli · linux & macos

One YAML profile.
Every binary.

binstaller turns a single declarative profile into the same set of command-line tools on every machine — with a dry-run plan you read first, sha256 verification you can trust, and a lock file that pins the result.

curl --proto '=https' --tlsv1.2 -sSfL https://github.com/worxbend/binstaller/releases/latest/download/install.sh | sh
🐧 linux amd64 · arm64 🍎 macos amd64 · arm64 🔏 sigstore signed
Why binstaller

Small scope. Sharp edges filed off. 🧰

It does one job — installing binary tool distributions — and refuses to become a package manager, a dotfiles runner, or a shell-script host.

🔍

Plan before you apply

binstaller plan resolves every version, URL, archive mapping and symlink, then prints exactly what would happen. Nothing touches the filesystem.

🔐

Checksums, loudly

SHA-256 is verified when configured — and when it is missing, the plan says so in red. policy.mode: strict makes unverified downloads a hard failure.

🧊

Lock files

binstaller lock writes resolved versions, URLs and digests to JSON. apply --locked refuses to drift from it.

🛡️

SSRF-guarded downloads

HTTPS only. Loopback, link-local, private and cloud-metadata hosts are rejected — and every redirect hop is re-validated before it is followed.

↩️

Resumable state

Apply state is written after every tool result and keyed to the manifest fingerprint, so a re-run skips what already succeeded.

Native, no JVM

Shipped as a GraalVM native image for Linux and macOS on amd64 and arm64. One binary, instant start, no runtime to install.

📦

Archives handled

Direct binaries, zip, tar.gz and tar.xz — with member paths validated so nothing escapes the staging root.

🔗

Symlinks, opt-in sudo

Local symlinks by default. System-wide ones require allowSudoSymlinks and are flagged as sudo risk in the plan.

🆙

Update awareness

binstaller versions prints what you pinned next to the newest GitHub release, so drift is visible without applying anything.

How it works

Three commands, start to finish 🚀

Install the binary, describe your tools once, then plan and apply anywhere.

1

Install binstaller

The script verifies the release checksum, and the Sigstore signature too when cosign is present.

curl --proto '=https' --tlsv1.2 -sSfL \
  https://github.com/worxbend/binstaller/releases/latest/download/install.sh | sh
2

Describe your tools

Start from the example profile and keep it in version control next to your dotfiles.

cp config.example.yaml config.yaml
$EDITOR config.yaml
3

Plan, then apply

Read the plan, narrow it with --only / --skip, then let it run.

binstaller plan
binstaller apply
binstaller versions
Screenshots

Output you can actually read 📸

Script-friendly text by default, colour and live progress when a terminal is attached.

binstaller plan output showing resolved versions, download URLs, checksums and archive mappings
The manifest

One file describes the whole toolchain 📄

Declarative, reviewable in a pull request, and validated before a single byte is downloaded.

apiVersion: binstaller.io/v1alpha1
kind: BinaryDistributionProfile

spec:
  policy:
    mode: strict
    appsDir: "${HOME}/.apps"
    allowSudoSymlinks: false

  versions:
    lazygit: 0.61.0
    kubectl:
      resolver:            # resolve at plan time
        type: http-text
        url: https://dl.k8s.io/release/stable.txt

  plan:
    - name: lazygit
      kind: binary-tool
      spec:
        versionRef: lazygit
        installDir: "${appsDir}/lazygit"
        download:
          url: "https://github.com/.../v${version}.tar.gz"
          filename: lazygit.tar.gz
          checksum:
            algorithm: sha256
            value: 45d49e06…78633d
          archive:
            type: tar.gz
            extract:
              files:
                - from: lazygit
                  to: bin/lazygit
        executables:
          - path: bin/lazygit

Everything is checked up front ✅

  • 🚫Installer scripts are rejected — no shell syntax, no inherited environment, no surprise sudo.
  • 🔤Only safe variables interpolateHOME, USER, SHELL, XDG_*. A manifest can't smuggle a secret into a URL.
  • 🧭Selection is explicit--only is applied first, then --skip, always in manifest order.
  • 🧱Strict mode rejects dynamic latest URLs, missing checksums, sudo symlinks and tar.xz fallback extraction.
  • 🧾Duplicates and dangling refs fail at load time, not halfway through an install.
CLI surface

Four commands. That's the whole API. 🎛️

Predictable exit codes: 0 success, 1 resolution or apply failure, 2 usage error.

CommandWhat it doesWrites files
planRender the resolved install plan.No
applyDownload, verify, stage, install, symlink, save state.Yes
versionsPrint package versions and available updates.No
lockResolve and write a JSON lock file.Lock file only

Ready in one line 🎉

Install the native binary, copy the example profile, and run binstaller plan. Nothing is written until you say apply.