The AT Protocol AppView behind colibri.social.
The spaces alpha is unstable and explicitly not for production. Also, if you're reading this, you're here early. Howdy.
The architecture, the sync algorithm, the storage model, the moderation model and the full XRPC reference are documented at https://colibri.social/docs. This README covers running and working on the code.
Requires Node 24 (.node-version) and pnpm.
pnpm install
cp .env.example .env # then fill in the required values
pnpm devThe AppView requires a few environment variables to boot: APPVIEW_DID, PUBLIC_URL, SIGNING_KEY,
CREDENTIAL_ENCRYPTION_KEY, PDS_URL and COMMUNITY_HANDLE_DOMAIN. .env.example
carries the openssl lines that generate the two secrets.
| Package | What it does |
|---|---|
packages/lexicons |
Every Colibri schema, the space type declarations, and the types generated from them |
packages/space |
Client for com.atproto.space and com.atproto.simplespace: delegation tokens, DPoP-bound credentials, verified repo reads |
packages/space-sync |
Keeps a local copy of a space current: writer set, operation log, LtHash reconciliation, CAR recovery |
packages/db |
Drizzle schema and migrations for both libSQL and Postgres |
packages/projections |
Turns synced records into the typed read models the API serves |
packages/identity |
Service auth, DID documents, handle resolution |
packages/community |
Provisioning, credential custody, roles and permissions, access checks, moderation |
packages/notifications |
Notification indexing, Web Push and FCM |
packages/blobs |
CID-verified blob proxy for permissioned blobs, with image variants |
packages/embeds |
SSRF-guarded link previews and the GIF picker |
packages/emoji |
Custom emoji providers that parse and build each supported emoji lexicon's records |
packages/voice |
mediasoup voice SFU |
apps/appview |
The server |
apps/migrate |
One-shot migration of repo-backed communities onto spaces |
cp .env.example .env # required, see Getting started
docker compose up --buildThat runs the AppView alone on libSQL against the appview-data volume, pointed
at whatever PDS_URL names. Three overlays compose on top of it:
| Overlay | What it adds |
|---|---|
docker-compose.postgres.yml |
Postgres, and rewrites DATABASE_URL to use it |
docker-compose.pds.yml |
a local spaces-alpha PDS and a private PLC directory, for developing without a PDS of your own |
docker-compose.integration.yml |
the same PDS and PLC, ephemeral, for pnpm test:integration |
docker compose -f docker-compose.yml -f docker-compose.pds.yml uppnpm dev # run from source, watching, no build step
pnpm build # generate lexicon types and the pg schema, then compile
pnpm test # unit tests
pnpm test:integration # against a real spaces-alpha PDS, not run in normal CI
pnpm typecheck # sources and tests
pnpm lint # BiomeEvery Colibri schema lives under social.colibri.beta.* while the spaces work is
unstable, so it cannot collide with the social.colibri.* schemas already
published from the pre-spaces AppView. The vendored com.atproto.space and
com.atproto.simplespace documents are upstream and are never republished from
here.
pnpm lexicons:codegen # regenerate the TypeScript types from the JSON
pnpm lexicons:check-dns # what DNS is missing before anything can be published
pnpm lexicons:publish # publish to com.atproto.lexicon.schema recordsAn NSID resolves through a _lexicon TXT record on its domain authority, which
is every segment but the last, reversed. So social.colibri.beta.channel.text
resolves through _lexicon.channel.beta.colibri.social. One record per
authority, thirteen in total. pnpm lexicons:check-dns prints the exact set,
and takes an optional DID to check that each record points where you expect:
pnpm lexicons:check-dns did:plc:mprdjqjluoswa7awzggaggj3export LEXICON_PDS=https://colibri.social
export LEXICON_IDENTIFIER=colibri.social
export LEXICON_PASSWORD=<app password>
pnpm lexicons:publish --dry-run
pnpm lexicons:publishPromoting out of beta, or moving again:
pnpm lexicons:renamespace social.colibri.beta social.colibri --dry-run
pnpm lexicons:renamespace social.colibri.beta social.colibri
pnpm lexicons:codegenA community record names its managingApp. That AppView holds the community's
credentials and answers checkUserAccess for every one of its spaces, so it is
the only one that can serve the community. A client that finds a community whose
managingApp is not the AppView it is talking to must talk to that AppView
instead, for reads, writes, events and voice alike. The field lives on the record
rather than being read from the space policies because
com.atproto.simplespace.getSpace only serves them to a caller already
authorized for that space, and a prospective member is not. The profile space is
publicly readable, so the record can be read before joining.
social.colibri.beta.community.create provisions an account on this AppView's
PDS and needs PDS_ADMIN_PASSWORD.
social.colibri.beta.community.authorizeHost hosts a community on an account
that already exists. The owner signs in as that account on its own server, and
the AppView adopts it as a new community, keeping its DID, handle and PDS. The
same sign-in reconnects a community after its access lapsed or its account moved
to another server. The PDS has to implement com.atproto.simplespace, and the
AppView needs HOST_OAUTH_PRIVATE_KEY, an ES256 key it signs its client
assertions with. The client metadata lives at /oauth/host/client-metadata.json.
OAuth sessions and passwords are stored AES-256-GCM encrypted under
CREDENTIAL_ENCRYPTION_KEY.
social.colibri.beta.community.adopt does the same with the account's password.
It has to be the full password, because com.atproto.space.getDelegationToken
refuses app passwords.
social.colibri.beta.community.handOverHost lets an administrator move an account
this AppView hosts to another server, for example with PDS MOOver. It sets their
email on the account and returns a new password, which works for 60 minutes. If
the account still lives here after that, the AppView sets a new password nobody
sees.
Three packages are published from here: @colibri-social/lexicons,
@colibri-social/space and @colibri-social/space-sync.
A change to any of them needs a changeset:
pnpm changesetA change to packages/lexicons is a protocol change. Treat it as a minor bump
at least, because the client and the AppView both validate against it.