Installation

Requirements

  • Linux (x86_64 or arm64) or macOS (Apple Silicon / arm64 only). Intel Macs (x86_64) are no longer supported. On Windows, consider using Ubuntu for WSL. BusyBox is not supported.

  • POSIX-compliant login shell (bash, zsh, dash, ksh93), fish, elvish, nushell, or powershell. csh and tcsh have minimal support (PATH and environment variables only).

  • Python 3.12, which will be bootstrap installed into ~/.local/share/koopa-bootstrap automatically when necessary.

  • Core utilities: curl, git, grep, mkdir, mktemp, rm, sed, tar.

macOS

Xcode Command Line Tools are required.

xcode-select --install

The command line tools will install into /Library/Developer/CommandLineTools.

Debian / Ubuntu

if [ "$(id -u)" -eq 0 ]
then
    apt-get update
    apt-get --quiet --yes install sudo
fi
sudo apt-get update
sudo apt-get \
    --no-install-recommends \
    --quiet \
    --yes \
    install \
        bash \
        build-essential \
        ca-certificates \
        coreutils \
        curl \
        findutils \
        git \
        locales \
        lsb-release \
        procps \
        python3 \
        unzip

Fedora / RHEL

if [ "$(id -u)" -eq 0 ]
then
    dnf -y install sudo
fi
sudo dnf -y install \
    automake \
    bash \
    coreutils \
    curl \
    findutils \
    gcc \
    git \
    make \
    procps \
    python3 \
    unzip

Install koopa

The install script will prompt to determine whether you want a shared install for all users, or for the current local user only. It will also ask about dotfile configuration and whether your shell profile configuration file should be modified.

sh -c "$(curl -LSs https://koopa.acidgenomics.com/install)"

Alternatively, download the install script as a temporary file and then execute.

install="$(mktemp)"
curl -LSs -o "$install" https://koopa.acidgenomics.com/install
chmod +x "$install"
"$install"

Here’s how to install koopa non-interactively, which is intended primarily for building Docker images.

curl -LSs https://koopa.acidgenomics.com/install \
    | sh -s -- --non-interactive

Offline / pinned install (restricted networks)

The install script above fetches and executes code from koopa.acidgenomics.com in one step. On a corporate build that requires every artifact to be reviewed and sourced from an approved host before it runs, install from a pinned release tarball instead.

What this secures: ingress. The artifact is pinned to a specific tag (an immutable commit, not a moving branch), mirrorable through an internal proxy, and reviewable before it ever runs, unlike curl | sh, which fetches and executes in the same step.

What this does not secure: the ~500 third-party apps koopa can install (compilers, language runtimes, CLI tools). Those are downloaded and, in many cases, compiled at koopa install time from their own upstream hosts. Use the internal mirror below, with pull_priority set to "vendor_only", to route those through the same review gate.

# Fetch a specific tagged release (or pull the same tarball from an internal
# mirror that proxies GitHub tag archives). GitHub's codeload URLs are
# content-addressed by tag, so re-fetching the same tag always returns the
# same bytes -- this is what makes the artifact reviewable ahead of use.
version=0.25.0
curl -LSs -o "koopa-${version}.tar.gz" \
    "https://github.com/acidgenomics/koopa/archive/refs/tags/v${version}.tar.gz"

# Extract into the standard koopa data directory.
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
mkdir -p "$data_home"
tar -xzf "koopa-${version}.tar.gz" -C "$data_home"
mv "${data_home}/koopa-${version}" "${data_home}/koopa"

# Activate, e.g. by adding this line to the shell profile.
. "${data_home}/koopa/activate.sh"

This is a pinned, non-git release: koopa install/uninstall/configure work normally against it, but koopa update recognizes the tree as a pinned release and reports as much instead of attempting a git pull. To update, fetch and extract the next tagged release the same way.

The first koopa invocation needs an interpreter matching the version pinned in .python-version (currently 3.12) to run its own CLI. It looks for one at /usr/bin/python3 first, then the .python-version-derived name (e.g. python3.12) and python3 as resolved on PATH; if none matches, it runs bootstrap.sh. bootstrap.sh reads the same vendor.json described in Internal mirror below, so a vendor_only network routes the bootstrap Python build through the mirror too, the same as every other app install.

Two things to know before relying on that for a fully offline bootstrap:

  • Parsing vendor.json needs a JSON parser. bootstrap.sh uses whichever python3 it finds (any version; this is unrelated to the 3.12 pin) and falls back to a sed-based parse of the flat HTTP fields when no python3 exists at all. That fallback cannot read the nested remotes map below, so it only supports a pre-populated src_repo mirror, not the remote-proxy rewrite.

  • The fast path (a prebuilt CPython via uv, seconds instead of a ~10 minute compile) needs uv already on PATH; bootstrap.sh never fetches the uv binary itself from the mirror. When uv is present, it can still fetch its CPython build through a mirror-derived UV_PYTHON_INSTALL_MIRROR (see below), but uv cannot present the mirror’s Bearer token, so an auth-required repo fails the fast path – bootstrap.sh then falls through to the source build automatically, which does authenticate.

Internal mirror (restricted networks)

Installing apps normally downloads source tarballs and prebuilt binaries from upstream hosts (GitHub, GNU/Savannah mirrors, koopa.acidgenomics.com/src, and similar) directly. On a network that restricts outbound traffic to an approved allowlist, or where every artifact must be reviewed before it reaches a host, route these downloads through an internal mirror instead: a generic HTTP(S) repository or an S3 bucket that you control and populate.

Copy the example config into ${XDG_CONFIG_HOME:-~/.config}/koopa/vendor.json and edit it in place. This location is recommended over etc/koopa/vendor.json inside the checkout (see precedence note below):

mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/koopa"
cp etc/koopa/vendor.json.example "${XDG_CONFIG_HOME:-$HOME/.config}/koopa/vendor.json"
{
  "enabled": true,
  "backend": "http",
  "http": {
    "base_url": "https://artifacts.example.com",
    "src_repo": "generic-team-koopa-src",
    "binary_repo": "generic-team-koopa-binaries",
    "token_env_var": "HTTP_ACCESS_TOKEN",
    "remotes": {
      "koopa.acidgenomics.com": "koopa-remote",
      "github.com": "github-remote",
      "www.python.org": "python-org-remote",
      ".gnu.org": "gnu-remote"
    }
  },
  "pull_priority": "vendor_only"
}

Fields:

  • backend: "http" or "s3". Only one backend section (http or s3) is read, matching backend.

  • http.base_url: the URL prefix that repository names hang off. koopa only ever appends /<repo>/... to it, so if your server serves repositories under a path prefix rather than at the domain root, include that prefix here.

  • http.token_env_var: the name of an environment variable holding a Bearer token, read at request time. Never put the token itself in vendor.json — anonymous read access needs no token at all.

  • http.remotes: an optional host -> repository map for remote repositories that proxy an upstream host and cache on first fetch (as opposed to src_repo, a local repository you populate yourself; see below). A request to https://github.com/... is rewritten to {base_url}/{remotes["github.com"]}/..., preserving everything after the host. Keys starting with . match as a hostname suffix, so ".gnu.org" covers ftpmirror.gnu.org, ftp.gnu.org, and any other GNU mirror host koopa tries. Every upstream URL koopa would otherwise contact is tried through a matching remote before falling back to the direct public host (or not at all, under vendor_only).

  • s3.profile: a named AWS CLI profile used for aws s3 cp / aws s3api head-object calls. Requires the aws CLI on PATH. The s3 backend has no equivalent to remotes.

  • pull_priority: "vendor_first" (the default) tries the mirror before falling back to the public host, useful while the mirror is still being populated. "vendor_only" never contacts a public host: only src_repo and remotes are tried, and the install fails outright if an artifact is missing from both — this is what a genuinely airgapped or allowlisted network needs.

vendor.json is checked in order, first existing file wins (the two are never merged): ${XDG_CONFIG_HOME:-~/.config}/koopa/vendor.json, then etc/koopa/vendor.json relative to the koopa prefix. The etc/koopa/ location is gitignored either way; it is not something you commit alongside the koopa checkout. The XDG_CONFIG_HOME location is preferred because it lives outside the koopa tree entirely: a pinned-release re-extract or a git clean in the checkout can never destroy it, so the mirror config the host depends on to install anything survives independently. Populating src_repo with the app versions your team needs is an operational task outside koopa itself — koopa develop push-app-build <name> uploads a locally built app to the configured backend once credentials are present. A remotes entry needs no such per-app population step: point one remote repository at koopa.acidgenomics.com and it covers every source tarball koopa already mirrors there, including the ones bootstrap.sh itself needs (see above) — a single entry gets a vendor_only network most of the way to a fully offline install with zero pre-population.

If you are behind a corporate proxy rather than (or in addition to) a vendor mirror, see Troubleshooting for the http_proxy variable koopa and its bootstrap dependencies honor.