Documentation/Run your server
Repository gateway6 min read

Optional repository gateway

On this page

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:

sh
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:7448

Build scribe-gateway with cargo build --release -p scribe-gateway --locked. Create and edit its example configuration:

sh
scribe-gateway example > scribe-gateway.toml
scribe-gateway --config scribe-gateway.toml init
scribe-gateway --config scribe-gateway.toml serve

init 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:

sh
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 --json

Existing 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.

toml
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 = 1099511627776

Paths 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.

Source docs/gateway-usage.mdSnapshot 93d02b17