1,100+ tests · rocky 10 · selinux enforcing gossip :7300 raft :7302 api :7304 rocky 10 / systemd 257

CLI reference

Faithful to coreutils in shape, unfaithful in scope — and never misleading about the difference.

Available now

CommandWhat it does
one nodesInventory: name, datacenter, rack, state, cores, load, memory.
one topThe cluster as one host, then the per-node breakdown.
one freeCluster memory, plus the largest single node.
one dfCluster storage, plus the largest single node.
one psWhat this node is running, with the mobility column.
one artifact …The content-addressed store: ingest, ls, show, materialize, scrub, gc.
one run / stop / startDeclare a workload, take it down, bring it back. --cluster declares it for the fleet; --secret delivers an operator secret to it as a credential.
one scale / execChange an instance count; run a one-off command in a workload’s slice, limits and identity — as the job’s user, never as the agent.
one logs <job>Its journal from this node, or --all for every node holding an allocation.
one alias …set, rm, ls. Repointing an alias is the rollback primitive.
one node …drain, undrain, cordons, upgrade, remove.
one metricsThis node in the Prometheus text format, over the agent socket. No port.
one secret …put, ls, rm. Sealed in the cluster, delivered as a systemd credential. No get.
one backup / restoreThe three unreconstructable things, sealed to an operator key. --inspect needs no key.
one bootstrap / token / joinCreate the trust material and the founder's keyring; mint a single-use join token for one node and one role (--role control|worker, default worker); redeem one, which writes the keyring for that role.
one snapshotThe replicated state as this node holds it, for reading. Not a backup.
one regionsRegions heard from, and where a request would go (--route).
one cluster …plan and create: order the machines and form the cluster from a laptop.
one datastore …plan, apply, endpoints for SoliDB, SoliES and SoliKV.
one importRead soli-proxy’s state and show what the cluster would see. Writes nothing.
one reloadRe-read desired state next pass; --force lifts the mass-deletion guard for one pass.

--host <node> runs any of them on a cluster node over SSH, so the cluster reads as one machine from a laptop. There is no control port: the agent’s only interface is a unix socket whose permissions are its authentication, and SSH is already on every one of these machines.

Every command takes -o json. The output carries a "schema": "one/v1" field from the first commit, because -o json gets scripted and an unversioned format becomes an accidental public API the moment somebody writes a monitoring check on it.

one top

$ one top
VIRTUAL HOST   3 nodes up / 3
CPU   48 cores   load 1.32 3.66 5.40   2%
MEM   82G total   42G used   39G available
DISK  294G total  27G free

NAME       DC     RACK   STATE     CPU   LOAD        MEM
rk-01      par1   r1     alive      16   0.44    14G/27G
rk-02      par1   r2     alive      16   0.44    14G/27G
rk-03      par1   r3     alive      16   0.44    14G/27G

3 nodes  3 alive  0 not alive

Degrading honestly

When a node dies, its capacity leaves the totals immediately. It is still listed — an operator most wants to see a node at the moment it stops answering — but it stops counting as something you could schedule onto.

$ one top
VIRTUAL HOST   2 nodes up / 3
CPU   32 cores   load 1.42 63.74 70.22   4%
MEM   54G total   25G used   30G available

NAME       DC     RACK   STATE     CPU   LOAD        MEM
rk-01      par1   r1     alive      16   0.71    12G/27G
rk-02      par1   r2     alive      16   0.71    12G/27G
rk-03      par1   r3     down       16   0.97    12G/27G

3 nodes  2 alive  1 not alive
! 1/3 nodes not alive — results are incomplete
$ echo $?
3

Exit code 3 means partial. A monitoring check that cannot tell "everything is healthy" from "I could not see a third of the fleet" is worse than no check at all.

Facts also expire. A node that stops reporting keeps its row but drops its numbers after thirty seconds, rather than showing hour-old memory as current.

one free — the last line is the point

$ one free
             total         used    available
Mem:           82G          45G          37G

Largest single node can allocate: 12G (rk-03)

Summing available memory across the fleet is arithmetically true and useless for placement: you cannot put a 400 GiB workload into two hundred nodes with 10 GiB each. Printing both numbers is what separates a useful illusion from a lie, and it heads off a whole category of confused bug report.

one ps

$ one ps
JOB                          SLOT     STATE       MOBILITY     RESTARTS  AGE
acme.soli.app                single   running     mobile              0  4m
billing-worker               single   running     pinned              0  4m
solidb                       single   running     replicated          0  4m

3 allocations on rk-01  3 running

The MOBILITY column is the point of the table. It is the one fact that decides what happens when this machine dies: mobile is rescheduled, pinned waits for the node to come back, replicated fails over on its own. A table that hides it is how an operator discovers at 3am that their database was never going to move.

The JOB column shows the id the operator typed. The slug that ends up inside the systemd unit name is an implementation detail nobody should have to recognise — -o json carries the full unit for when you do need to paste it into systemctl.

Degrading honestly, again

Break a workload file and the running jobs are untouched, but the view says so:

$ echo '[[job]' >> /etc/soli-one/workloads.d/apps.toml
$ one ps
acme.soli.app                single   running     mobile              0  4m
billing-worker               single   running     pinned              0  4m
solidb                       single   running     replicated          0  4m

3 allocations on rk-01  3 running
! desired state is stale: apps.toml: TOML parse error at line 21, column 7
$ echo $?
3

A typo must never stop production, so each file keeps its last good content. But serving stale desired state as if it were current would be worse than the typo, so the view is marked degraded and the exit code says 3. The same applies to an orphan — a unit in our namespace the agent cannot account for, which is reported and never killed.

one artifact

The store is node-local, so these talk to disk directly and need no running agent — which is exactly the situation in which you most want them.

one artifact ingest <path>              # chunk a tree in, print its id
one artifact ls                        # what this node holds
one artifact show <id>                 # entries, modes, chunk counts
one artifact materialize <id> <dest>   # write it back out as a tree
one artifact scrub                     # re-hash everything, quarantine the bad
one artifact gc --keep <id> [--dry-run]

materialize reports how each file got there. If nothing was reflinked it says so, because a store on a filesystem without FICLONE silently costs a full copy per deployment and this is the only place that surfaces.

scrub exits 1 when it finds corruption, so a timer driving it does not have to parse stdout.

What this section used to say

all of them exist now

This was a list of ten unimplemented commands. All ten exist, and so does everything added since: one metrics, one secret, one backup, one restore and one node upgrade. The table above is generated from nothing — it is maintained by hand — so scripts/check_docs_sync.sh fails the build if any page on this site calls a shipped command unbuilt. That check exists because this page was wrong for months in the direction nobody checks: it undersold the thing it documents.

one snapshot reads the local state machine, so it costs no quorum round trip and works on a node that has lost its leader — which also means it can be behind, so the revision and the deterministic clock are reported beside the data. A snapshot with no revision next to it cannot be told from a current one.

Its values are opaque bytes to the store, and it renders one as text only when it reads as text. Valid UTF-8 is not the test: a postcard frame often is valid UTF-8, and one rendered as a string came out as \u0006websvc\u0004demo — which reads as corruption to someone looking at a snapshot to find out whether something is corrupt.

Two distinctions the design already fixes. one run submits work to the cluster and lets the scheduler place it; one exec runs a command inside something already running. And there is no one shell giving a prompt "on the cluster" — that would promise something meaningless.

one exec is a root-equivalent remote execution API on every machine in the fleet. Operator identity and a per-invocation audit record ship with it, not after it.

Since this page was written

CommandWhat it does
one run --name X -- cmdDeclares a workload. Writes a declaration under the data directory; the reconcile loop starts it. --image makes it a container, --schedule a batch job. With --cluster, placement reserves --memory-mb and --disk-mb on the chosen node, above its free-disk floor.
one exec <job> -- cmdRuns a one-off command in the job's slice, so it inherits the job's limits rather than escaping them. Output goes to the journal.
one reload --forceLets the next reconcile pass ignore the mass-deletion guard. One pass only.
one importReads soli-proxy's state read-only and shows what the cluster would make of it. Exits 3 on a degraded view.
why run declares rather than starts

The reconcile loop is the only thing that starts a workload. A CLI that started one directly would produce something the loop does not know about, and the very next pass would stop it as an orphan.

two declaration directories

/etc/soli-one/workloads.d belongs to the operator — the agent's unit is ProtectSystem=strict and cannot write there even by mistake. one run writes under /var/lib/soli-one instead. Without the split, a CLI invocation would silently overwrite a file configuration management maintains, and the next config run would silently overwrite that. A job id declared in both is reported, not resolved by whoever wrote last.