How to Install the Chainsaw CLI Unattended (CI, Fleet/MDM, Golden Images)
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
CHAINSAW_INSTALL_DIR=/opt/chainsaw/bin \
CHAINSAW_NO_MODIFY_PATH=1 \
CHAINSAW_VERSION=v0.21.23 \
CHAINSAW_NO_INSTALL_PING=1 \
curl -fsSL https://chain305.com/install.sh | sh
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.
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 toauth login --token/CHAINSAW_TOKEN.Registry client credentials — a
client_id:client_secretpair 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.