Documentation/Run your server
Local orchestration5 min read

Local clusters with scribe-ctl

On this page

scribe-ctl provisions and supervises one optional auth service and any number (up to 256) of local storage services from TOML. It runs native Scribe binaries; OpenSSL 3 is needed when generating certificates.

Build with cargo build --release --locked. Keep scribe-ctl, scribe-auth, and scribe-server together. On macOS, ~/.local/bin is a convenient per-user installation directory when added to PATH; the client scribe can live there too. This does not install a launchd job or arrange startup after reboot.

Without --config, the controller reads scribe-cluster.toml beside its executable. An explicit config is usually easier for separate clusters:

sh
scribe-ctl example > ./scribe-cluster.toml
# Create accounts.toml and its referenced password files before init.
scribe-ctl --config ./scribe-cluster.toml init
scribe-ctl --config ./scribe-cluster.toml up
scribe-ctl --config ./scribe-cluster.toml status
scribe-ctl --config ./scribe-cluster.toml logs auth --lines 50
scribe-ctl --config ./scribe-cluster.toml down

The complete cluster template is example.toml. All relative paths in it resolve against the config directory. Binary paths can be overridden with [binaries], using auth and storage keys. Otherwise, binaries are located beside scribe-ctl.

Accounts and pepper

The separate accounts_file contains password-file references, never inline passwords. Password paths resolve against the accounts file's directory:

toml
[[users]]
name = "alice"
password_file = "passwords/alice"
administrator = false

[[users]]
name = "operator"
password_file = "passwords/operator"
administrator = true

Use private password files containing 12–1024 UTF-8 bytes; the existing auth password reader handles a trailing line ending. Keep this file and secrets out of version control. Omit accounts_file if no account bootstrap is wanted.

init explicitly creates a missing auth database, writing its required pepper to auth.pepper_file and initial admin token to auth.admin_token_file. It passes the pepper file to every auth service startup. Existing auth databases require their original pepper: the controller never regenerates a missing pepper for an existing database. Retain and back up the pepper separately from the database as described in auth.md.

init also registers authenticated storage identities and writes their service tokens, then bootstraps users. Repeat runs skip existing users without reading their password files or changing their password, administrator status, or enabled state. The initial admin account is consequently left unchanged too.

sh
scribe-ctl --config ./scribe-cluster.toml bootstrap-users

This command can add accounts while the cluster runs. When stopped, it starts auth temporarily and drains it afterward. Bootstrap operation IDs and password commitments are persisted under state_dir/bootstrap-users before requests are sent. An interrupted bootstrap resumes the same operations and checks user identity before setting a password. Preserve those records and the original password/configuration while an operation is pending. They contain no plaintext passwords. Account removal and password rotation remain explicit auth admin operations; removing a user from this file does not delete an account.

The admin token is required for provisioning and account bootstrap; ordinary up needs the pepper and service tokens, but does not read the admin token.

Certificates and topology

certificates.generate_missing = true permits init or certs to create a missing CA and missing leaf certificate/key pairs. Existing PEMs are preserved and leaf pairs are validated. A partial pair is refused: preserve/move the surviving file before explicitly regenerating the pair. This also applies to incomplete CA material. Certificates are never silently rotated on up.

sh
scribe-ctl --config ./scribe-cluster.toml certs

This requires the cluster stopped. Default generated SANs cover localhost, IPv4/IPv6 loopback, and the concrete listen IP. Set dns and/or ip arrays in a service's tls table for other names; explicit arrays replace these defaults. auth.ca can select an existing trust bundle; by default the controller uses certificates.ca_dir/ca.pem.

Auth requires TLS and a fixed, nonzero listen port so bootstrap retries retain the same endpoint. Authenticated storage requires auth = true, namespace, token_file, and a tls table. In the current storage adapter TLS is coupled to auth integration. Standalone storage uses auth = false and no TLS/auth fields; non-loopback plaintext requires allow_non_loopback = true explicitly. Storage port zero is allowed, with its assigned endpoint shown by status.

Each data root and the state directory must be distinct and nonoverlapping. Private keys, pepper, and service credentials must be outside data roots. Unknown TOML fields are rejected to catch configuration mistakes.

Lifecycle and diagnostics

init is explicit provisioning; up never initializes data or creates secrets. daemon = true makes up detach and return once all services report readiness. The default is foreground. up --daemon and up --foreground override TOML. In foreground mode, Ctrl-C requests graceful shutdown.

The supervisor starts auth first, followed by storage. It drains storage before auth during normal shutdown. Startup failures and unexpected service exits trigger cleanup of the other children. There is no automatic restart policy. Service output is appended to state_dir/logs/<service>.log; detached supervisor output is in supervisor.log. logs prints a bounded tail, not a live stream. These files are not automatically rotated.

status --json returns machine-readable runtime state. Status is read-only. Exclusive locks prevent duplicate controllers and conflicting data-root owners. If the supervisor is lost, status reports unmanaged services; down can still request shutdown using their unique runtime directories. Saved PIDs are only informational and are never signalled.

startup_timeout_secs defaults to 30 (allowed range 1–300), applies per service on startup, and bounds how long down waits. A shutdown timeout reports that services are still draining; it never force-kills a durable commit. Leave the supervisor running and retry status/down. Configured binaries must implement Scribe's readiness and cooperative shutdown protocol.

--timings reports controller phases and command duration to stderr; --log-format json selects structured diagnostics. Service logs remain separate. Timing support is compiled in by default. Building this executable with cargo build -p scribe-ctl --no-default-features --locked compiles out its timing scopes and makes --timings return an error.

An optional scribe-gateway can provide one client endpoint for several existing storage instances. Standalone operation remains supported.

Source docs/cluster-usage.mdSnapshot 93d02b17