# Better Compute > An embeddable TypeScript control plane for GPU, CPU and sandbox capacity: nodes, reservations, bare-metal provisioning, and isolated VMs, authorized by Better IAM. Created by Sean Filimon. # bcd commands (/docs/daemon/commands) > Every bcd subcommand with its flags, what it does and its exit codes, plus the full list of bcd doctor checks with their statuses and fixes. `bcd` is one binary with one subcommand per mode or local task. This page lists every subcommand the code registers in `daemon/cmd/bcd/main.go`, its flags exactly as defined there, what it does, and how it exits. The [overview](/docs/daemon#modes) explains what each mode is for, [installing bcd](/docs/daemon/install) covers the install script that runs most of these for you, and [the config.toml reference](/docs/daemon/configuration) covers the file most of them read. When something goes wrong, start with `bcd doctor` (below) and [troubleshooting](/docs/daemon/troubleshooting). > **What has run where.** Every command is covered by Go unit tests, mostly against fakes (fake sysfs trees, a fake `nvidia-smi`, a fake `virsh`). `bcd join`, `bcd node`, `bcd helper`, `bcd sim` and `bcd update` run in `tests/e2e/bcd.test.ts` as a fakehw build against a real gateway. On real hardware, `bcd hardware`, `bcd doctor`'s GPU checks and an unprivileged `bcd node` have run on one RTX 5090 under WSL 2 (`tests/e2e/bcd-host-real.test.ts`; 0915ab7, f84f1f9, da60bbd). No release has been tagged, so no installed host has run any of them from a release binary. ## Usage and exit codes [#usage-and-exit-codes] ```text bcd [flags] bcd -h flags of one command bcd help the command list (also -h, -help, --help) bcd --version same as bcd version (also -version) ``` Every command uses Go's `flag` package: flags are `-name value` or `--name=value`, booleans are `--name` or `--name=false`. No command takes positional arguments. bcd never echoes an unexpected argument, so a token pasted on the command line does not reach a log: ```bash $ bcd join bcjt_jt_01... bcd join: takes no positional arguments bcd join reads the join token from BCD_JOIN_TOKEN, stdin or --token-file, never from arguments ``` An unknown command name is echoed only when it looks like one (lower-case letters and `-`, at most 32 characters); anything else prints `bcd: unknown command` alone. | Exit code | Meaning | | --------- | ------------------------------------------------------------------------------------------------------------------- | | `0` | Success, `-h`, or an agent mode stopped by SIGINT or SIGTERM | | `1` | Failure: a configuration that does not load, a refused join, a doctor check that failed, an agent that cannot start | | `2` | Usage error: an unknown command or flag, a positional argument, a missing token, a command this build does not have | | Environment variable | Read by | Meaning | | ---------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------- | | `BCD_JOIN_TOKEN` | `bcd join`, `bcd sim` | The join token; unset by bcd as soon as it is read. Redacted from support bundles | | `NODE_NAME` | `bcd node` on Kubernetes | The Kubernetes Node bcd runs on, as a DaemonSet sets it (see [bcd on Kubernetes](/docs/daemon/kubernetes)) | | `BCD_RENEW_AFTER` | fakehw builds only | A Go duration: renew this soon after start (end-to-end tests) | | `BCD_FAKEHW_SEED`, `BCD_FAKEHW_TPM_KEYS`, `BCD_FAKEHW_TPM_EK_CA` | fakehw builds only | The fake machine's seed and simulated TPM | ## The commands [#the-commands] | Command | Runs as | Reads `config.toml` | Long-running | | --------------------------- | ------------------------------- | --------------------------- | ------------ | | [`node`](#bcd-node) | root, or the `[helper] user` | yes | yes | | [`site`](#bcd-site) | root (its own unit) | yes | yes | | [`ramdisk`](#bcd-ramdisk) | root, inside the PXE initramfs | no (`bc.*` boot parameters) | yes | | [`helper`](#bcd-helper) | root | yes | yes | | [`sim`](#bcd-sim) | anyone; fakehw builds only | yes | yes | | [`join`](#bcd-join) | root | yes, or writes it | no | | [`status`](#bcd-status) | whoever owns the status socket | no | no | | [`doctor`](#bcd-doctor) | root for the full set of checks | yes | no | | [`hardware`](#bcd-hardware) | root for DMI serials | no | no | | [`update`](#bcd-update) | root on a helper node | yes | no | | [`version`](#bcd-version) | anyone | no | no | Two names exist that are not for operators: `__bcx-exec` is bcd re-executing itself as the [plugin sandbox's shim](/docs/extending/drivers#the-plugin-sandbox), and `helper-call` exists only in builds with the `helpere2e` tag, where the root helper end-to-end test sends one JSON request from standard input to `[helper] socket`. A release build has neither in its command list. ## bcd node [#bcd-node] Runs the node agent: the session to the gateway, the journal, inventory, NodeSpec convergence, commands, tunnels, telemetry and self-update. | Flag | Default | Meaning | | ---------- | ---------------------- | ---------------------- | | `--config` | `/etc/bcd/config.toml` | The configuration file | At start it loads the configuration, refuses `dev_insecure_h2c` in a host build, loads the identity from `data_dir` (`identity.json` and `node.key`), opens the journal, starts the [bcx plugins](/docs/extending/drivers) of `[plugins] dir`, checks `[kubernetes]`, prepares self-update, switches to the WebSocket fallback when `[websocket] url` is set, serves `bcd status` on `socket_path`, and with `[imds] listen` serves the instance metadata service. With `[helper] socket` it sends its privileged work to `bcd helper` and reads the root-only inventory (DMI serials, DIMMs, NVMe health) through it; see [privilege separation](/docs/daemon#privilege-separation). A host build running as an ordinary user without `[helper]` starts anyway, but claims no GPU binding capability and fails every GPU hold at once as `node-unprivileged` (da60bbd). On an EC2 instance (a `*.metal` VPS host included), `169.254.169.254` and `fd00:ec2::254` belong to AWS's instance metadata service, which answers for the host: its identity document and its instance-profile credentials. `bcd node` then: * refuses to start with `[imds] listen` on either address (`[imds] listen "169.254.169.254:80" is EC2's own instance metadata service on this EC2 host`). Bound there, bcd's own service would make the address local, so the host's `cloud` section, spot interruption notices and `cloud-iid` join would read bcd's service instead of AWS's. Serve the instances' metadata on another address, such as the host's address on the instances' bridge. * drops every instance tap's packets to both addresses, whatever the instance's network policy, before any policy rule accepts them. Also launch the instance with `HttpTokens=required` and an `HttpPutResponseHopLimit` of 1 (2 when bcd runs in a container without host networking): IMDSv1 answers any packet the host forwards, and IMDSv2 answers a token request only within the hop limit. It logs JSON lines to standard error (`[log]`), and runs until SIGINT or SIGTERM. Exits: * `0` on a signal, or after a [self-update](/docs/daemon/install#self-update) swapped the binary slots, in which case `main` execs the entrypoint (the new binary) with the same arguments. * `1` when it cannot start (`bcd node: agent: this node has not joined (run bcd join)`, a journal of another node, an invalid `[kubernetes]` value, `[imds] listen` on AWS's metadata address on EC2) or when the certificate is revoked (`agent: this node's certificate was revoked`). ```bash bcd node --config /etc/bcd/config.toml ``` ## bcd site [#bcd-site] Runs the [site agent](/docs/daemon/install#a-site-agent): the same agent with role `site` (BMC operations, `site.httpProxy`, boot entries) and, with `[boot] listen` set, the boot server (proxy DHCP, TFTP, iPXE, the image cache). | Flag | Default | Meaning | | ---------- | ---------------------- | ---------------------- | | `--config` | `/etc/bcd/config.toml` | The configuration file | It refuses a data directory whose identity is not a site's ("this data directory holds a node identity; join with a site token (bcd join --role site)", exit 1). With `[boot] listen`, `join-encryption.jwk` must sit next to the configuration file (exit 1 otherwise), and `proxy_dhcp` or `tftp` need `[boot] server_ip` (IPv4) and `[boot] url` (exit 2). Without `[boot] listen` it logs a warning and serves no PXE machine. `[helper]` is ignored. ```bash BCD_JOIN_TOKEN=bcjt_... bcd join --role site --config /etc/bcd/config.toml bcd site --config /etc/bcd/config.toml ``` ## bcd ramdisk [#bcd-ramdisk] Runs inside the PXE ramdisk a site agent boots. It has no `config.toml`: it reads the `bc.*` parameters from the kernel command line (listed in [the ramdisk](/docs/daemon/install#the-ramdisk)), joins with the `discovery` attestor as role `ramdisk` unless the data directory already holds an identity, then runs the agent. | Flag | Default | Meaning | | ------------ | --------------- | ------------------------------------------------------------------------------- | | `--cmdline` | `/proc/cmdline` | Where to read the `bc.*` boot parameters | | `--data-dir` | `/run/bcd` | Key, identity and journal, on tmpfs; the status socket is `/bcd.sock` | A missing or malformed boot parameter, or an `http://` gateway in a host build, exits 2. A `bc.jwk` that does not match its pin exits 1 ("bc.jwk does not match the join encryption pin"). Logs are always JSON. The OS a `deploy.image` installs gets a `config.toml` rendered from the same parameters (with the default `data_dir`) and the same `join-encryption.jwk`. ## bcd helper [#bcd-helper] Runs as root beside an unprivileged `bcd node` and serves its typed privileged operations on `[helper] socket`. See [running bcd node unprivileged](/docs/daemon/install#running-bcd-node-unprivileged) for the units and [privilege separation](/docs/daemon#privilege-separation) for the operations. | Flag | Default | Meaning | | ---------- | ---------------------- | --------------------------------------------------------- | | `--config` | `/etc/bcd/config.toml` | The configuration file; it must have a `[helper]` section | Before it listens, it refuses to start (exit 1) when: | Message | Cause | | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `[helper] socket and user are not configured` | No `[helper] socket` | | `runs as root` | Not root (fakehw builds excepted) | | `helper.user: user: unknown user bcd` | `[helper] user` names no local account | | ` must be a regular file root owns that no one else can write` | `config.toml` is a symlink, not root's, or group- or world-writable, or a directory above it is (3fa9021) | | ` must be a directory root owns that no one else can write (the helper socket lives there)` | A directory from `/` down to the slots directory, the socket's directory or the entrypoint's directory is not root's or is group- or world-writable (c139b9c, 9e71b1f) | | `[kubernetes] allowed_clusters: ... is not https://host[:port]#sha256:<64 hex>` | A malformed allow-list entry | | `command_root_sha256 must be 64 lowercase hex digits per root, comma-separated` | A malformed root pin | It then creates the socket's directory if needed, binds the socket (mode 0660, group the node account's primary group) in a private directory and renames it into place, re-arms the self-update watchdog for a swap still waiting for confirmation, and serves until a signal. Each refused connection is logged as `helper: connection refused` with the reason; see [troubleshooting](/docs/daemon/troubleshooting#helper-socket-refusals). ```bash sudo bcd helper --config /etc/bcd/config.toml ``` ## bcd sim [#bcd-sim] Runs `--nodes` real node agents in one process against the configuration's gateway, each on a fake machine. It exists only in fakehw builds (`go build -tags fakehw`); a host build prints `bcd sim: needs a fakehw build (go build -tags fakehw)` and exits 2. See [bcd sim](/docs/daemon#bcd-sim) for what each simulated machine does. | Flag | Default | Meaning | | ---------- | ---------------------- | --------------------------------------------------------------------------------------- | | `--config` | `/etc/bcd/config.toml` | Gateway URL, pins, journal limits and logging; machines live under `/sim/` | | `--nodes` | `1` | How many machines; below 1 exits 2 | | `--seed` | `1` | The fakehw seed of machine 0; machine `i` uses `seed + i` | When any machine has not joined, it reads one token from `BCD_JOIN_TOKEN` or standard input (exit 2 without one) and needs `join-encryption.jwk` next to the configuration (exit 1 without it). At most 16 joins run at once. ```bash printf '%s\n' "$SIM_TOKEN" | bcd sim --config ./sim.toml --nodes 5 --seed 100 ``` ## bcd join [#bcd-join] Joins this machine to the fleet and writes `node.key` and `identity.json` under `data_dir`. The token comes from `BCD_JOIN_TOKEN`, else the first line of standard input (at most 4 KiB, a leading byte-order mark dropped), else `--token-file`; never from an argument. | Flag | Default | Meaning | | ----------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--config` | `/etc/bcd/config.toml` | The configuration file; with `--server`, the file to write when it does not exist | | `--role` | `node` | `node` or `site`; anything else exits 2. The token decides what is allowed | | `--token-file` | | Read the token from this file: a regular file, mode 0600 or 0400 (others may not read it), at most 4 KiB. Removed once the join succeeds or the machine had already joined; a read-only mount only logs a warning | | `--node-id` | | The node an installed OS must join as; implies `--attestor provisioned`. A different identity exits 1 | | `--server` | | The control plane URL (`https://`): its `/.well-known/better-compute` supplies the join key when `join-encryption.jwk` is absent, and the gateway when `config.toml` is | | `--attestor` | `join-token` | `tpm` joins with the host's TPM (`/dev/tpmrm0`) and no token; `cloud-iid` with an EC2 instance's signed identity document and no token (exits 1 off EC2, before any metadata request; EC2 is the DMI `sys_vendor` `Amazon EC2`, or `Xen` with EC2's BIOS version or UUID on previous-generation types such as t2 and m4); `provisioned` with `--node-id`. `tpm` or `cloud-iid` with `--token-file` exits 2 | | `--serving-ca-pin` | | `sha256/`; required with `--server` and no `config.toml` (host builds). With a `config.toml` it must equal `serving_ca_spki_pin` | | `--join-signing-pin` | | Same, for `join_signing_key_pin` | | `--join-encryption-pin` | | Same, for `join_encryption_key_pin` | | `--data-dir` | `/var/lib/bcd` | The `data_dir` a configuration written by `bcd join` names | | `--reset` | `false` | Move this node's identity and journal aside first, so it joins again; root only | | `--yes` | `false` | Confirm `--reset` | What it does, in order: 1. Loads `config.toml`, or, when the file does not exist and `--server` is given, builds one from the well-known document checked against the three pin flags ([joining from the well-known document](/docs/daemon/install#joining-from-the-well-known-document)). 2. With `--reset`: as root, renames `identity.json`, `node.key`, `node.key.next` and `journal.db` to `.reset-`. Without `--yes` it prints what it would move and exits 2. See [joining again with \--reset](/docs/daemon/install#joining-again-with---reset). 3. If an identity loads, prints `already joined as (site , role )` and exits 0, without reading a token. Under `[helper]` it hands `identity.json` and `node.key` to the `[helper] user` first. 4. Otherwise reads the credential, seals the token to the pinned JoinService key, and calls `Join` with the hardware claims. Retryable failures (`UNAVAILABLE`, `RESOURCE_EXHAUSTED`, deadlines) retry with jittered back-off up to 20 attempts, or exactly when the gateway's `grpc-retry-pushback-ms` says; a pending approval waits and asks again; a `tpm` join answers up to 3 challenge rounds. A `cloud-iid` join reads the identity document and its signature from IMDSv2 once and sends them as the attestation, with no token. The control plane admits each instance once, so a `Join refused` after a retried attempt says the evidence is spent: the lost attempt may have admitted the instance (check for the node, or re-join with `--reset` and a join token). In a container on EC2 (an EKS DaemonSet without host networking), IMDSv2 answers the token request only when the instance's metadata hop limit (`HttpPutResponseHopLimit`) is at least 2; otherwise the read times out with that hint, and `bcd node` keeps retrying the cloud section with back-off (5 s doubling to 5 min). 5. Verifies the signed `JoinResponse`, stores the key and identity, writes a bootstrapped `config.toml` (0600) and `join-encryption.jwk` (0644), removes the token file, and prints `joined as (site , role ); certificate valid until