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 --releasebuilds 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:
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.