Agent index (llms.txt) — fetch this first, then the page it names.
OramaOS
OramaOS is a custom minimal Linux image, built with Buildroot, purpose-made for running Orama nodes. It replaces the Ubuntu deployment with a locked-down system where the operator cannot read the node's data.
Status: built, never booted on a live network. Devnet and testnet nodes run Ubuntu today. Several parts do not work yet, and they are listed under "Not yet implemented". Do not plan a production node on OramaOS until they are.
Why it exists
An Ubuntu node gives its operator root: they can read every stored byte, modify binaries and tamper with the system. That is why running a node is by invitation today (Security model). OramaOS removes that attack surface so that people the network does not know can eventually run nodes.
| Ubuntu | OramaOS | |
|---|---|---|
| SSH and shell | Yes | No |
| Disk encryption | No (design only) | LUKS2, key split by Shamir across peers |
| OS updates | Manual rolling upgrade | Automatic, signed, A/B |
| Service isolation | systemd sandboxing | Linux namespaces and seccomp |
| Rootfs integrity | None | dm-verity hashes can be built in; not wired into boot, so not enforced today |
| Binary signing | Required (archive signature) | Required |
| Operator data access | Full | None |
| Environments | All, including sandbox | Devnet, testnet, mainnet. Sandbox clusters stay on Ubuntu |
How it works
Buildroot produces a minimal Linux with only systemd, cryptsetup, WireGuard (in the 6.6 LTS kernel) and the orama-agent. The agent is the only root process and manages the boot sequence and LUKS key reconstruction, the WireGuard tunnel, service lifecycle in sandboxed namespaces, commands from the gateway over WireGuard, and OS updates.
/dev/sda1 rootfs-A (SquashFS, read-only) /dev/sda2 rootfs-B (standby for A/B updates) /dev/sda3 data (LUKS2 encrypted, ext4) Boot: systemd-boot -> rootfs -> orama-agent -> WireGuard -> services
Known inconsistency: the GPT image genimage builds puts the ESP first, followed by rootfs-A, rootfs-B and data, which does not match the device paths the agent operates on. This is unresolved in the code.
Enrollment
OramaOS nodes join through enrollment instead of orama maint node install.
- +Flash the image to the VPS through your provider's custom-image upload (Hetzner, Vultr, OVH and others).
- +First boot. The agent enters enrollment mode: it generates an 80-bit registration code (20 hex characters), prints it on the console TTY (not the journal), and opens a temporary HTTP server on port 9999 waiting for a payload sealed under that code. The code is not served over the network.
- +Enroll from your computer with
oramaand RootWallet:
orama node invite --expiry 24h # on any existing node orama maint node enroll \ --node-ip <vps-public-ip> \ --code <registration-code> \ --token <invite-token> \ --gateway <gateway-url>
The CLI sends the console code, invite and public IP to the gateway, which proves it holds the code, assigns a WireGuard address and pushes the cluster configuration encrypted under it. A wrong code fails at the node and configures nothing. The node configures WireGuard, LUKS-encrypts its data partition, and splits the LUKS key by Shamir's scheme across peer vault guardians. Port 9999 closes permanently. Each node mints its own agent token during enrollment; the gateway presents it on every later command and the agent's command receiver binds only the node's overlay address. A node enrolled before agent tokens existed keeps running but accepts no commands until re-enrolled.
- +Verify:
orama status report --env <env>.
Port 9999 must be open in your provider's external firewall (Hetzner firewall, AWS security groups). The agent installs no firewall rule of its own, so nothing on the node opens it for you.
Disk encryption and reboot
The data partition is LUKS2 (AES-XTS-Plain64). The key is never stored on the node: it is split into shares across peer vault guardians (Shamir over GF(256)), with threshold K = max(2, floor(N/3)) for N peers. Writes need W = min(N, max(K+1, ceil(2N/3))) shares so a successful write is always recoverable.
Normal reboot. The agent starts and brings up WireGuard, contacts peer guardians, fetches K shares, reconstructs the key by Lagrange interpolation, mounts the data partition, zeros the key from memory and starts the services. If not enough peers are reachable it retries with backoff (1, 2, 4, 8, 16 seconds, at most 5 retries), then falls back to genesis unlock mode.
Genesis unlock mode. The agent opens an HTTP server on port 9998 and waits for an operator to supply the raw key:
curl -X POST "http://<node-ip>:9998/v1/agent/unlock" \
-H "Content-Type: application/json" -d '{"key":"<base64 32-byte LUKS key>"}'Known issue (bugboard #89). This server is plain HTTP with no credential, and it binds :9998, which is every network interface, including the public one. Nothing in the agent or the image restricts it: the agent installs no firewall rule (the image ships iptables, but no code configures it), so it is reachable from wherever your provider's firewall lets port 9998 through. Only the command receiver, which starts after the node is unlocked, is deliberately bound to the overlay address. Until the unlock server is bound to the overlay address too, block port 9998 in your provider's firewall except from the operator machine. The same applies to the enrollment port 9999, which the agent also binds on every interface (it is protected by the sealed registration code, not by the network).
Not yet implemented
Do not rely on any of this:
- +Genesis enrollment. Enrollment always tries to distribute Shamir shares and fails with "no peers available for key distribution" when the cluster has zero peers. A genesis OramaOS node cannot currently complete enrollment.
- +Key escrow. The agent never creates or stores a RootWallet-encrypted copy of the LUKS key and serves no
genesis-keyendpoint. - +
orama maint node unlock --genesis. It needs--key-fileholding a RootWallet-encrypted key, and nothing produces one. Thecurlcall under "Genesis unlock mode" is the only unlock path that works today. - +The 5-peer transition. No code distributes Shamir shares once five or more peers join, or moves from escrow to normal unlock.
- +dm-verity at boot. The hashes can be built into the image but boot does not enforce them.
- +Syscall filtering. Seccomp runs in audit mode: disallowed syscalls are logged, not blocked.
- +Share redistribution on departure.
Until the genesis path is built, an operator must record the genesis node's raw LUKS key out of band during initial enrollment and supply it manually to unlock the node after a reboot.
Managing a node
Since OramaOS has no SSH, all management is through the gateway API, which proxies to the agent over WireGuard (port 9998). The command receiver binds only the node's overlay address and requires the node's agent token, so on an unlocked node it is not reachable from the public internet (the genesis unlock server above is the exception while a node waits for its key). These calls need an admin API key (or the owner's JWT):
curl "https://cluster.example.com/v1/node/status?node_id=<id>" -H "Authorization: Bearer <admin key>"
curl -X POST "https://cluster.example.com/v1/node/command?node_id=<id>" -H "Authorization: Bearer <admin key>" \
-H "Content-Type: application/json" -d '{"action":"restart","service":"rqlite"}'
curl "https://cluster.example.com/v1/node/logs?node_id=<id>&service=gateway&lines=100" -H "Authorization: Bearer <admin key>"
curl -X POST "https://cluster.example.com/v1/node/leave" -H "Authorization: Bearer <admin key>" \
-H "Content-Type: application/json" -d '{"node_id":"<id>"}'service on logs is an allow-list: rqlite, olric, ipfs, ipfs-cluster, gateway, coredns, agent. Every command is logged.
OS updates
A/B partition scheme:
- +The agent checks for new versions hourly (
https://updates.orama.network/v1/latest). - +Downloads the signed image over HTTPS.
- +Verifies the SHA-256 and the RootWallet EVM signature against the embedded public key.
- +Writes to the standby partition.
- +Sets systemd-boot to boot from it with
tries_left=3. - +On the next reboot (the agent does not reboot by itself), the node boots into it.
- +If it comes up (agent starts, WireGuard connects, services healthy), it is marked good.
- +If it fails three times, systemd-boot falls back to the previous partition.
Updates are staged automatically; activating one needs a reboot. Failed updates are rolled back automatically.
Service sandboxing
Each service runs in its own mount namespace, UTS namespace and dedicated UID and GID, with a per-service seccomp allow-list (audit mode today). The root filesystem is a read-only SquashFS. The code names one writable DataDir per service but does not bind-mount or remount it: the mount namespace is created empty of changes, so what a service can write is decided by the read-only rootfs and by file ownership for its UID, not by a per-service mount. PID namespaces are deliberately omitted: they make a service PID 1, which changes signal semantics.
| Service | DataDir | Extra syscalls |
|---|---|---|
| RQLite | /opt/orama/.orama/data/rqlite | fsync, fdatasync (Raft and SQLite WAL) |
| Olric | /opt/orama/.orama/data | sendmmsg, recvmmsg (gossip) |
| IPFS | /opt/orama/.orama/data/ipfs | sendfile, splice (data transfer) |
| IPFS Cluster | /opt/orama/.orama/data/ipfs-cluster | fsync, fdatasync, sendfile (CRDT datastore, libp2p) |
| Gateway | /opt/orama/.orama | sendfile, splice (HTTP) |
| CoreDNS | /opt/orama/.orama | sendmmsg, recvmmsg (DNS) |
Removing or resetting a node
orama node wipe cannot reach it (no SSH).
- +Graceful departure:
POST /v1/node/leave(there is noorama node leavesubcommand). It stops services and removes the WireGuard peer. - +Cluster-side removal:
orama node remove --env <env> --node <ip> --offlinefrom a survivor.--offlineis required: the command never tries to reach an OramaOS node. - +Factory reset: reflash the image at your provider. Data is unrecoverable, because the key is distributed across peers.
Troubleshooting
- +Enrollment never completes. Can you reach
http://<vps-ip>:9999/? Open the port in the provider's firewall. Read the code from the console and pass--code. There is no WebSocket enrollment path. A wrong--node-ipalso fails: it must be the node's public IPv4. - +LUKS unlock fails after reboot. Not enough peers are online to reach K. Bring cluster nodes up. If reconstruction keeps failing, the agent waits for a manual unlock on port 9998.
- +Update rolled back. Read the agent logs with
GET /v1/node/logs?service=agent. Common causes: a corrupted download (the signature check should catch it), a hardware issue, an incompatible configuration. - +Services unhealthy after reboot.
GET /v1/node/statusshows which are down; restart withPOST /v1/node/commandand{"action":"restart","service":"<name>"}.