krabka-cli

September 14, 2026 ยท View on GitHub

The krabka operator CLI, krabka.

Subcommands

Built in

krabka format prepares a fresh log directory, optionally seeding SCRAM credentials and KIP-1022 feature levels. krabka gres operates the Gres tenant registry and range layout, including tenant creation, inspection, balancing, and PgDog configuration rendering.

Plugins

Everything else is a plugin. An unrecognised subcommand is delegated to krabka-<name> on PATH, the way git finds git-foo and cargo finds cargo-foo:

krabka gres list --bootstrap localhost:9092

Each plugin is a separate install, and krabka --help lists the known ones:

  • krabka admin-ui runs krabka-admin-ui, the operator web UI. This repository builds it; see Admin UI below.
  • krabka restore runs krabka-restore, a point-in-time restore of a cluster data directory from a tiered-storage archive. It ships from krabka-broker, so install it separately.

The Gres operator command is built in because the demo uses it to provision its tenant before the Gres server starts accepting PostgreSQL connections.

A built-in always wins, so a stray krabka-format on PATH cannot shadow the compiled-in one. A subcommand that is neither built in nor on PATH exits 127, and one that is found but cannot be run exits 126 -- the codes a shell uses for the same two cases.

Admin UI

crates/admin-ui builds krabka-admin-ui, the second operator-facing surface in this repository. It is a web UI, not a terminal UI: a Dioxus component tree that the server renders to HTML with dioxus-ssr and serves over axum. Every page is rendered on the server, so there is no WebAssembly bundle, no JavaScript, and no static asset directory to build or ship. The binary is the whole deployment.

KRABKA_ADMIN_UI_BOOTSTRAP=localhost:9092 krabka admin-ui

It signs an operator in with SASL/SCRAM-SHA-512, holds the session server-side behind an HttpOnly cookie, and reads the operator's ACLs to decide which pages and actions to show. The pages are overview, topics, groups, ACLs, users, quotas and log directories.

Each mutation is a plain HTML form that the browser posts. The server refuses a mutation that carries no CSRF token, because the session cookie alone does not show that the operator asked for it. A form sends the token in its csrf_token field. A JSON client sends the same token in the x-krabka-csrf header. The token belongs to one session, so a page on another origin cannot supply it.

It stays a separate binary rather than a subcommand module of krabka, for the reason given under Plugins: the component framework is a large dependency graph, and the CLI does not take it on. krabka admin-ui finds it on PATH like any other plugin.

The UI talks to one service, the broker, through the Kafka admin protocol. krabka-client-rs supplies that client: krabka-client-admin for metadata, groups, log directories, ACLs, SCRAM users, quotas and configuration, and krabka-client-core for the security handshake. It reaches no HTTP endpoint of its own and no other krabka-io service.

Layering

Depends on four sibling repositories, pinned by revision in Cargo.toml's [patch.crates-io]:

RepositoryWhat it supplies
krabka-protocolWire types, security, metadata, units
krabka-client-rsThe admin and core clients
krabka-brokerRaft, and the bootstrap records format writes
gresGres registry, range planning, and tenant provisioning

crates/admin-ui uses two of the three: the admin and core clients, and the wire-layer security and unit types. It does not use krabka-broker.

Build

cargo test --workspace
bazel test //...

Both are supported and both are gated in CI. Cargo stays the dependency source of truth; Bazel reads the same Cargo.toml and Cargo.lock.

bazel run //:krabka -- format --help

Candidate-broker qualification

The ignored candidate_broker test runs the authenticated admin command matrix against a deployed candidate and emits one JSON evidence document. Run it from an immutable CLI revision and archive both the output and its digest:

export KRABKA_CLI_REVISION="$(git rev-parse HEAD)"
export KRABKA_CANDIDATE_REVISION='<40-character broker commit>'
export KRABKA_CANDIDATE_IMAGE='<registry/repository@sha256:digest>'
export KRABKA_CANDIDATE_BOOTSTRAP='<host:port>'
export KRABKA_COMMAND_CONFIG='<authenticated Kafka properties file>'
export KRABKA_DENIED_COMMAND_CONFIG='<properties file for the denied principal>'
export KRABKA_DENIED_PRINCIPAL='User:<principal named by the denied config>'
set -o pipefail
cargo test -p krabka-cli --test candidate_broker \
  authenticated_admin_matrix_matches_real_broker_state \
  -- --ignored --exact --nocapture \
  | tee candidate-broker.jsonl
sha256sum candidate-broker.jsonl

The properties files support security.protocol, PEM TLS, SASL PLAIN, SCRAM, GSSAPI, and a file-backed OAuth bearer token. They must not be archived with the evidence. The candidate must have at least two brokers. The denied principal must initially be able to describe the test topic so the lane proves that the ACL created by the matrix causes the recorded authorization failure.

The matrix records arguments, credential label, structured response, and exit status for every operation. It also observes broker state after topic creation, configuration, offset reset, reassignment, and deletion. Reassignment evidence contains every progress check through completion; invalid and unauthorized requests must return structured errors with a nonzero exit status.