scribe-gateway provides one normal Scribe endpoint backed by existing storage
instances. It routes repository operations, streams object uploads/downloads,
and places new repositories using observed instance sizes and repository counts.
It does not start storage instances, move repositories, replicate data, or make
an unavailable repository available from another instance.
Gateway and auth are independently optional. Start with scribe-server alone;
add auth with the existing explicit auth-init workflow when needed; introduce
a gateway when several storage instances are useful. A gateway can also front
several local plaintext servers without auth. Public deployments require TLS and
authenticated storage instances using the same auth service and namespace.
Local quick start
Start two ordinary servers, directly or using scribe-ctl. For example, the following commands initialize two new roots explicitly:
scribe-server init --data-dir ./storage-a
scribe-server init --data-dir ./storage-b
scribe-server serve --data-dir ./storage-a --listen tcp://127.0.0.1:7447
# In another terminal:
scribe-server serve --data-dir ./storage-b --listen tcp://127.0.0.1:7448Build scribe-gateway with cargo build --release -p scribe-gateway --locked.
Create and edit its example configuration:
scribe-gateway example > scribe-gateway.toml
scribe-gateway --config scribe-gateway.toml init
scribe-gateway --config scribe-gateway.toml serveinit requires a new or empty gateway data directory. Serving never initializes
or migrates it. serve stays in the foreground; Ctrl-C or a supervisor's
shutdown-request file drains admitted work. --runtime-dir DIR publishes
listen and pid readiness files for a supervisor. Instance membership is read
from TOML at startup; restart the gateway to add another configured instance.
An instance that comes online later is discovered by subsequent polls.
The CLI uses its existing server option; no gateway-specific client flags exist:
scribe --server tcp://127.0.0.1:7557 repo create game
scribe --server tcp://127.0.0.1:7557 repo list
scribe --server tcp://127.0.0.1:7557 workspace create game ./work --author ann
scribe-gateway --config scribe-gateway.toml status
scribe-gateway --config scribe-gateway.toml status --jsonExisting workspaces can use --server or SCRIBE_SERVER to select the gateway
instead of their recorded direct endpoint. Repository IDs, branch IDs, operation
IDs, and stored history remain on their original instance. Existing repositories
are discovered without copying their data.
TLS configuration
Use a certificate trusted by clients at the gateway endpoint. Trust this endpoint using the existing client trust workflow before sending credentials. The gateway terminates TLS, so it is part of the trusted service infrastructure. User credentials are held in memory and forwarded only after verifying upstream TLS; backend storage still checks each user's authorization. Monitoring credentials are never substituted for user credentials.
data_dir = "gateway-data"
listen = "0.0.0.0:7557"
poll_interval_secs = 15
stale_after_secs = 60
backend_timeout_secs = 30
repository_reserve_bytes = 1073741824
min_free_bytes = 10737418240
[tls]
cert = "pki/gateway/server.pem"
key = "pki/gateway/server-key.pem"
[[instances]]
name = "storage-a"
endpoint = "tls://storage-a.example:7447"
ca = "pki/ca/ca.pem"
monitor_token_file = "secrets/inventory-session"
capacity_bytes = 1099511627776
[[instances]]
name = "storage-b"
endpoint = "tls://storage-b.example:7447"
ca = "pki/ca/ca.pem"
monitor_token_file = "secrets/inventory-session"
capacity_bytes = 1099511627776Paths are relative to the TOML file. Every TLS instance needs a trusted CA and a monitor credential with namespace-administrator rights, such as an appropriately provisioned administrator session. Keep these files outside the gateway data root, protect them as secrets, and rotate expiring sessions. The inventory protocol requires these rights because it exposes all repository names and sizes. The current implementation does not define a narrower inventory-only credential. Plaintext mode accepts only loopback listeners and loopback backends, with no credentials; mixed plaintext/TLS backends are rejected.
Storage instances must include the inventory/preflight protocol additions shipped
with this gateway. Ordinary direct clients still use their existing protocol.
The gateway's own status command reads a private local snapshot; it does not
expose inventory to ordinary remote users.
Placement and reconciliation
Polling returns the immutable data-root identity, total repository count, per-repository IDs/names/apparent sizes, total apparent data-root bytes, and filesystem space available to the service. Repository pages contain at most 128 entries. Measurements read file metadata, not object contents, and do not follow links. Sizes are observations of a live filesystem, not an atomic storage snapshot. Whole-root size includes uncataloged files, so direct growth contributes to placement even when it did not pass through the gateway.
Only connected instances with a successful inventory newer than
stale_after_secs are eligible. Failed polling immediately excludes an instance.
The gateway adds a configurable baseline reservation for each repository to its
observed byte load, and also accounts for newly assigned repositories that were
not in the last observation. It selects the lowest load, normalized by optional
capacity_bytes; ties use repository count and then instance name. Use capacity
quotas consistently across instances when weighting machines of different sizes.
It requires at least min_free_bytes plus a new repository reservation to remain
available, and rejects placement beyond a configured quota. Unknown free space
is not eligible. This is a placement heuristic, not an enforceable storage quota
or a prediction of future repository growth.
Before forwarding a create, the gateway durably reserves its name on one
instance. Before acknowledging success, it records the returned repository ID.
An interrupted create stays pinned; a later attempt checks the same instance's
catalog before attempting creation. An existing completed repository still
returns the standard REPOSITORY_EXISTS error. Submit and branch operation IDs
and their outcomes pass through unchanged; unknown transport outcomes remain
unknown and are resolved at the original instance.
Polling imports repositories created directly on a backend. Duplicate names, duplicate IDs, missing known repositories, and replaced instance identities fail closed instead of silently changing ownership. Conflicting names are recorded in the ledger and require operator investigation; this version has no automated conflict repair or relocation command. Keep repository names unique across the cluster. Do not modify catalog ownership or introduce duplicate names behind the gateway. A backend may remain directly accessible if its deployment permits it, but normal repository creation should use the gateway once introduced.
Routes survive outages and restarts. A configured instance owning routes cannot simply be removed from TOML. Requests for unavailable repositories fail rather than redirecting to another root. Authenticated lookup failures conceal placement details. A repository list fails if an instance cannot be queried, instead of silently presenting an incomplete list.
Operations and limits
status --json includes observation timestamps, instance counts/bytes/free space,
health errors, and repository assignments with sampled sizes. Observations update
on polls, so a just-created repository may not appear until the next poll.
status does not probe or mutate instances. After shutdown, it labels the last
snapshot as stopped. The default poll interval is 15 seconds, staleness limit
60 seconds, and upstream I/O timeout 10 seconds. Tune these for large inventories
and slower networks. There are at most 64 configured instances and one million
repositories per collected instance inventory. Metadata scans can be costly on
large roots; increase the interval as needed.
The gateway reuses the bounded TCP server workers and transfer admission. Object bytes stream through it; they are not staged on gateway disk. TLS and transfer encoding introduce proxy work. There is one active gateway owner per data root, not a highly available gateway cluster. Protect and back up its routing ledger: losing it loses pending placement decisions. Stop the gateway before copying its data directory. The backend data roots remain authoritative for repository data.
--timings, --log-level, and --log-format text|json control gateway diagnostics.
Timing support is default-enabled; building with
cargo build -p scribe-gateway --no-default-features --locked compiles out timing
scopes and makes --timings an error. Logs contain no credential values.