# pcctl command reference

> Every pcctl command, flag, default, environment variable and local file, with what each command needs (the admin certificate, --repo, or --repo or --via) and an example.

`pcctl` is the only way to manage a PowerCloud cluster: there is no shell or SSH on the nodes. This page lists every command `pcctl` has, with its exact synopsis, flags and defaults, what it needs to run, and an example. It includes commands that `pcctl`'s own usage text leaves out.

For task-oriented guides, see [Quickstart](https://powercloud.unidatum.com/docs/quickstart.md), [Storage](https://powercloud.unidatum.com/docs/storage.md) and [Managing nodes](https://powercloud.unidatum.com/docs/nodes.md). Terms such as cluster, repository, location and node are defined in [Concepts](https://powercloud.unidatum.com/docs/overview.md). `pcctl` runs on Linux and macOS, on amd64 and arm64 (see [Install a node](https://powercloud.unidatum.com/docs/install.md)); there is no Windows build.

## Conventions

### Command line shape

```sh
pcctl [GLOBAL OPTIONS] COMMAND [SUBCOMMAND] [FLAGS] [ARGUMENTS]
```

- **Global options come before the command.** `pcctl --repo LOC put a b` works; `pcctl put a b --repo LOC` does not.
- **A command's flags come before its positional arguments.** `pcctl app rm --keep-data home-1 web` works; flags after the first positional argument are taken as arguments. The exceptions are noted in each entry (`node peer-unlock`, `node rm --erase`, `node upgrade --commit`, `rotate finish --force`).
- **`-flag` and `--flag` are the same**, and a value can be given as `--flag value` or `--flag=value`. Boolean flags take no value (`--dry-run`), or `--flag=false`.
- **Repeatable flags** (marked "repeatable") are given once per value: `--env A=1 --env B=2`.
- `pcctl -h` prints the usage summary and global flags. Running `pcctl` with no command prints it and exits with status 2. Any other failure prints `pcctl: ` and the error on stderr and exits with status 1.

### What a command needs

Each entry says what the command needs:

| Needs | Meaning |
| --- | --- |
| **certificate** | Only the admin certificate saved by `login`, and the node addresses in the local `nodes.json`. No passphrase, no storage access. `--via` is ignored. So is `--repo`, except by `console` (which then holds the keys) and the `s3` subcommands (which then ask for the passphrase although only `s3 enable` and `s3 key add` use it). |
| **--repo** | The storage locations themselves, with their credentials, and the passphrase. `--via` is refused. These commands work on each location separately. |
| **--repo or --via** | The passphrase, and the storage either directly (`--repo`) or through a node that serves it (`--via NAME`, which also needs the admin certificate). |

Commands that need the passphrase unlock the cluster's keys on your machine. With `--via`, the node only relays encrypted blobs and never sees a key.

## Global options

| Option | Environment | Default | What it does |
| --- | --- | --- | --- |
| `--repo LOC[,LOC...]` | `POWERCLOUD_REPO` | none | The cluster's storage locations, comma-separated. Every write is replicated to all of them. See [Location syntax](#location-syntax). |
| `--via NODE` | `POWERCLOUD_VIA` | none | Reach the storage through this node instead of `--repo` (the node must serve it; see `node storage`). A flag beats the other's environment variable: `--via` with `POWERCLOUD_REPO` set goes through the node, and `--repo` with `POWERCLOUD_VIA` set uses the locations. If both flags are given, `--repo` wins. |
| `--quorum N` | | majority | How many locations must accept a write for it to succeed. `node storage` passes it to the node. |

The flag overrides the environment variable. For example:

```sh
export POWERCLOUD_REPO=/srv/pc,s3://my-bucket/pc
pcctl ls photos/
pcctl --via home-1 ls photos/
```

### Location syntax

A location in `--repo` (and in `node storage`) is one of:

| Form | What it is |
| --- | --- |
| `/path/to/dir` or `./dir` | A local directory. |
| `s3://BUCKET[/PREFIX][?OPTIONS]` | An S3-compatible bucket, optionally under a prefix. |
| `node://NAME` | A replica on an enrolled node's encrypted disk, reached with the admin certificate. |
| `ec:K:LOC;LOC;...` | Erasure coding over the listed locations: each blob is split into `K` data shards plus parity shards, one per location (`1 <= K <` number of locations `<= 255`). A write needs `K+1` locations (or all, if fewer). The inner locations can be any of the forms above, but not another `ec:`. |

S3 options, joined with `&`:

| Option | Default | Meaning |
| --- | --- | --- |
| `endpoint=URL` | AWS (`https://s3.REGION.amazonaws.com`) | The provider's endpoint, `http(s)://HOST[:PORT]` with no path. Setting it turns on path-style requests. |
| `region=R` | `us-east-1` | The region to sign requests for (`auto` for Cloudflare R2). |
| `creds=NAME` | `AWS` | Which credentials to read from the environment: `NAME_ACCESS_KEY_ID`, `NAME_SECRET_ACCESS_KEY` and, optionally, `NAME_SESSION_TOKEN`, with `NAME` upper-cased. |
| `path_style=BOOL` | `false` without `endpoint`, `true` with | Path-style requests. A bucket name with dots needs `path_style=true` over HTTPS. |

Quote locations that contain `&` or `;`:

```sh
export R2_ACCESS_KEY_ID=... R2_SECRET_ACCESS_KEY=...
pcctl --repo '/srv/pc,s3://pc?endpoint=https://ACCOUNT.r2.cloudflarestorage.com&region=auto&creds=r2' ls
pcctl --repo 'ec:4:s3://a/pc;s3://b/pc;s3://c/pc;s3://d/pc;s3://e/pc;s3://f/pc' ls
```

### The passphrase

Commands that need the cluster's keys read the passphrase from the terminal without echoing it, or from an environment variable for automation:

- `POWERCLOUD_PASSPHRASE`: the cluster passphrase. With it, `init` also skips the "Repeat passphrase" prompt.
- `POWERCLOUD_RECOVERY_KEY`: unlock with the recovery key that `init` (or `rotate`) printed, instead of the passphrase. When it is set, no passphrase is asked for.
- `POWERCLOUD_NEW_PASSPHRASE`: the new passphrase for `rotate`. Without it, `rotate` asks twice on the terminal.

Without a terminal and without these variables, the command fails with `no terminal for passphrase`.

## Environment variables

Every variable `pcctl` reads:

| Variable | Used by | Meaning |
| --- | --- | --- |
| `POWERCLOUD_REPO` | all `--repo` commands | Default for `--repo`. |
| `POWERCLOUD_VIA` | all `--via` commands | Default for `--via`. |
| `POWERCLOUD_PASSPHRASE` | commands that unlock the cluster | The cluster passphrase (new passphrase for `init`). |
| `POWERCLOUD_RECOVERY_KEY` | commands that unlock the cluster | Unlock with the recovery key instead. |
| `POWERCLOUD_NEW_PASSPHRASE` | `rotate` | The new passphrase. |
| `POWERCLOUD_CONFIG` | everything | The local configuration directory (see [Local files](#local-files)). |
| `POWERCLOUD_UPGRADE_WAIT` | `node upgrade` | How long to wait for the node to come back on the new image, as a duration such as `20m`. Default `10m`. |
| `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` | S3 locations without `creds=` | S3 credentials. |
| `NAME_ACCESS_KEY_ID`, `NAME_SECRET_ACCESS_KEY`, `NAME_SESSION_TOKEN` | S3 locations with `creds=name` | S3 credentials for that location. `node storage` sends the ones its locations name to the node. |
| `DOCKER_CONFIG` | `app deploy --image`, `cluster app deploy --image` | Directory holding the registry logins (`config.json`, as `docker login` writes it). Default `~/.docker`. |
| `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` | S3 locations, image pulls | The usual proxy settings. Connections to nodes never use a proxy. |
| `POWERCLOUD_TEST_NODE_CERT_LIFETIME` | `node enroll`, `node provision`, `node renew` | A test hook that shortens node certificates (a duration). Don't set it on a real cluster: nodes drop out of the mesh when their certificates expire. |

## Local files

`pcctl` keeps its state in `$POWERCLOUD_CONFIG`, or else in the user configuration directory plus `powercloud`: `~/.config/powercloud` (or `$XDG_CONFIG_HOME/powercloud`) on Linux and `~/Library/Application Support/powercloud` on macOS. The directory is mode 0700 and the files 0600.

| File | Written by | Contents |
| --- | --- | --- |
| `admin.key`, `admin.crt` | `login`, renewed by any command that unlocks the cluster | The admin certificate (30 days) and its key. |
| `ca.crt` | the same | The cluster CA certificate. |
| `nodes.json` | `node enroll`, `node provision --addr`, `node addr`; `node rm` removes entries | Each known node's name, node ID and API address. Certificate-only commands find nodes here. |
| `amd-kds/` | attestation checks | Cached AMD certificates for SEV-SNP verification. |

## Cluster storage

### init

```sh
pcctl --repo LOC[,LOC...] init
```

Creates a new cluster in the given locations: a new root key, protected by the passphrase you choose (asked twice) and by a recovery key, which it prints once. Write the recovery key down and store it offline. It refuses if a cluster already exists there, and also if any location can't be reached (it might hold an existing cluster).

Needs: **--repo**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc init
```

```text
Initialized cluster 92283421c94b924005629e1405c47c54

RECOVERY KEY (write it down and store it offline — it is shown only once):

    PPXR-AHNU-ZWUQ-WODE-USI2-C2TZ-QI4N-7UFI

Without your passphrase or this recovery key, no one — including you — can read this data.
```

### put

```sh
pcctl --repo LOC,... put NAME FILE
```

Encrypts `FILE` and stores it as object `NAME`, replicated to every location. Prints the size and chunk count.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc put photos/cat.jpg ./cat.jpg
```

```text
stored photos/cat.jpg (48213 bytes, 1 chunks)
```

### get

```sh
pcctl --repo LOC,... get NAME [FILE]
```

Reads and decrypts object `NAME` into `FILE` (created with mode 0600, replaced if it exists), or to standard output without `FILE`. Every chunk is authenticated; a tampered copy is an error, not altered data.

Needs: **--repo or --via**.

```sh
pcctl --via home-1 get photos/cat.jpg ./out.jpg
```

### ls

```sh
pcctl --repo LOC,... ls [PREFIX]
```

Lists objects whose names start with `PREFIX` (all without it): size in bytes, modification time in local time, name.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc ls photos/
```

```text
48213  2026-10-28 14:02  photos/cat.jpg
```

### rm

```sh
pcctl --repo LOC,... rm NAME
```

Deletes object `NAME`. Its chunks stay until `gc` reclaims them.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc rm photos/cat.jpg
```

### check

```sh
pcctl --repo LOC,... check [--verify]
```

Compares every location and reports, per location, its blobs and how many are missing, stale or corrupt. Changes nothing. Exits with an error if the locations differ (`run 'pcctl repair'`) or if some object has data missing from every reachable location.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--verify` | off | Download and authenticate every copy of every chunk, instead of comparing listings. |

Needs: **--repo**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc check --verify
```

```text
1 objects, 0 deleted, 1 chunks in use, 0 unreferenced (reclaimed by gc)

LOCATION              BLOBS  MISSING  STALE  CORRUPT  REPAIRED  STATUS
/srv/pc               3      0        0      0        0         ok
s3://my-bucket/pc     3      0        0      0        0         ok
```

### repair

```sh
pcctl --repo LOC,... repair [--verify]
```

Brings every location up to date: copies missing blobs, and replaces stale or corrupt copies with a good one. Erasure-coded locations first rebuild missing shards from their own other shards. Use it after a provider was down, or after adding a location. Same flag as `check`.

Needs: **--repo**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc,node://home-1 repair
```

### gc

```sh
pcctl --repo LOC,... gc [--grace 24h] [--dry-run]
```

Deletes chunks that no object references any more, from every location, once they have been unreferenced for the grace period.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--grace DURATION` | `24h` | How long a chunk must stay unreferenced before it is deleted. |
| `--dry-run` | off | Report what would be deleted, delete nothing. |

Needs: **--repo**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc gc --dry-run
```

```text
1 chunks in use, would delete 0, 0 unreferenced waiting out the 24h0m0s grace period
```

## Nodes

See [Enroll a node](https://powercloud.unidatum.com/docs/enroll.md), [Unlocking nodes after a reboot](https://powercloud.unidatum.com/docs/unlock.md), [Upgrades and releases](https://powercloud.unidatum.com/docs/upgrades.md) and [Managing nodes](https://powercloud.unidatum.com/docs/nodes.md) for the tasks behind these commands.

### status

```sh
pcctl status
```

Asks every node in `nodes.json` about itself, in parallel (10 seconds each), and prints one line per node (state, architecture, version, uptime, CPU, memory, mesh peers reached, apps running, storage locations served, days left on its certificate), then a `needs attention` list or `all nodes healthy`.

Needs: **certificate**.

```sh
pcctl status
```

### console

```sh
pcctl console [--port N] [--listen ADDR [--host NAME]...] [--no-open]
pcctl --repo LOC,... console [flags]
```

Serves the web console on this machine and opens a browser at its one-time link: live status, node details, app logs, and deploying, restarting and removing apps. Started with `--repo`, it holds the cluster's keys and can also publish and remove cluster apps. Runs until Ctrl-C.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--port N` | any free port | Port to listen on. |
| `--listen ADDR` | `127.0.0.1` | IP address to listen on. Another than loopback lets other machines in, over plain HTTP; `0.0.0.0` is every address. |
| `--host NAME` | with `--listen`: this machine's name and addresses | A name or address other machines reach the console by. Repeatable. |
| `--no-open` | off | Don't open a browser; just print the link. |

Needs: **certificate**; with `--repo`, also the passphrase. Given `--via` (and no `--repo`), it runs without the keys, as if neither were given.

```sh
pcctl console --port 8080 --no-open
```

### node ls

```sh
pcctl node ls
```

Lists the nodes in this machine's `nodes.json` (name, node ID, API address) without contacting them.

Needs: **certificate** (reads only the local file).

```sh
pcctl node ls
```

### node info

```sh
pcctl node info NAME
```

Shows one node's details: IDs, architecture, kernel, boot and lockdown, state volume, certificate expiry, cluster CA, confidential computing, image and release, storage served and last sync, TPM and peer unlock, membership and revocation list serials, and its mesh peers with endpoint, handshake age and API round-trip time.

Needs: **certificate**.

```sh
pcctl node info home-1
```

### node enroll

```sh
pcctl --repo LOC,... node enroll HOST[:PORT] CODE NAME
```

Joins a new node to the cluster with the enrollment code shown on its console, gives it the key for its encrypted state volume, records it in the cluster registry and in `nodes.json`, and publishes a new membership list to every node. `PORT` defaults to 7443. The node's mesh endpoint is set to the same host on port 51820 (unless the host is `localhost`). Writes an admin certificate if none is saved. Fails if a node named `NAME` is already enrolled.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node enroll 192.0.2.10 XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX home-1
```

### node provision

```sh
pcctl --repo LOC,... node provision [--addr HOST[:PORT]] [--endpoint IP:PORT] NAME FILE
```

Creates a node's whole identity ahead of time and writes it to `FILE` as a join bundle, for a machine that joins at first boot with nobody at its console (cloud user data, or a QEMU fw_cfg file). Registers the node and publishes a membership list that includes it. `FILE` must not exist. The bundle holds the node's private keys and disk key: delete it once the node is up. Prints how to pass it to AWS, Google Cloud, Azure and QEMU.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--addr HOST[:PORT]` | none | Where `pcctl` reaches the node's API, if known. Without it, run `node addr` once the node is up. |
| `--endpoint IP:PORT` | none | Where peers reach the node's WireGuard port, if it is public. `HOST:PORT` is accepted too. |

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node provision web-1 web-1.join
```

### node addr

```sh
pcctl --repo LOC,... node addr NAME HOST[:PORT]
```

Records where `pcctl` reaches a node's API (`PORT` defaults to 7443), in the registry and in `nodes.json`. If the node's mesh endpoint was empty or derived from its old address, it is set to the new host on port 51820 and a new membership list is published. Also the way to add an existing node to `nodes.json` on another admin machine.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node addr cloud-1 203.0.113.50
```

### node unlock

```sh
pcctl --repo LOC,... node unlock NAME CODE [HOST[:PORT]]
```

Unlocks a rebooted node's encrypted state volume with the unlock code shown on its console. `pcctl` pins the node's temporary key from the code, checks the node and volume IDs against the registry, verifies a SEV-SNP attestation if the node offers one (and requires one if the node was attested before), and only then sends the key. `HOST[:PORT]` reaches the node at another address for this unlock only. During a key rotation, a node not yet moved is unlocked with the previous keys.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node unlock home-1 XXXX-XXXX-XXXX-XXXX   # the code from the node's console
```

### node tpm

```sh
pcctl --repo LOC,... node tpm NAME on|off
```

Turns unattended unlock with the node's TPM on or off. With Secure Boot and a TPM 2.0, the node then unlocks its state at boot by itself, only on the same machine with the same Secure Boot keys. See [Unlocking nodes after a reboot](https://powercloud.unidatum.com/docs/unlock.md).

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node tpm home-1 on
```

### node peer-unlock

Not in `pcctl`'s usage text.

```sh
pcctl --repo LOC,... node peer-unlock NAME on [--threshold K]
pcctl --repo LOC,... node peer-unlock NAME off
```

Turns peer unlock on or off: the node splits a key to its state volume into shares held by its peers, and after a reboot unlocks itself while `K` of them answer. `--threshold` comes after `on`. All peers must be reachable over the mesh to turn it on. Turn it on again after adding or moving nodes or rotating the keys. See [Unlocking nodes after a reboot](https://powercloud.unidatum.com/docs/unlock.md).

| Flag | Default | Meaning |
| --- | --- | --- |
| `--threshold K` | a majority of the peers | How many peers must answer. |

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node peer-unlock home-1 on --threshold 2
```

### node attest

```sh
pcctl node attest NAME
pcctl --repo LOC,... node attest NAME sev-snp|off
```

With only `NAME`: asks the running node for a fresh AMD SEV-SNP attestation, bound to a random nonce and the node's key, and verifies it against AMD's certificate chain. Needs: **certificate**.

With `sev-snp` or `off` (not in `pcctl`'s usage text): sets or clears the registry's requirement that the node present an SEV-SNP attestation at every unlock. `node unlock` sets it by itself the first time a node attests; use `off` when a node has moved to a machine without SEV-SNP on purpose. Needs: **--repo or --via**.

```sh
pcctl node attest cvm-1
```

```text
node cvm-1: AMD SEV-SNP verified (...); launch measurement ...
```

### node rm

```sh
pcctl --repo LOC,... node rm [--erase] NAME
```

Removes a node: revokes its certificate on every node with a CA-signed revocation list, drops it from the mesh and from the registry, and removes it from `nodes.json`. It can no longer be unlocked. `--erase` must come right after `rm`.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--erase` | off | First have the node crypto-erase its state volume and power off. If the node can't be reached, nothing is removed; run without `--erase` to remove it anyway. |

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc node rm --erase home-1
```

### node renew

```sh
pcctl --repo LOC,... node renew [NAME...]
```

Renews node certificates (they last a year). Without names, renews every registered node whose certificate expires within 30 days or has expired; named nodes are renewed regardless. Works on expired certificates. Prints one line per node.

Needs: **--repo or --via**.

```sh
pcctl --via home-1 node renew
```

### node storage

```sh
pcctl --repo LOC,... node storage [--sync 1h|off] NAME [LOC,...]
```

Lets node `NAME` serve the cluster's storage, so admins can use `--via NAME`. Sends the node the locations (`--repo`'s, or the ones given, for a node that reaches them by other addresses), the write quorum (`--quorum`) and the S3 credentials those locations name, taken from your environment. The node keeps them on its encrypted disk and refuses locations it can't reach or that belong to another cluster. Only `s3://` and `node://` locations (and `ec:` over them) can be served.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--sync DURATION` | `1h` | How often the node copies blobs some locations lack from one that has them; `off` turns it off. |

Needs: **--repo** (refused with `--via`).

```sh
pcctl --repo 's3://a/pc?creds=r2,s3://b/pc?creds=b2' node storage --sync 30m home-1
```

### node sync

```sh
pcctl node sync NAME
```

Has a node that serves storage copy missing blobs between its locations now, and prints the result. Exits with an error (`sync incomplete`) if any copy failed.

Needs: **certificate**.

```sh
pcctl node sync home-1
```

### node upgrade

```sh
pcctl node upgrade NAME FILE.efi
pcctl node upgrade --commit NAME
```

Installs a PowerCloud node image (`powercloud-VERSION-ARCH.efi` from a PowerCloud release, or `out/powercloud-ARCH.efi` built from source) on one node, A/B: the node writes it beside the running image and reboots into it once. When `pcctl` reaches the node on the new image, it commits it as the default. A node that doesn't come back on it reverts at its next boot. `pcctl` waits up to 10 minutes (`POWERCLOUD_UPGRADE_WAIT`). `--commit` (in that position) commits an image on trial, for example after you had to unlock the node first. See [Upgrades and releases](https://powercloud.unidatum.com/docs/upgrades.md).

Needs: **certificate**.

```sh
pcctl node upgrade home-1 powercloud-vYYYY.MM.DD-amd64.efi
```

### node poweroff, node reboot

```sh
pcctl node poweroff NAME
pcctl node reboot NAME
```

Powers off or reboots a node. After a reboot the node is locked until unlocked (by `node unlock`, its TPM or its peers).

Needs: **certificate**.

```sh
pcctl node reboot home-1
```

## Mesh

### mesh endpoint

```sh
pcctl --repo LOC,... mesh endpoint NAME IP:PORT
```

Sets where peers reach node `NAME`'s WireGuard port, and publishes a new membership list. `HOST:PORT` (a DNS name, resolved by the nodes every minute) is accepted, and `''` clears the endpoint, for a node behind NAT.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc mesh endpoint home-1 home.example.net:51820
```

### mesh sync

```sh
pcctl --repo LOC,... mesh sync
```

Asks every reachable node for its WireGuard key and records any that changed, then publishes the revocation list (if anything was ever revoked) and the membership list to every node. For a node whose key changed or that missed an update.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc mesh sync
```

### mesh api

Not in `pcctl`'s usage text.

```sh
pcctl --repo LOC,... mesh api NAME HOST:PORT
```

Sets where peers reach node `NAME`'s API outside the mesh, which locked nodes use for peer unlock. By default that is the address `pcctl` uses (unless it is on this machine only), else the mesh endpoint's host. `''` returns to the default. Publishes a new membership list. Turn peer unlock on again afterwards, as holders' addresses are copied when it is turned on.

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc mesh api home-2 192.0.2.11:7443
```

## Apps

Apps are static Linux binaries or container images that a node runs as sandboxed services. All `app` commands talk to one node and need only the **certificate**. See [Apps on a node](https://powercloud.unidatum.com/docs/apps.md).

### app deploy

```sh
pcctl app deploy [OPTIONS] NODE NAME FILE [-- ARGS...]
pcctl app deploy --image NAME[:TAG][@DIGEST] [OPTIONS] NODE NAME [-- COMMAND...]
```

Deploys (or replaces) app `NAME` on node `NODE`. For a binary, everything after `FILE` is passed to it as arguments, with or without `--`. For an image, `pcctl` pulls it on your machine for the node's architecture and sends the flattened files; arguments after `--` replace the image's command. Prints the node's apps afterwards.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--mem SIZE` | `256M` | Memory limit, with `K`, `M` or `G`. |
| `--cpu N` | none | CPU limit in CPUs, such as `0.5`. A microVM gets one vCPU per CPU, rounded up. |
| `--procs N` | 256 | Process and thread limit. |
| `--env K=V` | | Environment variable. Repeatable. |
| `--port NODE:APP` | | Publish the app's port `APP` on the node's port `NODE`; `NODE:APP/udp` for UDP. Repeatable. |
| `--net host` | the app's own network | Share the node's network instead. |
| `--image REF` | | Deploy a container image instead of a binary. |
| `--link NAME:PORT` | | Let the app reach app `NAME`'s port `PORT` by its name; `/udp` for UDP. Repeatable. |
| `--egress` | off | Let the app connect out, through the node (not to the node itself or other apps). |
| `--vm` | off | Run the app in a microVM, under a kernel of its own (nodes with KVM). |

```sh
pcctl app deploy --image nginx:alpine --port 80:80 home-1 site
```

### app ls

```sh
pcctl app ls NODE
```

Lists a node's apps: state, uptime, restarts, OOM kills, memory used and limit, CPU used and cap, seccomp, network and ports, last exit.

```sh
pcctl app ls home-1
```

### app logs

```sh
pcctl app logs NODE NAME [BYTES]
```

Prints the end of an app's output, up to `BYTES` (default 65536).

```sh
pcctl app logs home-1 site 4096
```

### app restart

```sh
pcctl app restart NODE NAME
```

Restarts an app. Not in `pcctl`'s usage summary. Prints nothing on success.

```sh
pcctl app restart home-1 site
```

### app rm

```sh
pcctl app rm [--keep-data] NODE NAME
```

Removes an app from a node.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--keep-data` | off | Keep the app's data directory. |

```sh
pcctl app rm --keep-data home-1 site
```

## Cluster apps

Cluster apps are apps the nodes place among themselves and move when a node goes away. All need **--repo or --via**. See [Cluster apps](https://powercloud.unidatum.com/docs/cluster-apps.md).

### cluster app deploy

```sh
pcctl --repo LOC,... cluster app deploy [--replicas N | --exclusive] [--nodes a,b] [--storage] [--volume] [APP OPTIONS] NAME FILE [-- ARGS...]
pcctl --repo LOC,... cluster app deploy [--replicas N] [--nodes a,b] [APP OPTIONS] --image REF NAME [-- COMMAND...]
```

Publishes (or replaces) cluster app `NAME`, signed by the cluster CA, and sends it to every node it reaches; others get it from their peers. A binary must be a static ELF for amd64 or arm64. An image is pulled once for each architecture among the nodes it may run on (amd64 if none is reached). Takes every `app deploy` option, plus:

| Flag | Default | Meaning |
| --- | --- | --- |
| `--replicas N` | 1 | How many nodes run the app. |
| `--nodes a,b` | any node | The nodes that may run it, comma-separated. |
| `--exclusive` | off | Run on at most one node at any time, through the nodes' consensus (needs `consensus init`). Only one replica. |
| `--storage` | off | Give the app an S3 endpoint at `127.0.0.1:9000` backed by the cluster's storage. |
| `--volume` | off | Keep the app's data directory in the cluster's storage, so it follows the app. Needs `--exclusive`. |

```sh
pcctl --repo /srv/pc cluster app deploy --replicas 2 --image nginx:alpine --port 80:80 site
```

### cluster app ls

```sh
pcctl --repo LOC,... cluster app ls
```

Lists the cluster apps: replicas, allowed nodes, where each runs now (and its state), and the source (binary or image, with `+storage`, `+volume`, `+exclusive`).

```sh
pcctl --via home-1 cluster app ls
```

### cluster app rm

```sh
pcctl --repo LOC,... cluster app rm NAME
```

Removes a cluster app from every node.

```sh
pcctl --repo /srv/pc cluster app rm site
```

## Consensus and key-value state

The nodes can form a Raft group that keeps a key-value state they agree on, while a majority of voters is up. See [Cluster apps](https://powercloud.unidatum.com/docs/cluster-apps.md) for what uses it.

### consensus init

```sh
pcctl --repo LOC,... consensus init [--nodes a,b,c]
```

Founds the group with a CA-signed document naming the first voters, and sends it to every node it reaches. Runs once per cluster; nodes enrolled later become voters by themselves. Warns that two voters tolerate no loss.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--nodes a,b,c` | every node | The first voters, comma-separated. |

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc consensus init
```

### consensus status

```sh
pcctl consensus status
```

Shows each node's view of the group: role, term, leader, applied index, number of keys, voters. Nodes without it show `not started`.

Needs: **certificate**.

```sh
pcctl consensus status
```

### kv

```sh
pcctl kv [--node NAME] [--stale] put KEY VALUE
pcctl kv [--node NAME] [--stale] get KEY
pcctl kv [--node NAME] [--stale] rm KEY
pcctl kv [--node NAME] [--stale] ls [PREFIX]
```

Reads and changes the consensus state through a node. Changes and (by default) reads need a majority of voters and fail after about 10 seconds without one. `get` prints the value; `ls` prints `KEY=VALUE` lines. The flags come before the subcommand.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--node NAME` | the first node (by name) that answers and has the consensus state | The node to ask. |
| `--stale` | off | Read the node's own copy, without a majority (may miss recent changes). |

Needs: **certificate**.

```sh
pcctl kv put greeting hello
pcctl kv --stale get greeting
```

## S3 gateway

The S3 gateway lets S3 clients use the cluster's storage through nodes. See [S3 gateway](https://powercloud.unidatum.com/docs/s3-gateway.md).

### s3 enable

```sh
pcctl --repo LOC,... s3 enable [--port 9443] [--name DNS]... [--ip ADDR]... NODE...
```

Starts the gateway on the nodes, with a certificate from the cluster CA (valid a year) for the node's own addresses plus the names and addresses given. Running it again renews the certificate. Warns if a node serves no storage (`node storage`).

| Flag | Default | Meaning |
| --- | --- | --- |
| `--port N` | 9443 | The port the gateway listens on. |
| `--name DNS` | | A DNS name clients reach the gateway by. Repeatable. |
| `--ip ADDR` | | An address clients reach the gateway at, besides the node's own. Repeatable. |

Needs: **--repo** (refused with `--via`).

```sh
pcctl --repo /srv/pc s3 enable --name s3.example.net home-1 cloud-1
```

### s3 disable

```sh
pcctl s3 disable NODE...
```

Stops the gateway on the nodes. Needs: **certificate**.

```sh
pcctl s3 disable home-1
```

### s3 key add

```sh
pcctl --repo LOC,... s3 key add [--bucket B]... [--read-only] NAME
```

Creates an access key named `NAME` and prints `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` once. Keys are kept in the nodes' consensus state, so `consensus init` must have been run.

| Flag | Default | Meaning |
| --- | --- | --- |
| `--bucket B` | all buckets | A bucket the key may use. Repeatable. |
| `--read-only` | off | The key may only read. |

Needs: **--repo** (refused with `--via`).

```sh
pcctl --repo /srv/pc s3 key add --bucket backups --read-only restic
```

### s3 key ls, s3 key rm

```sh
pcctl s3 key ls
pcctl s3 key rm NAME
```

`key ls` lists access keys: name, access key ID, buckets, read-only or read-write, creation date. `key rm` removes a key by name or access key ID; gateways refuse it as soon as their consensus state has the change. Needs: **certificate**.

```sh
pcctl s3 key rm restic
```

### s3 status

```sh
pcctl s3 status
```

Shows which nodes run the gateway: port, names, number of access keys, days left on its certificate, or the error it failed with. Needs: **certificate**.

### s3 ca

```sh
pcctl s3 ca
```

Prints the cluster CA certificate (PEM), for S3 clients to trust. Needs: **certificate**.

```sh
pcctl s3 ca > ca.pem
export AWS_CA_BUNDLE=ca.pem
```

## Releases

### release publish

```sh
pcctl --repo LOC,... release publish [--version V] IMAGE.efi...
```

Signs a release of the given node images (at most one per architecture, read from each image's header) and sends it to every node it reaches, with the image for that node's architecture. Nodes that are down get it from their peers. The nodes then upgrade themselves one at a time, and stop the rollout at the first node it fails on. See [Upgrades and releases](https://powercloud.unidatum.com/docs/upgrades.md).

| Flag | Default | Meaning |
| --- | --- | --- |
| `--version V` | the current UTC date and time, such as `2026-10-28T14:02Z` | The release name shown on nodes. |

Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc release publish --version vYYYY.MM.DD powercloud-vYYYY.MM.DD-amd64.efi powercloud-vYYYY.MM.DD-arm64.efi
```

### release status

```sh
pcctl release status
```

Shows each node's architecture, running image, the release it has (serial and name) and its state on it, including the node a halted rollout failed on. Needs: **certificate**.

## Keys and access

See [Admins, keys and revocation](https://powercloud.unidatum.com/docs/access.md).

### login

```sh
pcctl --repo LOC,... login
```

Issues a new admin certificate (valid 30 days) and writes it, with the CA certificate, to the configuration directory. Needs: **--repo or --via** (with `--via`, you must already hold a working admin certificate to reach the node).

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc login
```

### revoke-admins

```sh
pcctl --repo LOC,... revoke-admins
```

Revokes every admin certificate issued so far (for example one on a lost laptop), pushes the revocation list to every node, and issues you a new one. Other admins must run `login` again. Needs: **--repo or --via**.

```sh
pcctl --repo /srv/pc revoke-admins
```

### rotate

```sh
pcctl --repo LOC,... rotate
pcctl --repo LOC,... rotate finish [--force]
```

Replaces the cluster root key, after a passphrase or recovery key may have leaked. `rotate` asks for a new passphrase (or reads `POWERCLOUD_NEW_PASSPHRASE`), which must differ from the old one, prints a new recovery key, re-encrypts the store, deletes the data under the old keys from every location, and moves every reachable node to a new CA with its volume key rewrapped. Every location must be reachable. Run `rotate` again, with the new passphrase, to move nodes that were offline or to finish an interrupted rotation. `rotate finish` then stops nodes accepting the old CA and drops the old root key; it refuses while nodes are still on the old keys, unless `--force` (in that position) gives up on them, after which they must be removed and enrolled again. Moved nodes stop accepting the old CA by themselves 7 days after the rotation started.

Needs: **--repo** (refused with `--via`).

```sh
pcctl --repo /srv/pc,s3://my-bucket/pc rotate
pcctl --repo /srv/pc,s3://my-bucket/pc rotate finish
```

## Other

### version

```sh
pcctl version
```

Prints the version `pcctl` was built as: the release name (`vYYYY.MM.DD`) for a PowerCloud release, or `dev` when built from source with `go build ./cmd/pcctl`. Needs nothing.

## Commands missing from the usage text

`pcctl -h` summarises most commands. These exist in `pcctl` but are not in that summary, or appear there with fewer options:

| Command | Where it is described |
| --- | --- |
| `node peer-unlock NAME on [--threshold K]`, `node peer-unlock NAME off` | [node peer-unlock](#node-peer-unlock) |
| `node attest NAME sev-snp\|off` | [node attest](#node-attest) |
| `mesh api NAME HOST:PORT` | [mesh api](#mesh-api) |
| `app restart NODE NAME` | [app restart](#app-restart) |
| `app rm --keep-data`, `app logs NODE NAME BYTES` | [app rm](#app-rm), [app logs](#app-logs) |
| `app deploy` options `--procs`, `--port`, `--net`, `--image`, `--link`, `--egress`, `--vm` | [app deploy](#app-deploy) |
| `cluster app deploy` options `--exclusive`, `--storage`, `--volume` | [cluster app deploy](#cluster-app-deploy) |

## Quick lookup

| Command | Needs |
| --- | --- |
| `init`, `check`, `repair`, `gc`, `rotate`, `rotate finish`, `node storage`, `s3 enable`, `s3 key add` | --repo |
| `put`, `get`, `ls`, `rm`, `login`, `revoke-admins`, `node enroll`, `node provision`, `node addr`, `node unlock`, `node tpm`, `node peer-unlock`, `node attest NAME sev-snp\|off`, `node rm`, `node renew`, `mesh endpoint`, `mesh sync`, `mesh api`, `cluster app deploy\|ls\|rm`, `consensus init`, `release publish` | --repo or --via |
| `status`, `console`, `node ls`, `node info`, `node attest NAME`, `node sync`, `node upgrade`, `node poweroff`, `node reboot`, `app ...`, `consensus status`, `kv ...`, `s3 disable`, `s3 key ls`, `s3 key rm`, `s3 status`, `s3 ca`, `release status` | certificate |
| `version` | nothing |
