CLI Surface
retectl and flor command reference for C0
CLI Surface
Two binaries, split by audience:
retectl— operator-facing; present on every install, used from the operator's workstation. Authors rete state: CA operations, validation, compilation, bundle issuance. Never touches a running agent. It ships everywhere because artifact composition is a set, not a per-role subset — a node that never runs it is the cost of keeping placement a config fact — but the operator's workstation is where it is used.flor— every node. The daemon plus the node-local user CLI: enrollment, apply, status. One binary serves end users, server admins, and the system service.
This matches the author-vs-participant split used by kubectl/kubelet, terraform/agent, and similar tooling — and the names mirror the layer each acts on: retectl administers the rete (the trust domain — not any single flor, and not one Coordinator), while flor is the node agent, exactly as kubectl administers the cluster and kubelet runs on the node. The -ctl suffix names the thing controlled (the rete), which is why it is not florctl: the tool never controls a flor, it authors rete state. An end user installing Florete on their laptop gets flor with a small, focused command set and simply never reaches for retectl, which is present but not theirs to use; operators use it on their own machine for rete-authoring work.
Every command in both binaries follows the same output rules (Installer § Logging): user messages go to stderr, written for the person who typed the command; stdout carries a command's data and nothing else, so flor agent status | jq stays meaningful; and the log stream goes to syslog, not to the terminal. --log-stderr redirects that stream and is accepted by every command, with FLOR_LOG_DESTINATION and [log] destination in config.toml as its environment and file forms; RUST_LOG raises the level, which defaults to error for commands and info for the daemon. Commands that perform a change log it — install, uninstall, enroll, agent enable/disable; a command that asks another process to act does not, so flor agent sync is silent and the daemon records the sync it ran.
retectl (operator, authoring)
retectl ca init --out <dir> # once per rete: generate CA keypair + ca.crt
retectl ca sign <csr> --name <name>
--kind <user|service|node|management-plane|control-plane|vertex>
[--scope rete|node]
# sign a CSR into an X.509 cert.
# All six kinds are recognised from day one;
# `vertex` and `control-plane` aren't exercised
# at runtime in C0 (see ADR-0005).
# `management-plane` is how the rete's
# mgmt-envelope signer is minted in C0 — e.g.
# retectl ca sign --kind management-plane \
# --name primary
# produces the keypair `retectl publish` signs with.
retectl issue-bundle --node <node-name> --rete <coordinator-url>
[--validity <duration>] [--out <bundle>]
# per-node enrollment bundle: CA cert, node
# identity, and every workload identity
# (users/services) declared in YAML to run
# on this node, plus coordinator bootstrap.
# Flow A only in C0 (operator-generated keys);
# --csr / Flow B deferred (see below).
retectl validate [--repo <dir>] [-f <file|glob>…]
# schema + cross-ref + access consistency
retectl compile [--node <name>] [--repo <dir>] [--out <dir>] [-f <file|glob>…]
# compile all nodes (or one); write to
# <repo>/.flor/compiled/ by default
retectl publish [--repo <dir>] [--target <coordinator-url>]
# push compiled artifacts to the rete
# coordinator (see Coordinator)Notes:
- Source discovery.
validate/compilerequirerete.yamlat the--reporoot; if it is missing or nested elsewhere, discovery cannot start and a command fails. Then recursively glob*.yaml/*.ymlunder--repo(skipping.flor/,certs/, dotfiles; honoringinclude/excludeinrete.yaml), then merge same-kind collections into one whole-view before compiling. File layout is operator-chosen — see Source Layout.-f <file|glob>…(repeatable) overrides discovery for partial or CI runs; when used, at least one selected file must contain the singletonreteblock. retectl ca initruns once per rete. CA private key never leaves operator's secure storage.retectl ca signis the low-level primitive;retectl issue-bundleis the high-level wrapper that computes which principals live on a node (fromusers.yaml+services.yaml), signs each of their certs, and packages everything alongside the node identity and coordinator bootstrap info. One bundle per node, not per principal (avoids the combinatorial mess of separate bundles for a node hosting five services).--kindonca signselects the SPIFFE path namespace and the X.509 extension policy applied to the resulting cert. All six kinds are accepted from day one (user,service,node,management-plane,control-plane,vertex);vertexandcontrol-planearen't exercised at runtime in C0 but the CLI surface is stable. TLS-capable kinds (user,service,node,vertex) getkeyUsage: digitalSignature, keyEncipherment+extKeyUsage: serverAuth, clientAuth; signing-only kinds (management-plane,control-plane) getkeyUsage: digitalSignatureonly, noextKeyUsage— see ADR-0005 for the rationale.retectl compilewrites artifacts to.flor/compiled/<node>/mgmt/inside the repo (agent.jsonandflor.jsonper node, flat; pluscoordinator.jsonon the management node). Artifacts contain no filesystem references — identity is referenced by SPIFFE ID and materialized by each node's identity store (see Agent § Artifact handling). No secrets in artifacts; committing them turns the git log into an auditable shipment history. Nodes don't pull from git; they fetch from the coordinator (see Coordinator).retectl publishuploads the current.flor/compiled/tree to the rete's coordinator; nodes fetch their own artifacts on the nextflor agent sync. Publish is separate from compile so the operator can review the diff (and the audit commit) before the change is visible to nodes. Publish is idempotent — re-running with the same tree is a no-op on the server.- Nothing in
retectlaffects running agents directly. It reads and writes the rete repo plus operator-local CA material, and talks to the coordinator over the rete's own Florete. Safe to run anywhere the operator has the repo checked out and is enrolled as an operator principal.
flor (node, daemon + user CLI)
flor id create --node <node> [--principal <kind>/<name>]...
# generate keypairs locally for the node and
# any workload principals to run on it;
# package CSRs into a bundle (Flow B —
# low-level primitive; not in the C0 path)
flor enroll <bundle> [--as <scope>] # bootstrap: install certs, install initial
# compiled artifacts, start agent
# (two-step: bundle → sync)
# scope defaults to rete name in bundle;
# --as overrides if name collides locally
flor install [--system|--user] # install or upgrade this host: place executables
# + per-OS service wrappers, write the install
# receipt; run by install.sh; idempotent.
# Any release installs over any other — going
# back is installing the older one — and it
# never changes which scopes are active.
# Migrations are not its business (see Installer)
flor uninstall [--purge] # stop agents, remove what it placed: binaries,
# wrappers, runtime roots, receipt and lock.
# Scope roots, config.toml and the flor user
# survive unless --purge (see Installer)
flor version # release version, install mode, every resolved
# directory, and the release this one replaced
flor agent run [--rete <scope>] # the supervisor daemon (run by the OS service
# wrapper); reads agent.json from the active
# artifact set; auto-detects the scope if only
# one rete is enrolled; supervises one
# `flor-vertex` in C0 (+ coordinator on mgmt01).
# One agent per scope, enforced by a per-scope
# lock in the runtime root
flor agent sync [--rete <scope>] # fetch + verify + install latest artifacts,
[--dry-run] # then restart the vertex. Scope auto-detected
# if only one rete enrolled. --dry-run shows
# which version would install, no restart.
# (commit-timeout/--confirm land in C1)
flor agent status [--rete <scope>] # local agent state + active artifact version
flor agent enable [--rete <scope>] # activate this scope's service wrapper and
flor agent disable [--rete <scope>] # start it (enrollment's step 2, standalone);
# disable stops and deregisters it. Scope
# auto-detected if only one rete is enrolled,
# as elsewhere. Needed to resume a scope that
# survived an uninstall, and to leave one rete
# without touching others (see Installer)flor-vertex (agent-spawned, data plane)
flor-vertex [--rete <scope>] # the data-plane daemon. In production the agent
[--name <name>] # spawns it with full FLORWL_* context — no flags.
[--set <version>] # Flags exist for debugging only, as explicit
# overrides of the same variablesflor-coordinator (agent-spawned, management node)
flor-coordinator # the store-and-serve management-node service.
# Spawned by the agent with the same uniform
# FLORWL_* context and no flags of its ownNotes:
- State directory. Two deployment modes, chosen by the installer (see Installer § Modes and Directories): user mode (
$XDG_STATE_HOME/flor, macOS included — personal devices, user-level service, and the only macOS mode in C0) and system mode (/var/lib/flor; servers, dedicatedflorservice user). Node-local commands resolve it as: explicitFLOR_STATE_HOME(one of the per-directory development/CI overrides), else the column of the directory table selected by the mode the invoked binary infers from its own prefix — so which install a command acts on is which binary you typed, andflor versionprints the answer. The directory holdsretes/<scope>/per enrolled rete, created byflor enroll, plus two files belonging toflor installalone — its receipt and its lock, which no other install channel writes. Enrolled retes live at<state-dir>/retes/<scope>/; config, sockets, and executables have directories of their own. flor id createis the low-level primitive for Flow B (security-purist) enrollment; it generates keypairs for the named node + any principals hosted on it, emitting a CSR bundle for the operator to sign. Flow B is deferred from the C0 enrollment path — an open question for C1 (see C1 Scope); the command stays in the surface as a primitive, but C0 onboarding uses Flow A only. When exercised, the operator tells the user which principal names to include (out-of-band), so the bundle matches whatusers.yaml+services.yamldeclare for that node.flor enroll <bundle>is a two-step bootstrap. Step 1: unpack the bundle (CA cert + node cert + key + workload certs + keys + coordinator URL + expected coordinator SPIFFE ID + an initial artifact set) and write it into the scope root — theidentity/subtree (ca.crt, therete.jsontrust-domain record, andcerts/) plusartifacts/mgmt/sets/<version>/and themgmt/currentpointer — where<scope>is the rete name embedded in the bundle (override with--as <scope>if it collides with an already-enrolled rete). This gives the node enough material to start running immediately. Step 2: activate the installed service wrapper for this scope (systemctl enable --now flor-agent@<scope>or the launchd equivalent; the wrapper itself ships with the installer, not with enroll — see Agent § Supervision mechanics), which startsflor agent run; this step is exactly whatflor agent enabledoes standalone, which is how a scope that survived an uninstall resumes with no bundle at all (Enrollment § Activation and resume); the agent brings up its workloads and performs its startup sync to refresh against whatever the rete currently runs. Re-keying (or adding a new workload on this node) re-runs the same command with a freshly-issued bundle; re-enrolling an existing scope is not hot — it stops the scope's workloads, replaces store and sets, and restarts.flor agent syncis a thin client of the running agent's socket — the daemon executes the sync (it also syncs once at startup and on a poll timer — interval set bypoll_intervalinagent.json'ssyncblock). The daemon polls the coordinator with the held set version (a no-op while unchanged — the cheap poll), fetches and verifies the complete new set when it moved (acting as thenode/<node>principal via the local vertex's SOCKS5), stages it, activates it via themgmt/currentpointer, and restarts the workloads whose payloads changed — restarting itself through the service wrapper when its own payload changed. Fetch → verify → stage → restart; see Agent § Sync. No compilation on the node — artifacts were pre-compiled by the operator and published to the coordinator.- No commit-timeout / auto-rollback in C0. A sync activates the new artifacts and restarts the vertex; there is no confirm-or-roll-back guard. The Cisco-IOS-style
commit confirmedsafety net lands in C1. In C0 a bad sync is bounded by keeping an out-of-band emergency-access path (see Distribution & Reload · Safety Net), which suffices for limited pilots that never rely on Florete as the single access path. flor agent runis the long-running supervisor process per node. It readsagent.jsonfrom the active artifact set and forks a child per entry in itsworkloadsinventory, in list order — each entry'srunargv spawned with the uniformFLORWL_*environment (see Agent § Supervision contract). In C0 that's one child (plus the coordinator on the management node); C1 adds a second vertex for the mesh-flor layer. Launch mechanics are pinned in Agent § Supervision mechanics: the OS service manager keeps the agent alive; the agent forks and supervises its workloads directly (restart backoff, exit-code vocabulary, liveness window); per-workload launch runtimes (system units, containers, remote WRI) are a reserved evolution.flor-vertexis the data-plane daemon. In production the agent spawns it with the uniformFLORWL_*environment (see Agent § Supervision contract): it reads the configFLORWL_MGMT_ARTIFACTpoints at (plusFLORWL_CTRL_ARTIFACTfor a mesh vertex), resolves identity material against the scope root, and runs the workloads/io/transport-endpoint/connection-manager described there. The CLI flags are debug-only overrides of the same variables, all explicit: no auto-detection, and the vertex never reads themgmt/currentpointer — one uniform resolution path whatever the source. It does not re-verify signatures (the agent is the sole verifier); it is unaware of the agent, the coordinator, and the rest of the rete.flor agent statusreads from a per-rete Unix socket (agent.sock, under the scope's runtime root —/run/flor/<scope>/or the user-mode equivalent) exposed by that rete's agent. The socket speaks a structured protocol;statusrenders it for humans, and tools read fields directly —retectluses the endpoint-discovery map this way.--rete <scope>selects which socket to use; auto-detected when only one rete is enrolled. A future tray-icon GUI (B1+) will be another client of the same socket — CLI and GUI share the same daemon API.
Explicitly deferred from C0:
- Any YAML-mutation subcommands.
- Flow B / CSR-based enrollment wired into onboarding (
retectl issue-bundle --csr,flor id create) — open question for C1. - Multi-hop paths and label allocation (C1 feature).
- SIGHUP hot reload.
- CRL / OCSP. (Revocation is by name removal +
flor agent sync.) - Delta state updates.
- iptables-based transparent outbound redirection for services that can't speak SOCKS5.