Skip to content

The pluspanel CLI

pluspanel is a single static binary for Linux, macOS and Windows (amd64 and arm64). Every operation of the public API is a command, with the same permissions as the panel. It sends no telemetry, no analytics and no update checks: it talks only to the panel address you configure.

Linux and macOS:

Terminal window
curl -fsSL https://get.pluspanel.net/cli.sh | sh

Windows (PowerShell):

Terminal window
irm https://get.pluspanel.net/cli.ps1 | iex

The installers download the latest release from GitHub (112tech/pluspanel-cli), always check its SHA-256 checksum, and also check the cosign signature of the checksums when cosign is installed. They never call sudo: cli.sh installs into ~/.local/bin (as root: /usr/local/bin), cli.ps1 into %LOCALAPPDATA%\Programs\pluspanel and adds it to your user PATH. A directory or a version can be chosen:

Terminal window
curl -fsSL https://get.pluspanel.net/cli.sh | sh -s -- --prefix /opt/bin --version 0.1.0

Prefer to read a script before running it? Download it first:

Terminal window
curl -fsSLO https://get.pluspanel.net/cli.sh
less cli.sh
sh cli.sh

Each release carries the archives pluspanel_<version>_<os>_<arch>.tar.gz (Windows: .zip), a SHA256SUMS file and its cosign signature SHA256SUMS.sig, made with the PlusPanel release key: cosign.pub.

Terminal window
curl -fsSLO https://pluspanel.net/docs/cli/cosign.pub
cosign verify-blob --key cosign.pub --signature SHA256SUMS.sig --insecure-ignore-tlog=true SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS # macOS: shasum -a 256 --ignore-missing -c SHA256SUMS

Both commands must succeed before you use the binary. The signature is made offline with the release key and is not uploaded to a public transparency log, hence --insecure-ignore-tlog=true: the trust comes from the key.

Terminal window
pluspanel login --api-url https://panel.example.com

The CLI shows a short code and opens the panel’s /device page in your browser. Sign in there as usual (password and second factor, or a passkey), check that the code and the request (device name, IP address, time) are yours, and approve. Approving asks you to confirm your identity again (password or authenticator code) if you have not done so in the last 5 minutes. The CLI then receives a personal API key, valid 90 days and revocable in Settings → API, and keeps it in a file only you can read (0600) in its configuration directory. On the approval page you can make the key read-only.

Without a browser on this machine, add --no-browser and open the printed address anywhere. --client-name changes the name shown on the approval page (default: pluspanel CLI on <hostname>).

pluspanel whoami shows the user, organization and key in use. pluspanel logout forgets the stored key; pluspanel logout --revoke also revokes it on the control plane.

Use an API key created for the job, with the scopes it needs, from your CI’s secret store:

Terminal window
export PLUSPANEL_API_URL=https://panel.example.com
export PLUSPANEL_API_KEY="$PLUSPANEL_CI_KEY"
pluspanel servers list -o json

Or store a key in a profile: --api-key reads it from standard input and --api-key-file from a file, so it does not end up in your shell history:

Terminal window
printf '%s' "$PLUSPANEL_CI_KEY" | pluspanel login --api-url https://panel.example.com --api-key
pluspanel login --api-url https://panel.example.com --api-key-file ~/.pluspanel-key

The CLI checks that what it reads looks like a PlusPanel key (ppk_…) before sending it anywhere.

Profiles keep several control planes apart, for example staging and production:

Terminal window
pluspanel login --profile staging --api-url https://staging.example.com
pluspanel login --profile prod --api-url https://panel.example.com
pluspanel profiles list
pluspanel profiles use prod
pluspanel profiles set prod --default-output json
pluspanel --profile staging servers list
PLUSPANEL_PROFILE=prod pluspanel accounts list
pluspanel profiles delete staging

Without --profile the current profile is used (the first one you signed in to, or the one chosen with profiles use). The files live in the user configuration directory: ~/.config/pluspanel/ (or $XDG_CONFIG_HOME/pluspanel) on Linux, ~/Library/Application Support/pluspanel on macOS, %AppData%\pluspanel on Windows; PLUSPANEL_CONFIG_DIR overrides it. config.yaml holds the profiles (no secrets), credentials.yaml the keys, mode 0600: the CLI refuses to read it when other users can.

Variable Effect
PLUSPANEL_PROFILE profile to use
PLUSPANEL_API_URL API URL (overrides the profile’s)
PLUSPANEL_API_KEY API key (overrides the stored credential)
PLUSPANEL_CONFIG_DIR configuration directory
NO_COLOR no bold table headers (also --no-color)

-o table (default, for people), -o json and -o yaml (for programs). JSON output is the API’s answer, unchanged, so it pipes into jq:

Terminal window
# Every server's name and status
pluspanel servers list --all -o json | jq -r '.items[] | "\(.name)\t\(.status)"'
# Accounts as CSV
pluspanel accounts list --all -o json | jq -r '.items[] | [.id, .name] | @csv'

--all follows next_cursor through every page; without it a list prints one page and the next cursor (--cursor fetches it). --verbose prints each request, its status, request id and remaining rate limit to standard error (never the key). Errors read Error: <detail> (code <code>, request <id>). Exit codes: 0 success, 1 usage or API error, 2 authentication (401/403, not signed in), 3 not found, 4 conflict, precondition failed or rate limited (409/412/429), 5 the API could not be reached.

Terminal window
# Servers and accounts
pluspanel servers list
pluspanel servers get 6f1c2d3e-…
pluspanel accounts list
# Launch a WordPress site in an account (retries are safe with --idempotency-key)
pluspanel sites launch 5a5a5a5a-… \
--primary-domain shop.example.com --title Shop --admin-email owner@example.com \
--idempotency-key "$(uuidgen)"
# Full request bodies from a file or standard input (- = stdin); flags override its members
pluspanel sites launch 5a5a5a5a-… --data-file site.json
pluspanel dns create-record 7c7c7c7c-… --data '{"name":"www","type":"CNAME","content":"example.com."}'
# Optimistic concurrency: send back the ETag of the GET you read (412 if the object changed
# meanwhile; see Conventions → ETags for reading it with curl)
pluspanel security put-site 6b6b6b6b-… --data-file security.json --if-match "$etag"
# API keys and webhooks
pluspanel api-keys create --name ci --scopes 'servers:read,sites:write' -o json
pluspanel api-keys list
pluspanel webhooks create --url https://hooks.example.com/pluspanel --events 'site.*,backup.failed'
pluspanel webhooks list-deliveries 9d9d9d9d-… --state dead
# Any operation by path, like curl with your credentials
pluspanel api GET /php-versions
pluspanel api GET '/audit?limit=5'

Commands are grouped by resource (pluspanel --help lists the groups, pluspanel <group> --help their commands). Path parameters are positional arguments in the order of the route; query parameters and top-level body members are flags (lists comma-separated); every POST takes --idempotency-key and every PUT (and every PATCH whose path has a GET) --if-match. Every command’s --help shows its description, flags and HTTP route, generated from the API reference.

Terminal window
pluspanel completion bash > ~/.local/share/bash-completion/completions/pluspanel
pluspanel completion zsh > "${fpath[1]}/_pluspanel"
pluspanel completion fish > ~/.config/fish/completions/pluspanel.fish
pluspanel completion powershell | Out-String | Invoke-Expression