{"id":13586961,"url":"https://github.com/houseabsolute/ubi","last_synced_at":"2025-04-07T10:25:27.870Z","repository":{"id":41488685,"uuid":"327105217","full_name":"houseabsolute/ubi","owner":"houseabsolute","description":"The Universal Binary Installer","archived":false,"fork":false,"pushed_at":"2024-05-17T01:32:19.000Z","size":470,"stargazers_count":79,"open_issues_count":1,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-05-17T02:37:54.280Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/houseabsolute.png","metadata":{"files":{"readme":"README.md","changelog":"Changes.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-05T19:58:48.000Z","updated_at":"2024-05-17T02:37:55.764Z","dependencies_parsed_at":"2024-01-18T09:37:36.825Z","dependency_job_id":"9072b873-617f-49c1-9d0b-bc1e8e54c1bd","html_url":"https://github.com/houseabsolute/ubi","commit_stats":{"total_commits":233,"total_committers":3,"mean_commits":77.66666666666667,"dds":"0.025751072961373356","last_synced_commit":"414f0b9f93cbf59d20f8d1cc2992d56faed025cc"},"previous_names":[],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houseabsolute%2Fubi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houseabsolute%2Fubi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houseabsolute%2Fubi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houseabsolute%2Fubi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/houseabsolute","download_url":"https://codeload.github.com/houseabsolute/ubi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247633480,"owners_count":20970327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T15:05:56.179Z","updated_at":"2025-04-07T10:25:27.849Z","avatar_url":"https://github.com/houseabsolute.png","language":"Rust","funding_links":[],"categories":["Rust","others","CLI Applications"],"sub_categories":[],"readme":"# The Universal Binary Installer Library and CLI Tool\n\nWhen I say \"universal\", I mean it downloads binaries from GitHub or GitLab releases.\n\nWhen I say \"binary\", I mean it handles single-file executables like those created by most Go and\nRust projects.\n\nWhen I say \"installer\", I mean it plops the binary wherever you tell it to.\n\nAnd finally, when I say \"UBI\", I don't mean\n\"[universal basic income](https://en.wikipedia.org/wiki/Universal_basic_income)\", but that'd be nice\ntoo.\n\n## Using UBI as a Library\n\n```\n[dependencies]\nubi = \"x.y.z\"\n```\n\nSee the [`ubi` docs on docs.rs](https://docs.rs/ubi/latest/ubi/) for more details.\n\n## Installing the CLI Tool\n\nYou can install the CLI tool by hand by downloading the latest\n[release from the releases page](https://github.com/houseabsolute/ubi/releases).\n\nThere are also bootstrap installer scripts that provide a half-assed implementation of `ubi`:\n\n### Linux, macOS, FreeBSD, and NetBSD\n\n```\ncurl --silent --location \\\n    https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh |\n    sh\n```\n\nIf you run this as a non-root user, it will install `ubi` into `$HOME/bin`. If run as root it\ninstalls it into `/usr/local/bin`.\n\n#### Environment Variable Parameters\n\nThe bootstrap script supports several environment variables as parameters.\n\n| Variable       | Description                                                                                                                                                                                                                                                                                               |\n| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `TARGET`       | The directory in which to install `ubi`. Defaults to `$HOME/bin` for non-root users and `/usr/local/bin` for root.                                                                                                                                                                                        |\n| `TAG`          | The `ubi` version tag to download. Defaults to the latest release.                                                                                                                                                                                                                                        |\n| `FILENAME`     | The name of the [release file asset](https://github.com/houseabsolute/ubi/releases) to download. This skips the platform detection and just downloads the file with this name. Use this if the bootstrap script fails to detect your platform (but please consider submitting a PR to fix the detection). |\n| `GITHUB_TOKEN` | The GitHub API token to use when downloading releases. This is only necessary for private repos or if you are hitting the GitHub API anonymous usage limits. Hitting these limits is mostly likely to happen when you're running the bootstrap script repeatedly in CI.                                   |\n\nTo set these variables, you can either set them in the environment before running the script, or you\ncan set them on the command line. Note that you need to set them on the _right_ side of the pipe.\nFor example, to install a specific version of `ubi` using the `TAG` env var:\n\n```\ncurl --silent --location \\\n    https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh |\n    TAG=v0.0.15 sh\n```\n\n**Note for GitHub Enterprise:** If you are running this script from an Action in a GitHub Enteprise\ninstallation, the `GITHUB_TOKEN` environment variable will be for that GH Enterprise setup. You will\nneed to create a separate token for github.com, and explicitly pass that as your `GITHUB_TOKEN`.\n\n### Windows\n\n```\npowershell -exec bypass -c \"Invoke-WebRequest -URI 'https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.ps1' -UseBasicParsing | Invoke-Expression\"\n```\n\nYou can run this from a command or the Powershell command line. This will install `ubi.exe` into the\ncurrent directory.\n\n## How to Use It\n\n```\nUsage: ubi [OPTIONS]\n\nOptions:\n  -p, --project \u003cproject\u003e            The project you want to install, like houseabsolute/precious or\n                                     https://github.com/houseabsolute/precious.\n  -t, --tag \u003ctag\u003e                    The tag to download. Defaults to the latest release.\n  -u, --url \u003curl\u003e                    The url of the file to download. This can be provided instead\n                                     of a project or tag. This will not use the forge site's API, so\n                                     you will never hit its API limits. With this parameter, you do\n                                     not need to set a token env var except for private repos.\n      --self-upgrade                 Use ubi to upgrade to the latest version of ubi. The --exe,\n                                     --in, --project, --tag, and --url args will be ignored.\n  -i, --in \u003cin\u003e                      The directory in which the binary should be placed. Defaults to\n                                     ./bin.\n  -e, --exe \u003cexe\u003e                    The name of the file to look for in an archive file, or the\n                                     name of the downloadable file excluding its extension, e.g.\n                                     `ubi.gz`. By default this is the same as the project name, so\n                                     for houseabsolute/precious we look for precious or\n                                     precious.exe. When running on Windows the `.exe` suffix will be\n                                     added, as needed. You cannot pass `--extract-all` when this is\n                                     set.\n      --rename-exe \u003crename-exe-to\u003e   The name to use for the executable after it is unpacked. By\n                                     default this is the same as the name of the file passed for the\n                                     `--exe` flag. If that flag isn't passed, this is the same as\n                                     the name of the project. When running on Windows the `.exe`\n                                     suffix will be added, as needed. You cannot pass\n                                     `--extract-all` when this is set.\n      --extract-all                  Pass this to tell `ubi` to extract all files from the archive.\n                                     By default `ubi` will only extract an executable from an\n                                     archive file. But if this is true, it will simply unpack the\n                                     archive file. If all of the contents of the archive file share\n                                     a top-level directory, that directory will be removed during\n                                     unpacking. In other words, if an archive contains\n                                     `./project/some-file` and `./project/docs.md`, it will extract\n                                     them as `some-file` and `docs.md`. You cannot pass `--exe` or\n                                     `--rename-exe-to` when this is set.\n  -m, --matching \u003cmatching\u003e          A string that will be matched against the release filename when\n                                     there are multiple matching files for your OS/arch. For\n                                     example, there may be multiple releases for an OS/arch that\n                                     differ by compiler (MSVC vs. gcc) or linked libc (glibc vs.\n                                     musl). Note that this will be ignored if there is only one\n                                     matching release filename for your OS/arch.\n      --forge \u003cforge\u003e                The forge to use. If this isn't set, then the value of\n                                     --project or --url will be checked for gitlab.com. If this\n                                     contains any other domain _or_ if it does not have a domain at\n                                     all, then the default is GitHub. [possible values: github,\n                                     gitlab]\n      --api-base-url \u003capi-base-url\u003e  The the base URL for the forge site's API. This is useful for\n                                     testing or if you want to operate against an Enterprise version\n                                     of GitHub or GitLab. This should be something like\n                                     `https://github.my-corp.example.com/api/v4`.\n  -v, --verbose                      Enable verbose output.\n  -d, --debug                        Enable debugging output.\n  -q, --quiet                        Suppresses most output.\n  -h, --help                         Print help\n  -V, --version                      Print version\n```\n\n## Using a Forge Token\n\nYou can set a token for GitHub in the `GITHUB_TOKEN` environment variable. For GitLab, you can\neither use `CI_JOB_TOKEN` or `GITLAB_TOKEN`. The former is set in GitLab CI automatically, and it\nwill be preferred if both are set.\n\nIf a token environment variable is set, then this will be used for all API calls. This is required\nto download releases for a private project. If you are running `ubi` against GitHub in a CI\nenvironment that runs jobs frequently, you may also need this, as GitHub has a very low rate limit\nfor anonymous API requests.\n\nHowever, you can also use the `--url` option to bypass the forge site API by providing the download\nlink directly.\n\n## Installed Executable Naming\n\nIf the release is in the form of a tarball or zip file, `ubi` will look in that archive file for a\nfile that matches the value given for the `exe` field, if any. Otherwise it looks for a file with\nthe same name as the project. In either case, the file will be installed with the name it has in the\narchive file.\n\nIf the release is in the form of a bare executable or a compressed executable, then the installed\nexecutable will use the name of the project instead.\n\nThis is a bit inconsistent, but it's how `ubi` has behaved since it was created, and I find this to\nbe the sanest behavior. Some projects, for example `rust-analyzer`, provide releases as compressed\nexecutables with names like `rust-analyzer-x86_64-apple-darwin` and\n`rust-analyzer-x86_64-unknown-linux-musl`, so installing these as `rust-analyzer` seems like better\nbehavior.\n\n## How `ubi` Finds the Right Release Artifact\n\n\u003c!-- prettier-ignore-start --\u003e\n\u003e [!WARNING]\n\u003e Note that the exact set of steps that `ubi` follows to find a release artifacts is not considered\n\u003e part of the API, and may change in any future release.\n\u003c!-- prettier-ignore-end --\u003e\n\nWhen `ubi` looks at the release assets (downloadable files) for a project, it tries to find the\n\"right\" asset for the platform it's running on. The matching logic currently works like this:\n\nFirst it filters out assets with extensions it doesn't recognize. Right now this is anything that\ndoesn't match one of the following:\n\n- `.AppImage` (Linux only)\n- `.bat` (Windows only)\n- `.bz`\n- `.bz2`\n- `.exe` (Windows only)\n- `.gz`\n- `.jar`\n- `.pyz`\n- `.tar`\n- `.tar.bz`\n- `.tar.bz2`\n- `.tar.gz`\n- `.tar.xz`\n- `.tbz`\n- `.tgz`\n- `.txz`\n- `.xz`\n- `.zip`\n- No extension\n\nIt tries to be careful about what constitutes an extension. It's common for release filenames to\ninclude a dot (`.`) in the filename before something that's _not_ intended as an extension, for\nexample `some-tool.linux.amd64`.\n\nIf, after filtering for extensions, there's only one asset, it will try to install this one, on the\nassumption that this project releases assets which are not platform-specific (like a shell script)\n_or_ that this project only releases for one platform and you're running `ubi` on that platform.\n\nIf there are multiple matching assets, it will first filter them based on your platform. It does\nthis in several stages:\n\n- First it filters based on your OS, which is something like Linux, macOS, Windows, FreeBSD, etc. It\n  looks at the asset filenames to see which ones match your OS, using a (hopefully complete) regex.\n- Next it filters based on your CPU architecture, which is something like x86-64, ARM64, PowerPC,\n  etc. Again, this is done with a regex.\n- If you are running on a Linux system using musl as its libc, it will also filter out anything\n  _not_ compiled against musl. This filter looks to see if the file name contains an indication of\n  which libc it was compiled against. Typically, this is something like \"-gnu\" or \"-musl\". If it\n  does contain this indicator, names that are _not_ musl are filtered out. However, if there is no\n  libc indicator, the asset will still be included.\n\nAt this point, any remaining assets should work on your platform, so if there's more than one match,\nit attempts to pick the best one.\n\n- If it finds both 64-bit and 32-bit assets and you are on a 64-bit platform, it filters out the\n  32-bit assets.\n- If you've provided a `--matching` string, this is used as a filter at this point.\n- If your platform is macOS on ARM64 and there are assets for both x86-64 and ARM64, it filters out\n  the non-ARM64 assets.\n\nFinally, if there are still multiple assets left, it sorts them by file name and picks the first\none. The sorting is done to make sure it always picks the same one every time it's run.\n\n## How `ubi` Finds the Right Executable in an Archive File\n\nIf the selected release artifact is an archive file (a tarball or zip file), then `ubi` will look\ninside the archive to find the right executable.\n\nIt first tries to find a file matching the exact name of the project (plus an extension on Windows).\nSo for example, if you're installing\n[`houseabsolute/precious`](https://github.com/houseabsolute/precious), it will look in the archive\nfor a file named `precious` on Unix-like systems and `precious.bat` or `precious.exe` on Windows.\nNote that if it finds an exact match, it does not check the file's mode.\n\nIf it can't find an exact match it will look for a file that _starts with_ the project name. This is\nmostly to account for projects that include things like platforms or release names in their\nexecutables. Using [`houseabsolute/precious`](https://github.com/houseabsolute/precious) as an\nexample again, it will match a file named `precious-linux-amd64` or `precious-v1.2.3`. In this case,\nit will _rename_ the extracted file to `precious`. On Unix-like systems, these partial matches will\nonly be considered if the file's mode includes an executable bit. On Windows, it looks for a partial\nmatch that is a `.bat` or `.exe` file, and the extracted file will be renamed to `precious.bat` or\n`precious.exe`.\n\n## Upgrading `ubi`\n\nYou can run `ubi --self-upgrade` to upgrade `ubi` using `ubi`. Note that you must have write\npermissions to the directory containing `ubi` for this to work.\n\nOn Windows, this leaves behind a file named `ubi-old.exe` that must be deleted manually.\n\n## Best Practices for Using `ubi` in CI\n\nThere are a few things you'll want to consider when using `ubi` in CI.\n\nFirst, there are forge site API rate limits. See the\n[GitHub API rate limits documentation](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting)\nand\n[GitLab API rate limits documentation](https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits).\n\nThe GitHub limit can be as low as 60 requests per hour per IP when not providing a `GITHUB_TOKEN`,\nso you will almost certainly want to provide this if you are getting releases from GitHub.\n\nWhen running in GitHub Actions you can use the `${{ secrets.GITHUB_TOKEN }}` syntax to set this env\nvar, and in that case the rate limits are per repository.\n\n```yaml\n- name: Install UBI\n  shell: bash\n  run: |\n    curl --silent --location \\\n        https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh |\n        sh\n  env:\n    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n- name: Install tools with UBI\n  shell: bash\n  run: |\n    \"$HOME/bin/ubi\" --project houseabsolute/precious --in \"$HOME/bin\"\n  env:\n    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n```\n\nSimilarly, the GitLab CI system sets a `CI_JOB_TOKEN` for all jobs. Make sure this environment\nvariable is set when you use `ubi` to install something from GitLab in CI.\n\nIf you only run `ubi` on one platform, you can avoid hitting the GitHub or GitLab API entirely by\nusing the `--url` parameter. But if you run on multiple platforms this can be tedious to maintain\nand it largely defeats the purpose of using `ubi`.\n\nIf you are downloading executables from repos you don't control _and_ you don't use the `--url`\nparameter, then you should use the `--tag` parameter to specify the released version you want to\ninstall. Otherwise `ubi` will always download the latest version, which can lead to surprises,\nespecially if you are running the tools you download in CI.\n\n## Using `ubi` with GitHub Enterprise or GitLab for Enterprise\n\nThe command line tool takes an `--api-base-url` flag for this purpose. This should be the full URL\nto the root of the API, something like `https://github.my-corp.example.com/api/v4`.\n\n## Why This Is Useful\n\nWith the rise of Go and Rust, it has become increasingly common for very useful tools like\n[ripgrep](https://github.com/BurntSushi/ripgrep) to publish releases in the form of a tarball or zip\nfile containing a single executable. Having a single tool capable of downloading the right binary\nfor your platform is quite handy.\n\nYes, this can be done in half a dozen lines of shell on Unix systems, but do you know how to do the\nequivalent in Powershell?\n\nOnce you have `ubi` installed, you can use it to install any of these single-binary tools on Linux,\nmacOS, and Windows.\n\n### Is This Better Than Installing from Source?\n\nI think so. While you can use `go` or `cargo` to install these tools, that requires an entire\nlanguage toolchain. Then you have to actually compile the tool, which may require downloading and\ncompiling many dependencies. This is going to be a lot slower and more error prone than installing a\nbinary.\n\n### Is This Better Than Installing from a deb/RPM/homebrew/chocolatey Package?\n\nThat's debatable. The big advantage of using `ubi` is that you can use `ubi` in the same way on\nLinux, macOS, and Windows. The big disadvantage is that you're not using a package manager, so you\ndon't get any record of the installation, a way to uninstall, etc. If a tool provides\nplatform-specific packages for your platforms, you should probably consider using those instead of\n`ubi`.\n\n### Is this Better Than Installing via `curl https://some.site/random/installer.sh | sh`?\n\nIsn't literally anything else better than this?\n\nIn all seriousness, `ubi` does not execute arbitrary code when you install anything. That seems like\na good thing.\n\n## Linting and Tidying this Code\n\nThe code in this repo is linted and tidied with\n[`precious`](https://github.com/houseabsolute/precious). This repo contains a `mise.toml` file.\n[Mise](https://mise.jdx.dev/) is a tool for managing dev tools with per-repo configuration. You can\ninstall `mise` and use it to run `precious` as follows:\n\n```\n# Installs mise\ncurl https://mise.run | sh\n# Installs precious and other dev tools\nmise install\n```\n\nOnce this is done, you can run `precious` via `mise`:\n\n```\n# Lints all code\nmise exec -- precious lint -a\n# Tidies all code\nmise exec -- precious tidy -a\n```\n\nIf you want to use `mise` for other projects, see [its documentation](https://mise.jdx.dev/) for\nmore details on how you can configure your shell to always activate `mise`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhouseabsolute%2Fubi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhouseabsolute%2Fubi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhouseabsolute%2Fubi/lists"}