How to Install the Chainsaw CLI Unattended (CI, Fleet/MDM, Golden Images)

Intermediate 15 minutes Platform Engineers / DevOps Getting Started Updated

A single non-interactive flow to install the Chainsaw CLI, authenticate with a pre-minted token, and wire package managers — no browser, no shell-rc edits, no prompts. Built for CI runners, MDM-managed fleets, and golden images.

The interactive install assumes a human at a terminal. CI runners, MDM-managed fleets, and golden images need the opposite: a single non-interactive flow that never opens a browser, never rewrites a shell rc, and never prompts. All the pieces already exist in the CLI — this guide wires them into one path.

The flow has four stages — install the binary (no PATH mutation) → mint credentials out-of-band → authenticate non-interactively → wire the package managers — plus a fifth step that verifies the result with a machine-readable exit code.

1. Install the binary without touching any shell rc

Pass an explicit CHAINSAW_INSTALL_DIR and CHAINSAW_NO_MODIFY_PATH=1 so the installer drops the binary in a known location and does not rewrite ~/.zshrc / ~/.bashrc on the fleet machine. Add the install-ping opt-out (CHAINSAW_NO_INSTALL_PING=1, or the cross-tool DO_NOT_TRACK=1) and pin the version for reproducible images:

sudo mkdir -p /opt/chainsaw/bin
curl -fsSL https://chain305.com/install.sh | \
  CHAINSAW_INSTALL_DIR=/opt/chainsaw/bin \
  CHAINSAW_NO_MODIFY_PATH=1 \
  CHAINSAW_VERSION=v0.22.131 \
  CHAINSAW_NO_INSTALL_PING=1 \
  sh

The variables go on sh, the process that runs the installer, not on curl. Written as CHAINSAW_NO_MODIFY_PATH=1 curl … | sh, they would only reach curl, and the installer would ignore them.

The installer normally turns the install guard on as its last step, by adding one line to the user’s shell rc. CHAINSAW_NO_MODIFY_PATH=1 also skips that step, because it forbids rc edits; so does CI being set, and so does CHAINSAW_NO_GUARD=1. The output ends with a Guard: line saying why. On a fleet, turn the guard on through the same config-management layer as PATH (for example, run chainsaw guard init --install as each user), or route CI installs through chainsaw npm … explicitly.

Because CHAINSAW_NO_MODIFY_PATH=1 leaves PATH alone, put the install dir on PATH through your config-management layer instead of a shell rc:

# Option A — a system-wide profile.d drop-in (managed by Ansible/Chef/MDM):
echo 'export PATH=/opt/chainsaw/bin:$PATH' | sudo tee /etc/profile.d/chainsaw.sh

# Option B — a symlink into a directory already on PATH:
sudo ln -sf /opt/chainsaw/bin/chainsaw /usr/local/bin/chainsaw

On Windows, install to a fixed directory and set the machine PATH via your MDM (Intune/Group Policy) rather than the per-user edit the interactive PowerShell snippet performs.

The client install is checksum-only over TLS — it never requires or runs cosign. Do not set CHAINSAW_REQUIRE_SIGNATURE on fleet machines; that opt-in flag pulls cosign onto every endpoint for a same-host binary and is not the supported client posture.

2. Mint the credentials out-of-band

Automation uses two distinct secrets — mint both ahead of time and inject them as CI secrets / MDM-managed variables. Never bake them into an image.

  • A management API token authenticates the CLI to the server. Generate one at https://chain305.com/dashboard/api-keys (self-host / VPC: https://<your-server>/dashboard/api-keys). This is the value you pass to auth login --token / CHAINSAW_TOKEN.

  • Registry client credentials — a client_id:client_secret pair the package-manager config uses to authenticate installs through the proxy. Mint them from the dashboard (Settings → Client Credentials → New) or from the CLI once a token is configured:

    # --json prints the cleartext secret exactly once; capture it immediately.
    chainsaw auth client create --name ci-frontend --client-type service-token --json \
      | jq -r '"\(.client_id):\(.client_secret)"'
    

3. Authenticate non-interactively (never a browser)

Pass the pre-minted management token with --token; the CLI validates and saves the session without ever attempting a browser or device-code flow:

export CHAINSAW_SERVER=https://chain305.com   # self-host/VPC: your server URL
export CHAINSAW_TOKEN=<management-api-token>   # from step 2

chainsaw auth login --token "$CHAINSAW_TOKEN"

--token (CI) and --device (headless with a second device) are the two non-interactive modes. CHAINSAW_TOKEN + CHAINSAW_SERVER alone are also honoured by every command, so you can skip auth login entirely and let the env vars drive — but running it once persists the session so later commands don’t need the env each time. Re-running auth login when already signed in is a no-op that reports the current identity; pass --force to re-authenticate.

4. Wire the package managers non-interactively

Feed the registry credentials straight in with --credentials and set --scope user so nothing prompts. Always pass --org <slug> in automation — the proxy rejects slug-less URLs (CHW-4314), and the auto-discovery fallback needs a live token round-trip you don’t want a build step to depend on:

CLIENT_CREDS="<client_id>:<client_secret>"   # from step 2

chainsaw --server "$CHAINSAW_SERVER" install-hook npm \
  --org acme-corp \
  --scope user \
  --credentials "$CLIENT_CREDS"

Repeat per manager (pip, cargo, go, maven, …), or wire every installed manager at once with --all in place of the positional name. --server is a root flag, so it goes before install-hook; omit it if CHAINSAW_SERVER is set or you ran auth login in step 3.

5. Verify in automation

chainsaw auth status --json prints a machine-readable status and exits non-zero when the token is missing, expired, or the server is unreachable. Gate your provisioning step on the exit code:

if chainsaw auth status --json; then
  echo "chainsaw authenticated"
else
  echo "chainsaw auth failed (see JSON above)" >&2
  exit 1
fi

chainsaw install-hook likewise exits non-zero if wiring a manager fails, and --json returns a per-manager wired result you can assert on. A complete provisioning script checks both.