Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Signing in from the CLI and automation

Sign piquelctl in, keep several accounts signed in, and give scripts, CI jobs and agents an API token that can only do what they need.

Before you start

  • You have an account with a passkey.

  • The commands on this page run on your machine. They reach the daemon at the dashboard’s address, which you give piquelctl once per shell:

    export PIQUELD_URL=https://piqueld.<tailnet>.ts.net
    

    To keep the address for good, save it as a connection profile.

Sign piquelctl in

$ piquelctl login
Open https://piqueld.<tailnet>.ts.net/dashboard/auth#device=KQ7M-3XHT
Check the page shows code: KQ7M-3XHT
The daemon sees this login coming from: 100.101.102.103
Waiting for passkey login…
alice (01a16b4b-ef93-7971-b6f6-e8359d3c4d52)

Open the link, sign in with your passkey, and approve the login. The link already carries the code, so you never have to type or copy the code: creating your account walks through it. piquelctl then prints the account it signed in as. This works over SSH too, since the browser can run on another machine than the terminal.

Anyone who can reach the daemon can start a login, and approving it gives their terminal your account’s access. Only approve a code from a terminal you started yourself, whose address matches Requested from. Behind your own reverse proxy, every login shows the proxy’s address: check that the code is the one your terminal printed. Approving a login never creates an account: new people need an invitation first.

On the daemon’s host, root and the daemon’s user need no login: they act as the host operator.

Check who you’re signed in as:

$ piquelctl whoami
alice (01a16b4b-ef93-7971-b6f6-e8359d3c4d52)
  admin on every application

The login lasts 30 days. Then run piquelctl login again.

Ask for less access

A login can do less than your account, which suits a shared or less trusted machine:

piquelctl login --preset read-only

--preset, --permission and --app choose the access, as in give an account access. Here, --app only takes application IDs, such as app-01a16b4c59db7202b51fefb283dee86b, which piquelctl app list prints: names can’t be looked up before you’re signed in.

The approval page shows the access asked for under Access, and you can only approve access you hold. whoami then shows limited credential next to your account. Like a token, a limited login can’t create credentials.

Sign in over plain HTTP

piquelctl refuses to send credentials to a remote daemon over plain HTTP. If something else already encrypts the connection, such as Tailscale, allow it with --allow-insecure-http, on every command:

piquelctl --allow-insecure-http --url http://<tailnet-host>:7845 login

This doesn’t encrypt anything itself. HTTPS, localhost and the daemon’s Unix socket need no flag. Passkeys still only work at the dashboard’s HTTPS address.

Saved logins

login saves its credential in $XDG_CONFIG_HOME/piqueld/credentials.json, by default ~/.config/piqueld/credentials.json, which only you can read. Set PIQUELD_CREDENTIALS_FILE to use another file. Connection profiles never hold credentials.

Logins are saved per daemon address and account. The daemon’s Unix socket and its network address count as different addresses, and so do two spellings of one address, such as localhost and 127.0.0.1. Your most recent login is the default for its address. To use another saved account, add --account with its username or ID:

$ piquelctl --account carol whoami
carol (01a16b4c-8584-7390-a0cb-b6b2c348ec20)
  apps:read on blog
  logs:read on blog
  events:read on blog

--account matches the username exactly as it was when you logged in. When you rename an account with piquelctl account update, its saved login on that daemon is renamed too. After a rename on the dashboard, select the login by ID, or log in again.

piquelctl logout revokes the login and removes it from the file. With --account, it signs that account out.

piquelctl also creates credentials.json.lock next to the file. Don’t delete it while login or logout is running.

Create an API token

A token lets a script act as your account without a passkey. It acts with its own grants, limited on every request by your account’s current access: lowering your access lowers your tokens’ access too.

  1. On the Accounts page, open your own account, then its Access tab, and select New token under Sessions and API tokens.
  2. Choose the token’s access: clear Application permissions apply to every application, including future ones and select applications to limit it, then choose a Preset or permissions. Effective access shows what the token will be able to do.
  3. Enter a New token name, such as ci.
  4. Under Expires in days (blank: never), keep 90 or enter another number of days.
  5. Select Create token.
  6. At the top of the page, select Copy next to the new token, then Done. The token isn’t shown again.

The New API token dialog: the deploy preset limited to blog, so the effective access is apps:read and apps:deploy on blog, named ci and expiring in 30 days

With piquelctl:

$ piquelctl token create ci --preset deploy --app blog --days 30
pqd_0UJj…

It prints the token, which starts with pqd_ so that secret scanners can find leaked ones. piqueld only keeps a hash of it.

  • --preset, --permission and --app choose its access, from the access you hold.
  • --days sets its lifetime, 90 days by default. --no-expiry makes it last until you revoke it.

piquelctl token create lists all its options.

The daemon’s auth.max_token_days setting can limit how long new tokens last. With a limit, tokens without expiry are refused, and so are tokens that outlive it.

What tokens can’t do

Tokens, and limited logins, can’t create credentials: no tokens, passkeys, invitations, enrollment links or CLI login approvals. They can’t give anyone access, and they can’t change their own account. They can revoke themselves. So a leaked token never outlives or exceeds its own limits.

Without accounts:manage, a token only sees itself. piquelctl account list shows its account with the token’s own access, and piquelctl token list shows the token alone. Your passkeys and your other sessions and tokens stay hidden from it.

A token with accounts:manage can still edit other accounts’ profiles and passkeys, revoke their sessions and tokens, and delete them, within its access. It can’t change their grants.

Use a token in automation

Give the token to the script in PIQUELD_TOKEN, from your CI’s secret store:

export PIQUELD_URL=https://piqueld.<tailnet>.ts.net
export PIQUELD_TOKEN=pqd_…
piquelctl app deploy blog --yes

PIQUELD_TOKEN takes precedence over saved logins. Don’t put tokens in connection profiles or in your Nix configuration. HTTP API clients send it as Authorization: Bearer pqd_….

With PIQUELD_TOKEN set, piquelctl logout revokes that token and leaves saved logins alone.

Bind a token to tailnet devices

When piqueld has its own tailnet node, or a dashboard hostname, you can bind a token to a tailnet user or tag. A leaked CI token is then useless outside your tailnet’s CI runners.

On the dashboard, fill in Tailnet user or tag (optional, e.g. tag:ci) when you create the token. Its row then shows Only from tailnet tag:ci.

With piquelctl, add --tailnet:

piquelctl token create ci --preset deploy --app blog --tailnet tag:ci
piquelctl token create laptop --preset read-only --tailnet [email protected]

piqueld then accepts the token only through its tailnet node, or that hostname, from a device signed in as that user or carrying that tag. It checks on every request, and again when a command started with app exec begins, so removing a tag or signing a device out takes effect at once. Anywhere else, including the Unix socket and other devices, the token is refused with 401 tailnet_binding_mismatch, and the refusal is recorded in the audit trail.

Tagged devices belong to no user, so a user binding never matches them: bind their tokens to a tag. Logins can’t be bound.

Revoke a token

On the dashboard, select Revoke next to the token, under Sessions and API tokens on your account’s Access tab.

With piquelctl, list your sessions and tokens, then revoke one by its ID:

$ piquelctl token list
…
01a16b4c-3504-7053-b78f-e68736555bdf cli piquelctl (expires at Unix 1795393518)
  the account's full access
…
01a16b4e-6995-79b2-bfb1-0f72630a4d96 token ci (expires at Unix 1795393663)
  apps:read on blog
  apps:deploy on blog
$ piquelctl token revoke 01a16b4e-6995-79b2-bfb1-0f72630a4d96
Info: revoked 01a16b4e-6995-79b2-bfb1-0f72630a4d96

Your logins are listed as piquelctl, and browser sessions as Browser.

Deleting an application removes every grant on it. A token or limited login left with no grant at all can do nothing, and expires a minute later. That minute lets piquelctl app delete, run with such a token, see the deletion finish.

How long credentials last

CredentialLasts
Browser session7 days, or until it goes 24 hours unused
piquelctl login30 days
API token90 days by default; any number of days, or no expiry, within auth.max_token_days
Host operator browser session12 hours

piqueld never renews a credential. When one expires, sign in again or create a new token.

Troubleshooting

  • “remote HTTP authentication requires HTTPS or an explicit insecure HTTP opt-in”. Use the dashboard’s HTTPS address, or --allow-insecure-http.
  • “–app “blog” must be an application ID when signing in“. Pass the ID from piquelctl app list instead.
  • Approving a login is refused with permission_exceeded. The login asks for access your account doesn’t hold. Start it again with less access.
  • “login expired; run piquelctl login again”. The code is valid for 10 minutes. Run piquelctl login again.
  • Starting a limited login fails with request_body_too_large. The access it asks for is over 16 KiB, which fits a preset on a few dozen applications. Ask for every application instead of listing them.
  • “token lifetime exceeds this installation’s auth.max_token_days”. Pass a --days within the limit. The limit also applies to the default 90 days.
  • Creating a bound token answers “tailnet bindings need the daemon’s tailnet node (tailscale.enabled) or a dashboard hostname (ingress.private.dashboard_hostname)”. Turn on piqueld’s tailnet node or set a dashboard hostname.
  • A bound token is refused with tailnet_binding_mismatch. Use it through piqueld’s tailnet address, from a device with that user or tag. tailnet_unavailable (503) means tailscaled didn’t answer in time: retry.
  • “credentials file must be readable only by its owner (chmod 600)”. Run chmod 600 on the credentials file.
  • “the daemon predates limited tokens” or “it predates limited logins”. The daemon is too old to limit access, so piquelctl refuses rather than getting full access. Upgrade it.