Agent index (llms.txt) — fetch this first, then the page it names.

Signed archives

Nodes install only build archives signed by an address they trust. The list of trusted addresses — the trust anchor — is /etc/orama/archive-signers on every node: root:root 0644 in a root-only directory, one lowercase 0x address per line. There is no unsigned mode, no source-build mode and no built-in signer: a cluster trusts its operator's wallet, not a DeBros key.

Build. orama maint build signs by default. It asks the RootWallet agent for its active account before compiling (a locked or absent wallet fails in seconds), then signs through the agent's wallet:sign:orama-archive capability (the request carries purpose: "orama-archive") — the first time, RootWallet asks you to approve orama for archive signing, separately from wallet:sign, and shows the build's version, commit, date and signer rotation. The message signed (EIP-191 personal_sign) is:

Orama build archive v1
version: <version>
commit: <commit>
arch: <arch>
date: <build date, RFC 3339>
signers: <the --signers rotation, or none>
manifest sha256: <hex SHA-256 of manifest.json exactly as archived>

The fixed first line means a signature your wallet gave any other application can never pass as a build signature, and the dialog shows a change of who is trusted before you approve it. A field with a character that is not printable (controls, line separators, bidirectional overrides) is refused. The build checks the signature with the same code every node runs before it writes the archive. The manifest lists the SHA-256 of every file the archive carries (binaries by name, templates as systemd/<name>, packages as packages/<name>), so nothing in the archive is outside the signature. --unsigned builds an archive for local inspection only; no node installs it. orama maint rollout builds a signed archive the same way.

Auto-update. A cluster that follows a release repository installs only releases signed under the release root its nodes adopted, and the agent never signs anything. A validator is never updated automatically: its chain binary goes in through orama maint global stage-oramad --upgrade <plan> (see Run a global node). The policy, the install and its rollback are in Upgrades.

Push. orama maint push uploads into a fresh mktemp -d directory on each node and runs the node's installed CLI, /usr/local/bin/orama maint node stage-archive. Under a lock on /opt/orama that install and upgrade share, it removes staging (and setup CLI) directories an interrupted run left, extracts the upload into a private directory (regular files and directories under the archive's own paths only; at most 1024 entries, 512 MiB per file, 4 GiB in all; modes set explicitly), verifies the signature against the node's anchor, every file against the signed manifest and the architecture against the node's, makes bin/ and its binaries root:orama 0750, and only then swaps manifest.json, manifest.sig, bin/, systemd/ and packages/ into place — the old manifest out first and the new one in last, and everything put back if any step fails. A refused archive leaves /opt/orama exactly as it was; this matters because systemd runs orama-node, the gateway, SFU, TURN and vault straight from /opt/orama/bin. Because the verifier is the node's installed CLI, push reaches only installed nodes; a fresh machine gets its first archive from orama node setup (or orama maint node install --remote).

Release root (opt-in). A cluster may also trust a TUF release root: the key set of a group of release signers, whose threshold signature on release metadata makes an archive installable without the operator building and signing it. It is off by default; a cluster that never adopts one trusts only its operator's wallet. A node adopts a root as /etc/orama/release-root.json, by either of:

  • +sudo orama node trust add-root <root.json>, on that node. The root is checked first (well-formed, signed by its own keys at its threshold, not expired); adopting a root other than the one already there needs --replace.
  • +A signed archive that carries it: orama maint build --release-root <root.json> puts the root in the signed manifest, and every node that installs that archive adopts it, with the signer rotation's replay rule (a build older than the last rotation cannot put an older root back). orama node setup --release builds such an archive for you (below).

A node with a root can require it when it stages: orama maint node stage-archive --archive <file> --release-metadata <dir> --release-target <name>. <dir> holds timestamp.json, snapshot.json and targets.json, and <role>.json for the channel when the target is <channel>/orama-.... The archive is first copied into the node's 0700 staging directory under /opt/orama; that copy is what is checked and what is extracted. Before anything is extracted, pkg/releaseverify checks the metadata against the adopted root (every role at its threshold, an unexpired timestamp, a snapshot no older than the one recorded in /etc/orama/release-seen.json, which is read and raised under a flock on release-seen.json.lock) and checks, through the descriptor that wrote the copy, that it has the length and hashes <name> has in the verified targets. Any failure — no adopted root, a tampered archive, an expired timestamp, metadata signed under another root, an older snapshot, a channel signing a path outside its own — refuses the archive and leaves /opt/orama untouched; the command never falls back to the wallet-only path when these flags are given, and giving only one of the two is an error. An archive that passes is then verified against the wallet anchor exactly as above: the release root is required in addition to the operator's wallet signature, not in place of it.

--release-only is the one case where the root is enough. The archive is an unsigned release (the CI build, hashed in the TUF targets): it may not name signers or a release root, so a release can change what code runs and never who is trusted. After the TUF checks, the node verifies every file against the archive's own manifest and records the manifest's SHA-256, the root's SHA-256, the target and the snapshot version in /etc/orama/release-staged.json (outside /opt/orama, so no archive can write it). orama node upgrade and install accept an archive with no manifest.sig only when that record names its manifest under the root adopted now; replacing the root withdraws every earlier endorsement. This is how the auto-update agent installs (see Upgrades). It needs a node that is already installed, since the archive trust anchor must exist.

Release repository. A repository is a static directory served over HTTPS at a public address (a host that is localhost, or an address on this machine or a private network, is refused by ParseRepositoryURL — cluster setting and --release-repo alike — and by the client's connection to it, which is checked against pkg/netguard's reserved ranges once the name is resolved, redirects included; only a binary built with the localrepo tag honours ORAMA_ALLOW_LOCAL_RELEASE_REPO=1 to serve one from loopback, and only the fleet e2e suite builds one: orama maint build --test-local-release-repo for the archive, go build -tags localrepo for the operator's CLI; a release build ignores the variable, and a Go test asks with releaseverify.AllowLocalRepositories): timestamp.json, snapshot.json, targets.json, root.json and every <N>.root.json, and the files under targets/, such as targets/nightly/orama-0.3.1-linux-amd64.tar.gz. A channel (nightly, main, dev/<branch>) is the path prefix of its targets in the one targets file, not a role. releaseverify.Repository fetches it. The client never takes its first root from the repository (a network's manifest pins its SHA-256), but it follows the repository's <N>.root.json rotations from the root it holds.

Install from a release. orama node setup --release <version> --release-repo <url> --release-root <root.json> [--channel stable] needs no checkout, Go or zig. On your machine it fetches the metadata, verifies it against the root you name (threshold, expiry, a snapshot not older than the newest this machine has accepted, kept in ~/.orama/release-seen.json), downloads <channel>/orama-<version>-linux-<arch>.tar.gz and checks its length and hashes. Only then does your RootWallet sign it: the manifest gains the root and your wallet's signature, and the archive goes on through the install above. The cluster therefore still trusts your wallet for what it installs, and now also trusts the root. --release and --archive are alternatives.

Signers and the test root. Publishing a release is orama maint release (pkg/releasepub): the RootWallet release key signs targets, snapshot and timestamp, three approvals, and publish uploads the archives to a GitHub release and the metadata to the release host. For a test network, go run ./cmd/testtuf (in core/) makes and updates a repository signed by software keys: testtuf init -dir D, testtuf publish -dir D -channel stable -archive orama-0.3.1-linux-amd64.tar.gz, and testtuf refresh -dir D before the timestamp (7 days by default) expires. Serve D/repo over HTTPS and give operators D/repo/root.json. Never adopt a test root on a production cluster. A root changes through the repository's numbered root files (each signed by the previous root's keys, which a node following the repository walks), through add-root --replace, or through a new signed archive.

First install. A new machine has no verified binary of its own: the one that runs the install comes out of the archive. So orama node setup and orama maint node install --remote --archive <path> verify the archive on your machine — against your RootWallet account (setup) or --operator-wallet (--remote) — check that it is built for the node's architecture (uname -m), and upload not the file you named but a canonical archive written from the verified files, into a mktemp -d directory. On the node, /opt/orama must be root's alone; only the CLI is extracted first, into a root-only directory under it; it runs only if its SHA-256 is the one in the verified manifest, and it puts the archive in place with node stage-archive — the verification, lock and crash-safe swap a push uses (creating the anchor from your wallet on a machine that has none). Setup is skipped only when the node already has exactly this manifest and a CLI with its checksum.

Install and upgrade. Phase 2b holds the /opt/orama lock, verifies the extracted archive against the anchor (architecture before any rotation) and only then copies anything to /usr/local/bin and installs the privileged helper; namespace templates are installed under the same lock. No archive, an unreadable manifest, a missing manifest.sig, an untrusted signer, a file that does not match or is not listed, another architecture, or a missing or empty anchor fails the install or upgrade with the reason. An upgrade runs the same check (without changing anything) before it stops any service, so an archive Phase 2b would refuse never takes the node down. A genesis install requires --operator-wallet (EIP-55 checked when given in mixed case) and creates the anchor from it before Phase 2b — only after the archive has verified against it, so a mistyped wallet writes nothing — and drops any rotation mark an earlier cluster left; an existing anchor must trust exactly that wallet. The first archive must therefore be signed by that wallet (see Node Setup). A joining node installs Tor and checks the archive — present, built for this node, every file matching its signed manifest — before it requests the join, so an archive that cannot be installed never spends the invite; then it writes the anchor (and rotation mark) the minting node returns in the join response. That list is served by the minting node's gateway, which runs as the orama user, so setup passes --expect-archive-signers — the --join-via node's anchor read over your SSH, or else your wallet. The joiner sends that expectation in the join request, and the minting node refuses a mismatch (HTTP 409, naming the list it trusts) before it spends the invite or writes a peer row; the joiner also refuses a response naming anything else. A cluster that trusts more than your wallet is joined with --join-via. A node without an anchor refuses to admit anyone, before the invite is spent.

Rotating signers. orama maint build --signers 0xA,0xB writes that list into the signed manifest. A node that verifies the archive against its current anchor rewrites the anchor to exactly that list in Phase 2b (atomically, root-owned). The list must include the account signing the build — so the archive still verifies after the rotation, and re-running an interrupted upgrade, or a join through a node that already rotated, installs it again. Retiring a key therefore takes two builds: the old key signs one that adds the new key (--signers 0xOld,0xNew), then the new key signs one that drops the old (--signers 0xNew). Each node records the build date of the last rotation it took (/etc/orama/archive-signers.rotated, written before the anchor and copied to joining nodes) and accepts a rotation only from a build at least that new — the same date resumes an interrupted rotation of that build — and never moves the mark to a date more than an hour ahead of its clock (nor takes such a mark from a join), so an older signed build cannot be replayed to bring a retired key back. A list of at most 32 signers. Nodes that miss a rotating build still trust the old list, so roll it out everywhere before the next one. Signature checks prove who built an archive, not that it is the newest: rolling a node back to an older build signed by a trusted key is allowed.

Nodes installed before archive signing have no anchor, and a plain push refuses them until they get one. Create it with the push that carries the first signed build:

bash
orama maint push --env devnet --archive <path> --trust-signers 0xYourWallet

A push with --trust-signers does not use the node's installed CLI at all, so it also reaches 0.122.x nodes, whose CLI has no node stage-archive. It stages the way orama maint node install --remote does: the archive is verified on your machine against --trust-signers, and what is uploaded is a canonical archive written from the verified files; on each node only bin/orama is extracted — into a root-only mktemp -d directory under /opt/orama, which must be root's and writable only by root — and it runs only if its SHA-256 is the one the verified manifest lists. That CLI then runs node stage-archive --trust-signers …, which verifies the archive again: against the node's anchor when it has one (which must then be exactly --trust-signers), and otherwise against --trust-signers, writing the anchor only after the archive passed. A mistyped address fails that push and leaves nothing behind. A new anchor never inherits a rotation mark left beside a removed one. --trust-signers never changes an existing anchor (that is what --signers is for). The script reaches the node base64-encoded on a pipe into bash -s, so a shell that wraps the command cannot reinterpret it.