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
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.
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.