Skip to main content

Crate didbot_serve

Crate didbot_serve 

Source
Expand description

HTTP surface and the development server.

This crate is the outside of the system: it turns provisioning into [axum] routes, and turns what the provisioner does into a log a developer can read while it happens.

The whole surface is built around one object-safe trait, didbot_pds::Registry. The provisioner is generic over its DNS provider and its account store; the HTTP layer must not be, or every handler signature carries type parameters that no route cares about. Arc<dyn Registry> is where that genericity stops.

§Running it

The didbot-pds binary in this crate wires a development stack – loopback DNS, an in-memory store – and serves it, so that one command produces a server for didbot-swarm to drive:

cargo run -p didbot-serve --bin didbot-pds

§Routes

RoutePurpose
GET /on the server’s own hostname, a page naming it, its zones and its claim; elsewhere the same 404 as any unserved path
GET /healthliveness, and which zone this server mints under
GET /.well-known/did.jsonthe DID document for the request’s Host
GET /.well-known/atproto-didthe DID of the account whose handle is the request’s Host, 404 when nobody holds it
POST /xrpc/bot.did.createAccountcreate an account beneath the creator the credential names
POST /xrpc/bot.did.createSessiona session as a hosted account, from a key-signed JWT or an OpenID Connect token
POST /xrpc/bot.did.parkKeyhold a public key for minutes, for the operator to admit under a name
GET /xrpc/bot.did.describeParkedKeythe key parked under a name
POST /xrpc/bot.did.deleteAccounterase one: data gone, identity kept
POST /xrpc/bot.did.freezeAccount, unfreezeAccounthang or lift the account’s own frozen lock
POST /xrpc/bot.did.deactivateAccount, activateAccounthang or lift the account’s own deactivated lock
GET /xrpc/bot.did.listAccountslist them
POST /xrpc/bot.did.pollOperatorClaimask this server to look for its operator’s claim now — no credential, because the nudge confers nothing; see auth
POST /xrpc/com.atproto.repo.createRecordwrite a record to a repository
POST /xrpc/com.atproto.repo.putRecordwrite one at a key you choose
POST /xrpc/com.atproto.repo.deleteRecordremove one, or make sure it is gone
POST /xrpc/com.atproto.repo.applyWritesa batch of the three above
GET /xrpc/com.atproto.repo.getRecordone record by collection and key
GET /xrpc/com.atproto.repo.listRecordsread a collection back, newest first
GET /xrpc/com.atproto.repo.describeRepothe account, its document and its collections
GET /xrpc/bot.did.statsaccounts, records, bytes per collection, blobs by lifecycle state, the policy revision enforced
GET /xrpc/com.atproto.server.describeServerthis server’s DID and its real zone-derived handle domains
GET /xrpc/com.atproto.server.getServiceAutha service-auth token signed as the caller’s account
GET /xrpc/com.atproto.server.getSessionthe account the caller’s credential signs in as: its DID, handle and status
GET /xrpc/com.atproto.identity.resolveHandlethe DID a handle points at, hosted here or not
GET /xrpc/com.atproto.identity.resolveDida DID’s document, hosted here or not
GET /xrpc/app.bsky.actor.getPreferencesthe account’s preferences: none, since this server stores none
POST /xrpc/app.bsky.actor.putPreferencesMethodNotImplemented, and never proxied
any other /xrpc/ method, with atproto-proxythe call, forwarded to the service that header names; see proxy
any other app.bsky.* method, without itthe same, to [proxy] app_view when one is set

Every repo above is an at-identifier: a DID or a handle this deployment issued. Which credential each route needs, if any, is auth::ROUTE_CREDENTIALS — the read surface stays Public, and every com.atproto.repo.* write route takes the agent token createAccount hands back; see that module’s own doc for the full taxonomy.

Failures are returned in atproto’s error shape, {"error":..,"message":..}, with the names the lexicons declare – RecordNotFound, RepoNotFound, InvalidRequest, InvalidRecord, MethodNotImplemented. See ApiError for the whole mapping, and for the two conditions that get a name no lexicon declares.

Re-exports§

pub use blob_collect::collect_once;
pub use blob_collect::BlobCollector;
pub use blob_collect::DEFAULT_COLLECT_INTERVAL;
pub use confinement_poll::ConfinementPoll;
pub use confinement_poll::DEFAULT_CONFINEMENT_INTERVAL;
pub use lookups::DeclaredLookups;
pub use lookups::Reader as LookupReader;
pub use operator_handle::OperatorHandle;
pub use operator_poll::OperatorPoll;
pub use operator_poll::OperatorRecords;
pub use operator_poll::PollNudge;
pub use operator_poll::PollSchedule;
pub use operator_poll::MIN_POLL_INTERVAL;
pub use policy_poll::list_records;
pub use policy_poll::PolicyPoll;
pub use policy_poll::PolicyPollError;
pub use policy_poll::PollError;
pub use stale_sweep::sweep_once;
pub use stale_sweep::StaleSweeper;
pub use stale_sweep::SweepSchedule;
pub use stale_sweep::DEFAULT_SWEEP_INTERVAL;
pub use stale_sweep::DEFAULT_SWEEP_LIMIT;
pub use stale_sweep::DEFAULT_UNFINISHED_AGE;
pub use tree_reload::TreeReloader;
pub use tree_reload::DEFAULT_TREE_RELOAD_INTERVAL;

Modules§

analytics 🔒
The analytics dashboard: this deployment’s accounts, drawn as the tree they form.
auth 🔒
The credential taxonomy: what this server accepts, and what each route requires. See plan/auth-types.md.
blob_collect
The timer that runs didbot_pds::Provisioner::collect_blobs.
blobs 🔒
The three blob methods, and the streaming that keeps them honest.
confinement_poll
The confinement poll: re-derives every inherited lock from the tree of principals on a schedule.
dashboard 🔒
The per-server homepage and operations dashboard: this server, reporting on itself. docs/web-surfaces.md calls this surface 3 of the project’s four websites, and draws the boundary this module keeps: it is served by didbot-serve, about the deployment serving it, one per deployment rather than one for the whole project, and it owns no policy — policy is written from a separate, statically-hosted origin (surface 2) and only ever read here, never edited.
dashboard_accounts 🔒
The operations dashboard’s account view: every account this deployment holds, and one account in full.
dashboard_apps 🔒
The operations dashboard’s per-app view: what each OAuth application holds on this server, and what it did.
error 🔒
The error body every failing route returns.
foreign 🔒
The deployment’s one reader for records in repositories it does not hold.
handles
Forward handle resolution: the DID a handle points at.
health 🔒
The periodic line that says what the deployment currently holds, and the state behind GET /health’s progress report.
home 🔒
GET / on the server’s own hostname: a page naming this server, its zones and who stands behind it.
landing 🔒
Serving didbot_brand::page: every page a browser lands on during a sign-in here, the operator’s (crate::dashboard) and an account’s (crate::routes::oauth), and every refusal it can meet on the way.
lookups
Reading the records a write’s policies declared they read, before the write is judged.
oauth
OAuth 2.0 for atproto (plan/oauth.md).
operator
Signing in as the operator, over atproto OAuth, to reach this server’s operator controls.
operator_handle
The handle the operator goes by, for the homepage.
operator_poll
Drives didbot_pds::OperatorRelation’s poll against a real operator repository, and reacts to what it finds. See plan/handshake.md.
policy_poll
The policy poll: the operator’s bot.did.policy and bot.did.policyBinding records, read on the operator poll’s tick and built into one policy set.
proxy 🔒
atproto-proxy: an XRPC call this server does not serve, forwarded to the service a DID document names.
rate_limit
A minimal fixed-window rate limiter, shared by every anonymous route that does real work before it knows who is calling.
refusal 🔒
Refused OAuth and XRPC requests, as the journal says them.
relay
com.atproto.sync.requestCrawl and notifyOfUpdate, called outward.
replay
The bounded “spent once” map both proof checks keep.
routes 🔒
The router: every route the development server answers.
service_auth 🔒
com.atproto.server.getServiceAuth, and the rules a proxied call shares with it; see crate::proxy.
stale_sweep
The timer that runs didbot_pds::Provisioner::sweep_stale_bounded.
subscribe 🔒
com.atproto.sync.subscribeRepos: this server’s repositories, as atproto wants to hear about them.
subscribers
How many streams this server holds open at once, and how many of them one caller may hold.
surface 🔒
What the two dashboards this server serves do the same way.
tree
The ownership tree over HTTP: creating an account beneath a creator, parking a key for the operator to admit, and a session from a proof.
tree_reload
The timer that re-reads [tree]’s bounds from the configuration file.
wire 🔒
The JSON shapes the XRPC routes read and write.
write_meter
Per-account write metering: one token bucket on write operations and one on request bytes, both keyed by the authenticated account.

Structs§

AccountSummary
One entry in the listAccounts response.
ApiError
A failure rendered as an HTTP status plus an ErrorBody.
ApplyWritesRequest
Body of com.atproto.repo.applyWrites.
AuthState
Session storage, the e-stop latch and the operator credential, bundled so a caller building app_with_auth hands them over together.
BlobsView
The blobs member of StatsResponse: this deployment’s blobs, by the state each one is in.
CertificateHealth
What GET /health reports about certificate renewal.
ConnectInfoService 🔒
The per-connection service MakeServiceWithConnectInfo hands back: inner, unchanged, with ConnectInfo(addr) inserted into every request’s extensions before the request reaches it.
CreateAccountRequest
Body of bot.did.createAccount.
CreateRecordRequest
Body of com.atproto.repo.createRecord.
CreateRecordResponse
Response of com.atproto.repo.createRecord.
DeleteRecordRequest
Body of com.atproto.repo.deleteRecord.
DescribeRepoQuery
Query of com.atproto.repo.describeRepo.
DescribeRepoResponse
Response of com.atproto.repo.describeRepo.
DescribeServerResponse
Response of com.atproto.server.describeServer.
DidRequest
Body of bot.did.deleteAccount, and of the freeze and deactivate routes and their inverses.
Disclosure
Per-route configuration for every Credential::Disclosure route: open (public) or closed (operator-only), one flag per route rather than one flag for all of them.
DiskHealth
What GET /health reports about the data volume.
DiskWatermarks
The free space, as a percentage of the data volume, below which GET /health’s disk.alert and the journal raise an alert. The [capacity] disk_*_free_percent keys set them.
ErrorBody
An atproto XRPC error body: a machine-readable name and a human sentence.
Exclusions
Which message types one connection wants.
Fragment
Text for one slot of a pool’s naming scheme.
Frame
One numbered frame, already encoded.
GetRecordQuery
Query of com.atproto.repo.getRecord.
GetRecordResponse
Response of com.atproto.repo.getRecord.
HealthSnapshot
What GET /health reports beyond liveness: whether the tick that logs this deployment’s own pulse is still advancing, and how recently.
HealthState
The tick’s pulse, shared with whoever answers GET /health.
HealthTick
A running health tick, stopped by HealthTick::stop or by dropping it.
LifecycleLine
What this deployment is doing right now, for a reader with no credential.
ListRecordsQuery
Query of com.atproto.repo.listRecords.
MakeServiceWithConnectInfo 🔒
Turns an already-Clone service — limited_make_service’s [ConcurrencyLimit]-wrapped router — into a [tower::Service] axum::serve and axum_server can both drive per accepted connection, the same way [tower::make::Shared] does, but also carrying a [ConnectInfo] extension for the connection’s peer address, which crate::rate_limit::caller_key reads by default.
MimePattern
A MIME pattern: type/subtype, with either half (or both) as *.
ParkKeyRequest
Body of bot.did.parkKey.
ParkedKeyQuery
Query of bot.did.describeParkedKey.
PolicyBindingView
One bot.did.policyBinding record in force, and whom it reaches.
PolicyRecordView
One bot.did.policy record in force.
PolicyRefusalView
One record a build would not honour, and why.
PolicyView
The policy member of StatsResponse.
ProxyLimits
The per-account bounds on proxied calls, and the cache of targets’ endpoints, for one deployment.
ProxySettings
The bounds on proxied calls, and where an app.bsky.* call with no atproto-proxy goes; see ProxyLimits.
PutRecordRequest
Body of com.atproto.repo.putRecord.
RecordView
One entry in the listRecords response.
ReplayBounds
How much the replay buffer holds. It evicts its oldest frame when a new one would take it past either bound, unless an open stream has not sent that frame yet. Those it keeps up to twice the bounds. See the module docs.
Repos
The producer: the sequence and the replay buffer consumers read from.
ReposStream
One consumer’s stream: the replay, then whatever arrives live, both read from the replay buffer.
ScopeRefused
The exact scope a ceiling refused, for insufficient_scope.
ScopeSet
A space-separated set of Scopes, as carried on a scope parameter or a token response.
StatsResponse
Response of bot.did.stats.
StopHandlers 🔒
The handlers for the signals that stop this process, registered when this is constructed rather than when it is awaited.
Stopping
One observation of the stop signal for a whole run, installed once at its start and awaited by everything that stops on it.
StrictTransportSecurity
A Strict-Transport-Security header; see [tls] hsts_max_age_secs and [tls] hsts_include_subdomains in didbot-config.
Swap
The compare-and-swap fields, as they arrive on the wire.
TallyView
How many blobs, and what they occupy.
XrpcBody
A buffered request body, refused in the atproto shape.
ZoneCertificateHealth
One zone’s entry in CertificateHealth::zones.

Enums§

AccountProof
What the account’s own DID document offers a stranger as proof of being it, beside the #atproto key this server signs its commits with.
Action
One action a repo: scope’s action= query may name. account: has its own, in crate::AccountAction.
ActionSet
The set of actions a repo: scope grants.
Credential
A credential kind a route may declare it accepts.
DiskAlert
Which free-space watermark the data volume is below.
EdgeState
Whether the edge from an account to its operator still stands.
FrameKind
Which of the lexicon’s message types a frame is.
NsidPattern
An NSID pattern: exact, a recursive prefix wildcard, or the bare *.
RepoWrite
One operation in a com.atproto.repo.applyWrites batch.
Scope
One parsed scope atom.
ScopeParseError
A failure parsing one scope atom or a whole scope string.
Transition
The three legacy transition:* scopes.
WriteResult
One entry in the applyWrites results, one per write, in order.

Constants§

APP_BSKY_METHODS
The app.bsky.* methods this server serves itself rather than proxies, as the reference PDS does: the account’s preferences.
ATPROTO_METHODS
Every com.atproto.* method this router answers, in NSID order.
BOT_DID_METHODS
The bot.did.* methods this server serves — its own account admin surface, alongside the com.atproto.* one ATPROTO_METHODS names.
DEFAULT_HEALTH_INTERVAL
How often the health line is written when a caller does not choose.
DEFAULT_LOG_FILTER
Filter used when RUST_LOG is unset.
DEFAULT_REPLAY_BYTES
How many encoded bytes the replay buffer holds, if a deployment does not choose.
DEFAULT_REPLAY_FRAMES
How many frames the replay buffer holds, if a deployment does not choose.
FUTURE_CURSOR
The lexicon’s error for a cursor ahead of the stream.
HEADER_READ_TIMEOUT
How long a connection has to finish sending its request headers.
MAX_CONCURRENT_REQUESTS
How many requests this server answers at once before a new one waits for a slot to free up.
MAX_LIST_LIMIT
The most records listRecords will return in one answer.
MAX_REQUEST_BODY
The largest request body this server will hold in memory.
NEVER_WROTE_MIN_IDLE
The shortest idleFor a neverWrote delete takes, unless [operator] never_wrote_min_idle_secs sets another. An agent’s account is created before its session makes its first write, and an hour is well past that.
OUTDATED_CURSOR
The one name this server ever sends on an #info frame.
ROUTE_CREDENTIALS
Every com.atproto.*, app.bsky.* and bot.did.* method this router serves, and the credential it declares. See the module doc for what each kind means.
SHUTDOWN_GRACE
How long a shutdown waits for connections that are still open before it stops anyway, unless the caller names its own bound; didbot-pds reads [server] drain_secs.
STREAM_CLOSE_GRACE
How long a shutdown waits, once serving has ended, for each subscribeRepos connection to send what it holds and close.
TURN_HEADER
The header a client may name the turn a request belongs to in.
TURN_HEADER_MAX_LENGTH
The longest turn identifier this server will put in a log line.

Traits§

CertificateAlerts
Reads back each zone’s certificate alert level.

Functions§

app
Builds the router.
app_with_auth
Builds the router with every collaborator supplied explicitly.
app_with_health
Builds the router with the health pulse wired to an existing producer.
app_with_repos
Builds the router with com.atproto.sync.subscribeRepos attached too.
cross_origin_layer
The cross-origin policy on the public protocol surface.
encode_error
Encodes one error frame, which closes the connection after it.
encode_message
Encodes one message frame: the header, then the body.
grace_expired 🔒
Resolves SHUTDOWN_GRACE after started fires, and never if it does not.
init_tracing
Installs the tracing subscriber.
installed_shutdown_signal
Resolves when this process is asked to stop: SIGTERM, or Ctrl-C. The handlers are registered before this returns.
limited_make_service 🔒
What both listeners hand each accepted connection: router behind MAX_CONCURRENT_REQUESTS, carrying the peer’s address as [ConnectInfo].
make_service_limited_to 🔒
limited_make_service at a limit a test can overrun with two requests.
record_uri
The at:// URI naming one record.
refuse_skipped_validation
The validate flag, which is atproto’s own name for the three stances.
repo_surface
The same mapping, as the com.atproto.repo.* surface reports it.
serve_router
Serves an already-built router on listener until SIGINT or SIGTERM, waiting at most SHUTDOWN_GRACE on connections that will not close by themselves.
serve_tls
Serves an already-built router on listener, terminating TLS with config, until SIGINT or SIGTERM, on the same SHUTDOWN_GRACE serve_router gives the plain-HTTP path.
serve_tls_inner 🔒
serve_tls_until
serve_tls, stopping when stopped resolves rather than on a signal observed from here on, and waiting drain rather than SHUTDOWN_GRACE for requests in flight. A process whose startup runs after its handlers are installed passes Stopping::wait, so a stop sent during that startup is not lost.
serve_tls_with_header_timeout
serve_tls, with the header-read timeout named rather than taken from HEADER_READ_TIMEOUT.

Type Aliases§

LimitedMakeService 🔒
The service both listeners are driven by; see limited_make_service.