Skip to content

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 key
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.

Terminal window
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-id and --public-url are required.
  • A second init on the same node fails unless --force is given, which replaces the file. Use config to 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.
Terminal window
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-id or --public-url is missing, or there is no data directory;
  • a --peers entry is not id=url;
  • only one of --kms-url and --kms-identity is given;
  • the node is in TDX mode (no --dev) and --storage-key or --storage-key-file is given, or no storage-key source is given at all;
  • the node is in TDX mode and MERO_TEE_VERSION, or both MERO_TEE_PROFILE and --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.

Terminal window
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.

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.

Terminal window
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.

Terminal window
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.
Terminal window
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.

Terminal window
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.