merod CLI Reference
merod is the Calimero node. One process is one node of a cluster; it serves
the node API on one HTTP listener and keeps its state under a data directory.
The binary is crates/merod (src/main.rs, src/node_config.rs); what the
node does lives in calimero-node.
merod [--home DIR] [--node NAME] <COMMAND>
Commands: init Write the node's configuration run Start the node from its configuration config Change the node's configuration, then print it local A development cluster in this process inspect What a stopped node's data directory holds kms The data-disk key from mero-kms (for a TEE boot script) attest A release attestation, made without any KMS keyGlobal arguments
Section titled “Global arguments”| Flag | Environment | Default | Meaning |
|---|---|---|---|
--home DIR |
CALIMERO_HOME |
~/.calimero |
Directory for nodes’ configuration and data. |
--node NAME |
MEROD_NODE_NAME |
none | The node. Its configuration is {home}/{NAME}/config.toml and its data {home}/{NAME}/data unless --data-dir says otherwise. init, config and inspect (without --data-dir) need it; run does not. |
--version and --help work on every command.
merod --node NAME init [NODE FLAGS] [--force]Writes {home}/{NAME}/config.toml from the node flags given,
creates {home}/{NAME}/data, and prints the file’s path. The file is written
with mode 0600: it may hold the ops token or a development storage key.
--node-idand--public-urlare required.- A second
initon the same node fails unless--forceis given, which replaces the file. Useconfigto change one. - Only the flags you pass are written. Defaults are applied when the node starts, so the file never records a default as if it had been chosen.
merod [--node NAME] run [NODE FLAGS] [--accept-rollback]Starts the node and serves until Ctrl-C. The configuration is the node’s file
(if --node is given and the file exists) with every flag and MEROD_*
variable given here overriding it for this run. A container can therefore run
with flags or environment variables only, no init and no --node, as long as
--node-id, --public-url and --data-dir are set.
run refuses to start when:
--node-idor--public-urlis missing, or there is no data directory;- a
--peersentry is notid=url; - only one of
--kms-urland--kms-identityis given; - the node is in TDX mode (no
--dev) and--storage-keyor--storage-key-fileis given, or no storage-key source is given at all; - the node is in TDX mode and
MERO_TEE_VERSION, or bothMERO_TEE_PROFILEand--peer-profiles, are unset (it cannot judge its peers’ quotes without them); - its key comes from a mero-kms that keeps rollback counters and the store is older than the node’s counter there (a restored older copy of the disk), is empty or older than rollback protection while mero-kms has seen this node id, belongs to another node id, or had a counter and mero-kms answered without one.
--accept-rollback (this run only; never stored in the configuration) starts a
node on a store its counter refuses as older, when the newest disks are lost:
mero-kms lowers the node’s counter to the store’s and raises the cluster’s
rollback epoch, which every node reports (rollbackEpoch in /health,
rollback in /ops/status). If only one node is behind, wipe its data
directory and register it under a new node id instead. A store under another
node id is never started. See Security.
Logging goes to stdout through tracing; RUST_LOG overrides the default
filter info,openraft=warn.
config
Section titled “config”merod --node NAME config [NODE FLAGS]Merges the flags given into the existing file, writes it back (mode 0600)
and prints it. With no flags it only prints the file. It fails if the node has
no configuration yet.
A list flag (--peers, --origins, --peer-profiles) replaces the whole list.
There is no flag to remove a key; edit the file for that.
Node flags
Section titled “Node flags”init, run and config take the same flags. Each is also a key of
config.toml (the flag name without --, for example node-id = 1) and an
environment variable. See Configuration for the file.
| Flag | Environment | Default | Meaning |
|---|---|---|---|
--node-id N |
MEROD_NODE_ID |
required | This node’s id in the cluster (a u64). |
--listen ADDR |
MEROD_LISTEN |
0.0.0.0:2528 |
Socket address the API listens on. |
--public-url URL |
MEROD_PUBLIC_URL |
required | Base URL peers and clients use for this node. |
--data-dir DIR |
MEROD_DATA_DIR |
{home}/{node}/data |
Where the node keeps its state. |
--peers ID=URL,... |
MEROD_PEERS |
none | Cluster members, this node included. Used by --bootstrap and by whichever node leads the system group, which registers them in the fleet directory. A node starting on an empty data directory without --bootstrap waits (serving only /health, /attestation and the key delivery) until one of them delivers the cluster key to its registered address, for up to 10 minutes. |
--cluster-identity HEX |
MEROD_CLUSTER_IDENTITY |
none | With --job-node, required: the cluster’s identity key (32 bytes, 64 hex digits; meroctl ops cluster prints it). The job node takes work and components only when this key signed them, and its attestation binds the key, so the cluster’s leaders attest only job nodes that serve them. A key that is not a curve point or is of small order is refused at start. Refused without --job-node. |
--job-slots N |
MEROD_JOB_SLOTS |
2 |
With --job-node: how many compute runs it takes at once; reported as its capacity. Refused without --job-node. |
--job-node [BOOL] |
MEROD_JOB_NODE |
false |
Run as a job node: it runs compute for the cluster’s apps and holds no app, store, vote or cluster key. Serves only /health, /attestation (purpose=jobs attests its job key) and /job/*. It needs --cluster-identity HEX (from meroctl ops cluster) and takes work and components only from the cluster that signs with that key. Register it with POST /ops/nodes and "role": "job". Flags that would give it a cluster (--bootstrap, --peers, --storage-key, --kms-url, --ops-token, …) are refused. |
--bootstrap [BOOL] |
MEROD_BOOTSTRAP |
false |
Form the system group with --peers as voters. Given alone it means true. Harmless on a restart: a group that exists is not formed again. On an empty data directory it also mints the cluster key (from this node’s storage key). |
--cluster NAME |
MEROD_CLUSTER |
dev |
The cluster’s name. It is mixed into the storage key and the cluster secret, so every node of a cluster needs the same one. Passkeys made on the cluster are named with it and the cluster’s tag (alice · NAME · 3f2a; GET /sign-up answers the tag), so a person can tell clusters that share an rp-id apart in their password manager. |
--dev [BOOL] |
MEROD_DEV |
false |
Development mode: mock attestation quotes, mock peers accepted, a development storage key, plain (unsealed) requests allowed, raw WASM and .mpk uploads allowed (without it, every release comes from --registry-url). |
--storage-key HEX |
MEROD_STORAGE_KEY |
none | A 32-byte storage key in hex. --dev only. |
--storage-key-file FILE |
MEROD_STORAGE_KEY_FILE |
none | A file of at least 32 bytes of key material; the storage key is derived from it and --cluster. --dev only. Conflicts with --storage-key and --kms-url. |
--kms-url URL |
MEROD_KMS_URL |
none | Fetch the storage key from mero-kms after it verifies this node. Needs --kms-identity; the only source in TDX mode. Conflicts with --storage-key. |
--kms-identity FILE |
MEROD_KMS_IDENTITY |
none | The cluster’s disk identity (a protobuf libp2p keypair), shared by its nodes so they derive one key. Created at the path if missing. |
--kms-disk-evidence FILE |
MEROD_KMS_DISK_EVIDENCE |
none | The record kms disk-key --evidence-out wrote of the KMS that released the data disk’s key. Read from tmpfs or ramfs only, and reported (bound into the quote) beside the storage key’s at GET /attestation?purpose=kms. A file that cannot be read is logged and not reported; the node still starts. |
--rp-id ID |
MEROD_RP_ID |
localhost |
The WebAuthn relying-party id passkeys are bound to. It must be a registrable suffix of every host that runs a passkey ceremony: each --origins host and --apps-domain must equal it or be a subdomain of it (calimero.network covers console.calimero.network and {label}.apps.calimero.network, not evil-calimero.network), or the browser fails every sign-in there with a SecurityError. The node refuses to start otherwise, naming the flag and host; origins on an IP address are exempt (CORS only). |
--origins URL,... |
MEROD_ORIGINS |
http://localhost:3000 |
Origins allowed to run passkey ceremonies and to call the API from a browser (CORS). |
--ops-token TOKEN |
MEROD_OPS_TOKEN |
none | The Ops API bearer token. Without it or --ops-token-sha256 the Ops API answers 403. |
--ops-token-sha256 HEX |
MEROD_OPS_TOKEN_SHA256 |
none | The ops token’s hex SHA-256 instead of the token, so the machine’s configuration holds nothing usable. Conflicts with --ops-token. |
--operator-key HEX |
MEROD_OPERATOR_KEY |
none | The operator’s Ed25519 verify key (32 bytes, hex). Recorded at genesis; every operator decision governance gives the operator must be signed with it (Operator). Without one, such decisions are refused. |
--metrics-listen ADDR |
MEROD_METRICS_LISTEN |
off | A plain-HTTP host:port serving only GET /metrics (the /ops/metrics text) for a Prometheus scraper; no credential, so bind it to a private address. |
--replicas N |
MEROD_REPLICAS |
3 |
How many nodes hold each new app. |
--backup-dir DIR |
MEROD_BACKUP_DIR |
none | Where encrypted backups are written: a snapshot of each app group the node leads, and of the system group when it leads it, each with its sealed manifest; the cluster key wrapped under this release’s storage key (cluster-key/{release}); the code the system state names (code/{hash}). When set, the node writes them every 60 seconds; without it, no backups. |
--restore-from DIR |
MEROD_RESTORE_FROM |
none | With --bootstrap, on an empty data directory: found the cluster again from the backups in DIR instead of minting a cluster key and writing genesis. The cluster key comes from the copy in DIR/cluster-key/ this node’s storage key opens (same cluster name, a release that kept one), the system state from the newest system backup. The founder forms the system group alone; the other --peers join as learners. Refused while a node of the cluster backed up still answers at an address --peers does not reuse, and when the governance flags (--release-approvals, --app-upgrades, --outbound-approvals, --deployments, --deployers, --sign-up) or --operator-key differ from the backup’s (Found a cluster again). |
--tee-release VERSION |
MERO_TEE_VERSION |
none | The mero-tee release this image is; the node’s attestation names it so clients and peers fetch the signed measurements. Required in TDX mode. |
--tee-profile NAME |
MERO_TEE_PROFILE |
none | The mero-tee image profile this node runs. |
--peer-profiles NAME,... |
MEROD_PEER_PROFILES |
--tee-profile |
Profiles a peer’s quote may match. |
--peer-min-release VERSION |
MEROD_PEER_MIN_RELEASE |
client floor (2.3.109) |
The oldest mero-tee release a peer may run. |
--registry-url URL |
MEROD_REGISTRY_URL |
none | The package registry nodes fetch signed releases from. Without it, PACKAGE@VERSION installs answer NO_REGISTRY. |
--allow-unsigned-bundles [BOOL] |
MEROD_ALLOW_UNSIGNED_BUNDLES |
false (on with --dev) |
Accept raw WASM uploads (POST /bundles), which carry no publisher signature. |
--release-approvals owner|operator |
MEROD_RELEASE_APPROVALS |
owner |
Who approves the mero-tee releases that may hold apps’ data. |
--app-upgrades owner|operator |
MEROD_APP_UPGRADES |
owner |
Who upgrades apps to another bundle. |
--outbound-approvals owner|operator |
MEROD_OUTBOUND_APPROVALS |
owner |
Who approves the URL prefixes apps may call out to. |
--deployments open|granted |
MEROD_DEPLOYMENTS |
open |
Who may install apps (packages, raw bundles): any account, or only accounts holding a deployer grant. Anyone may still create instances of the releases they see (Deployers). |
--deployers operator|owner |
MEROD_DEPLOYERS |
operator |
Who grants deployers where deployments are granted: the operator’s key, or the cluster’s owner once the operator names it. |
--sign-up open|invite|closed |
MEROD_SIGN_UP |
open |
Who may create an account: anyone; someone with an organization or cluster sign-up invite; or only someone the operator (or owner) pre-authorized with a sign-up invite (Sign-up policy). Config file key: sign-up. |
--apps-domain DOMAIN |
MEROD_APPS_DOMAIN |
none | Serve each package’s frontend at {label}.{DOMAIN} and {cluster_label}.{DOMAIN}, and each deployment’s at {dlabel}.{DOMAIN} (a wildcard DNS name pointing at the cluster). Must be --rp-id or a subdomain of it. |
--settings FILE |
MEROD_SETTINGS |
none | A TOML file whose [outbound] table replaces the configuration’s. |
A boolean flag takes an optional value: --dev is --dev true, and
--dev false turns off a dev = true the file holds.
merod local [--nodes N] [--base-port P] [--dir DIR] [--fresh] [--ops-token TOKEN] [--registry-url URL] [--origins URL,...]A development cluster in this process: N nodes on
http://127.0.0.1:{P+1}/ to {P+N}, mock attestation, the development
storage key for cluster local, raw WASM uploads allowed, frontends served
under localhost (--apps-domain localhost), backups under {dir}/backups.
Node 1 bootstraps the system group; each app gets min(N, 3) replicas. It
prints the node URLs, the state directory, the ops token and the two
meroctl commands that connect to it, then runs until Ctrl-C.
| Flag | Environment | Default | Meaning |
|---|---|---|---|
--nodes N |
3 |
How many nodes. | |
--base-port P |
2500 |
Node i listens on P + i. |
|
--dir DIR |
.merod-local |
Where the cluster keeps its state, across restarts. | |
--fresh |
off | Delete --dir first. |
|
--ops-token TOKEN |
MEROD_OPS_TOKEN |
random, printed | The Ops API token. |
--registry-url URL |
MEROD_REGISTRY_URL |
none | The package registry the cluster installs from. |
--origins URL,... |
http://localhost:3000,http://localhost:5173 |
Origins allowed to run passkey ceremonies. |
The nodes listen on 127.0.0.1, so merod local inside a container is not
reachable from the host; see Install for a
container setup. The default log filter is warn,openraft=off.
inspect
Section titled “inspect”merod --node NAME inspect [--storage-key-file FILE]merod inspect --data-dir DIR [--storage-key-file FILE]Prints, as JSON, what a stopped node’s data directory holds.
Without a key it reads the raw layout only, and sizes are sealed sizes. With
--storage-key-file (a file holding the storage key K_r as 64 hex characters)
it also reads each group’s metadata: vote, applied index, membership. K_r
unwraps the cluster key from <data-dir>/cluster-key, which the store is
sealed under (The cluster key).
| Flag | Default | Meaning |
|---|---|---|
--data-dir DIR |
the --node’s data directory |
The data directory to read. |
--storage-key-file FILE |
none | The storage key K_r, hex, in a file. |
kms disk-key
Section titled “kms disk-key”merod kms disk-key --kms-url URL --identity PATH [--create-identity] --key-out PATH [--evidence-out PATH]Fetches the data-disk unlock key for this TD from mero-kms, for an image’s boot
script (for example to unlock an encrypted data disk before merod run). The
KMS is trusted only if its quote matches the signed mero-tee release the
environment names (MERO_KMS_RELEASE_TAG, else MERO_KMS_VERSION, else
MERO_TEE_VERSION; MERO_TEE_PROFILE selects the profile’s policy). With no
release named, no key is fetched.
| Flag | Meaning |
|---|---|
--kms-url URL |
URL of the mero-kms service. |
--identity PATH |
The disk-unlock identity: a protobuf-encoded libp2p keypair. |
--create-identity |
Generate the identity first if the file does not exist, for a disk being formatted. An existing disk must present the identity it was made with. |
--key-out PATH |
Where to write the key. It must not exist yet and must be on tmpfs or ramfs; the command checks this before touching the network. |
--evidence-out PATH |
Also write what was verified of the KMS (its quote’s registers, TCB status, the signed release and profile, the quote’s SHA-256 and the time; JSON) here, for run --kms-disk-evidence to report in the node’s attestation. Same rules as --key-out. |
A TDX node does not take its storage key from this file: run --storage-key-file is refused without --dev. In TDX mode merod fetches its
own storage key with --kms-url and --kms-identity.
attest
Section titled “attest”merod [--node NAME] attest [--nonce HEX] [--out FILE] [--format json] [--identity PATH] [--dev]merod [--node NAME] attest --serve ADDR [--identity PATH] [--dev]A release attestation: a quote over a verifier’s nonce and what this merod
says it is, made without any KMS key, store or running node. It is how a
manager checks a fresh TD’s image and merod before mero-kms releases a
cluster key to it: run fetches its key before it serves anything, so the
node’s own /attestation comes too late for that. --dev (or the node’s
configured dev) makes a mock quote; otherwise the TDX module makes a real
one, and the command fails where there is none.
The claim it binds: merodVersion, merodSha256 (of the running executable),
teeRelease (MERO_TEE_VERSION), teeProfile (MERO_TEE_PROFILE) and
identity, the libp2p peer id of --identity (default: the configured
--kms-identity, when that file exists; never created here). The report data:
report_data = nonce(32) || SHA-256("calimero.tee-attest.release.v1" || for each of merodVersion, merodSha256, teeRelease, teeProfile, identity: u32 BE length || UTF-8 bytes (length 0 when null))| Flag | Environment | Meaning |
|---|---|---|
--nonce HEX |
The verifier’s nonce, 32 bytes hex (default: a random one, which proves no freshness). | |
--out FILE |
Write the attestation here instead of stdout. | |
--format json |
The only format: one JSON object (below). | |
--identity PATH |
Bind this protobuf libp2p keypair’s peer id. | |
--serve ADDR |
Serve GET /attestation/release?nonce=<64 hex> on ADDR, a fresh quote per request, until SIGINT or SIGTERM. Nothing else is served, nothing is authenticated, one quote is made at a time. |
|
--dev |
MEROD_DEV |
A mock quote. |
--tee-release |
MERO_TEE_VERSION |
The mero-tee release this image is. |
--tee-profile |
MERO_TEE_PROFILE |
The image profile. |
{ "format": 1, "quote": "<base64>", "nonce": "<64 hex>", "claim": { "merodVersion": "1.0.0", "merodSha256": "<hex>", "teeRelease": "2.4.0", "teeProfile": "locked-read-only", "identity": null }, "reportData": "<128 hex>", "reportDataLayout": "nonce(32) || SHA-256(...)", "measurements": { "mrtd": "<hex>", "rtmr0": "<hex>", "rtmr1": "<hex>", "rtmr2": "<hex>", "rtmr3": "<hex>" }, "mock": false}measurements is null for a mock quote, and only informational otherwise: a
verifier reads the registers from the verified quote. Verify with meroctl attest-release or calimero_client::attest::release::verify_release (the
same quote check as every client’s, with its measurement policy), or with
calimero-tee-verify given the quote, the nonce and the report data’s second
half as app_hash_hex, then the registers against the signed release. See
Attestation & receipts.
Next steps
Section titled “Next steps”- Configuration: the same settings as a file.
- Deployment: a production cluster.
- meroctl: the command line that talks to nodes.