Operate a Node from the CLI
rings is one binary in two roles. rings run is the node: it joins the overlay and serves the
JSON-RPC API. Every other subcommand is a client of a running node: it reads the same
config.yaml to find the node’s endpoint and the Bearer token that guards its internal API,
issues one request, and prints the reply. The API chapter documents the methods
the clients call; this chapter documents the commands.
rings <command> [options]
init Initializes a node with the given configuration.
new-delegation Creates a new delegatee signing key.
run Runs a foreground, composable Rings node.
connect Connects to a remote peer.
peer Manages peers on the network.
send Sends a message to another peer.
service Registers or looks up a service on the network.
pubsub Provides chat room-like functionality on the Rings Network.
inspect Show information of swarm: transport table, successors, predecessor, finger table.
rings <command> --help lists every option of a command; rings run --help is reproduced in
Host a Native Node.
Addressing the node
The client commands share these options:
| Option | Meaning | Default |
|---|---|---|
-c, --config <FILE> | The node configuration to read | ~/.rings/config.yaml |
-u, --endpoint-url <URL> | The node’s internal JSON-RPC endpoint | endpoint_url from the config, http://127.0.0.1:50000 |
--api-token-path <FILE> | Bearer token file of the node’s internal API; a relative path resolves next to the config file | api_token_path from the config |
Each option is also read from the environment variable of the same name in upper case
(ENDPOINT_URL, API_TOKEN_PATH, …).
Join the overlay
A node needs one peer to start from; Chord stabilization discovers the rest. There are four ways to get that first connection.
Through a peer’s HTTP endpoint
rings connect node https://node.rings.rs
The remote peer’s external API answers the handshake (nodeDid, answerOffer) without
authentication, so no token is needed by default. A peer that gates its handshake behind its
token takes --remote-api-token-file <FILE>.
Through a seed
rings connect seed file:///etc/rings/seed.json
rings connect seed https://example.org/seed.json
A seed is a JSON document listing peers by DID and endpoint URL; the node tries them in order:
{
"peers": [
{ "did": "0x…", "url": "https://node.rings.rs" }
]
}
An entry may carry an api_token for a peer whose handshake is gated.
Through the DHT
rings connect did 0x…
Routes a connection request to the DID through the peers already connected; the two nodes then exchange SDP over the overlay and open a direct datachannel.
By hand
When neither node has a reachable HTTP endpoint, carry the handshake yourself. On the initiator:
rings connect offer <peer-did>
prints an encoded offer. Deliver it to the peer, which answers it:
rings connect answer <offer> # or `-` to read the offer from stdin
and prints an encoded answer. Bring that back to the initiator:
rings connect accept <answer> # or `-`
Exchange SDP explains what each message carries.
Keeping a bootstrap peer reachable
Each rings connect form connects once. A node that later loses its transport to that peer,
and every other peer with it, stays partitioned until something reconnects it. For peers the
node should always be able to reach, list them under bootstrap.peers in
config.yaml, or hand rings run a seed document for
one run:
rings run --bootstrap-seed file:///etc/rings/seed.json
A file:// document is read at startup; an http(s):// document is fetched once at startup,
so the node does not start while the document’s host is unreachable.
rings run then supervises those targets: whenever one is no longer reachable through the
overlay it is redialed through its HTTP endpoint, in a short burst first and then every five
minutes plus up to thirty seconds of jitter, until it is admitted again. A target that other
peers can route to is left alone.
Peers
rings peer list
rings peer disconnect <address>
Messages
rings send message <to-did> <namespace> <data>
Delivers data to the protocol the peer registered under namespace. Receiving applications
embed rings-node and install a namespaced extension protocol; the removed /ws endpoint is not
an application-message boundary.
rings pubsub <topic>
Opens a chat-room loop on topic: each line you type is published to the topic, and messages
other peers publish to it are printed as they arrive. Each message expires by the element
lifetime rule of DHT storage.
Services
rings service register <name>
rings service lookup <name>
register publishes this node as a provider of name in the DHT; lookup returns the DIDs
currently providing it. A registration expires by the element lifetime rule of
DHT storage, so a provider runs register again
periodically to stay listed. Decentralized services describes
the model.
Inspect
rings inspect
Prints the swarm as the node sees it: the transport table, successors, predecessor, and finger table.
Delegations and keys
rings init writes the configuration and a delegatee signing key (~/.rings/delegatee_key unless
-s, --delegatee-key says otherwise). rings new-delegation writes a fresh delegatee key. Those two
delegation-creation commands accept -k or --key-file for the account signer; rings run and the
RPC client commands do not. A native node stores the resulting delegatee key in plain
text, so never derive it from a key that holds assets.
A delegation bounds the life of the process that uses it (#1063). Every frame a node sends
carries its own delegation, and from 25 s before the delegation expires (one delivery bound,
the margin that keeps a frame from expiring in flight) every frame is refused. The node can
send nothing from then on, and the delegation cannot be replaced without a restart. So
rings run:
- refuses to start with a delegatee key that has less than those 25 s left
(
DelegationHorizonReached, error code 1003); - logs a warning once the end is less than a day away, giving the end in Unix milliseconds;
- exits with a non-zero status at that end, with the same error, instead of running on mute.
--ttl of rings init and rings new-delegation (default 30 days) therefore sets how long a
node can run. Run it under a supervisor that restarts it (Restart=always under systemd,
--restart unless-stopped under Docker), and write a fresh delegatee key before the restart,
with rings new-delegation. A restart with the old key fails at startup with that error, so
the outage is explicit rather than silent. A plain Restart=always alone only repeats that
error: the restart needs its re-mint step, for example ExecStartPre= running
rings new-delegation for the node’s key.
The deadline is checked at least once a minute against the wall clock, so a host suspended across it still exits within a minute of resuming.