Alternative ATProto PDS implementation
Rust 94.0%
Nix 1.7%
Bicep 1.5%
Dockerfile 0.1%
Other 2.6%
269 1 0

Clone this repository

https://tangled.org/quilling.dev/bluepds
git@tangled.org:quilling.dev/bluepds

For self-hosted knots, clone URLs may differ based on your setup.

README.md

ATProto PDS#

         __                         __
        /\ \__                     /\ \__
    __  \ \ ,_\  _____   _ __   ___\ \ ,_\   ___
  /'__'\ \ \ \/ /\ '__'\/\''__\/ __'\ \ \/  / __'\
 /\ \L\.\_\ \ \_\ \ \L\ \ \ \//\ \L\ \ \ \_/\ \L\ \
 \ \__/.\_\\ \__\\ \ ,__/\ \_\\ \____/\ \__\ \____/
  \/__/\/_/ \/__/ \ \ \/  \/_/ \/___/  \/__/\/___/
                   \ \_\
                    \/_/

This is an implementation of an ATProto PDS, built with Axum and Atrium. This PDS implementation uses a SQLite database to store private account information and file storage to store canonical user data.

Heavily inspired by David Buchanan's millipds. This implementation forked from the azure-rust-app starter template and the upstream DrChat/bluepds. See TODO below for this fork's changes from upstream.

If you want to see this fork in action, there is a live account hosted by this PDS at @teq.shatteredsky.net!

WARNING

This PDS is undergoing heavy development. Do NOT use this to host your primary account or any important data!

Quick Start#

cargo run

Cost breakdown (on Oracle Cloud Infrastructure)#

This is how much it costs to host the @teq.shatteredsky.net account:

  • $0/mo Always Free Resources
    • $0/mo: VM.Standard.A1.Flex
      • OCPU count: 2
      • Network bandwidth: 2 Gbps
      • Memory: 12 GB
    • $0/mo: Virtual Cloud Network
      • IPv4 address
      • IPv6 address
    • $0/mo: Boot volume
      • Size: 47 GB
      • VPUs/GB: 10

This is about half of the 3,000 OCPU hours and 18,000 GB hours available per month for free on the VM.Standard.A1.Flex shape. This is without optimizing for costs. The PDS can likely be made much cheaper.

Code map#

* migrations/   - SQLite database migrations
* src/
  * endpoints/  - ATProto API endpoints
  * auth.rs     - Authentication primitives
  * config.rs   - Application configuration
  * did.rs      - Decentralized Identifier helpers
  * error.rs    - Axum error helpers
  * firehose.rs - ATProto firehose producer
  * main.rs     - Main entrypoint
  * metrics.rs  - Definitions for telemetry instruments
  * oauth.rs    - OAuth routes
  * plc.rs      - Functionality to access the Public Ledger of Credentials
  * storage.rs  - Helpers to access user repository storage

To-do#

Teq's fork#

  • OAuth
    • /.well-known/oauth-protected-resource - Authorization Server Metadata
    • /.well-known/oauth-authorization-server
    • /par - Pushed Authorization Request
    • /client-metadata.json - Client metadata discovery
    • /oauth/authorize
    • /oauth/authorize/sign-in
    • /oauth/token
    • Authorization flow - Backend client
    • Authorization flow - Serverless browser app
    • DPoP-Nonce
    • Verify JWT signature with JWK
  • Email verification
  • 2FA
  • Admin endpoints
  • App passwords
  • listRecords fixes
    • Fix collection prefixing (terminate with /)
    • Fix cursor handling (return cid instead of key)
  • Session management (JWT)
    • Match token fields to reference implementation
    • RefreshSession from Bluesky Client
      • Respond with JSON error message ExpiredToken
  • Cursor handling
    • Implement time-based unix microsecond sequences
    • Startup with present cursor
  • Respond RecordNotFound, required for:
    • app.bsky.feed.postgate
    • app.bsky.feed.threadgate
    • app.bsky... (profile creation?)
  • Linting
    • Rustfmt
      • warnings
      • deprecated-safe
      • future-incompatible
      • keyword-idents
      • let-underscore
      • nonstandard-style
      • refining-impl-trait
      • rust-2018-idioms
      • rust-2018/2021/2024-compatibility
      • ungrouped
    • Clippy
      • nursery
      • correctness
      • suspicious
      • complexity
      • perf
      • style
      • pedantic
      • cargo
      • ungrouped

High-level features#

  • Storage backend abstractions
    • Azure blob storage backend
    • Backblaze b2(?)
  • Telemetry
    • Metrics (counters/gauges/etc)
    • Exporters for common backends (Prometheus/etc)

APIs#

  • com.atproto.admin
    • xx /xrpc/com.atproto.admin.deleteAccount
    • xx /xrpc/com.atproto.admin.disableAccountInvites
    • xx /xrpc/com.atproto.admin.disableInviteCodes
    • xx /xrpc/com.atproto.admin.enableAccountInvites
    • xx /xrpc/com.atproto.admin.getAccountInfo
    • xx /xrpc/com.atproto.admin.getAccountInfos
    • xx /xrpc/com.atproto.admin.getInviteCodes
    • xx /xrpc/com.atproto.admin.getSubjectStatus
    • xx /xrpc/com.atproto.admin.sendEmail
    • xx /xrpc/com.atproto.admin.updateAccountEmail
    • xx /xrpc/com.atproto.admin.updateAccountHandle
    • xx /xrpc/com.atproto.admin.updateAccountPassword
    • xx /xrpc/com.atproto.admin.updateSubjectStatus
  • com.atproto.identity
    • xx /xrpc/com.atproto.identity.getRecommendedDidCredentials
    • AP /xrpc/com.atproto.identity.requestPlcOperationSignature
    • UG /xrpc/com.atproto.identity.resolveHandle
    • AP /xrpc/com.atproto.identity.signPlcOperation
    • xx /xrpc/com.atproto.identity.submitPlcOperation
    • AP /xrpc/com.atproto.identity.updateHandle
  • com.atproto.repo
    • AP /xrpc/com.atproto.repo.applyWrites
    • AP /xrpc/com.atproto.repo.createRecord
    • AP /xrpc/com.atproto.repo.deleteRecord
    • UG /xrpc/com.atproto.repo.describeRepo
    • UG /xrpc/com.atproto.repo.getRecord
    • xx /xrpc/com.atproto.repo.importRepo
    • xx /xrpc/com.atproto.repo.listMissingBlobs
    • UG /xrpc/com.atproto.repo.listRecords
    • AP /xrpc/com.atproto.repo.putRecord
    • AP /xrpc/com.atproto.repo.uploadBlob
  • com.atproto.server
    • xx /xrpc/com.atproto.server.activateAccount
    • xx /xrpc/com.atproto.server.checkAccountStatus
    • xx /xrpc/com.atproto.server.confirmEmail
    • UP /xrpc/com.atproto.server.createAccount
    • xx /xrpc/com.atproto.server.createAppPassword
    • AP /xrpc/com.atproto.server.createInviteCode
    • xx /xrpc/com.atproto.server.createInviteCodes
    • UP /xrpc/com.atproto.server.createSession
    • xx /xrpc/com.atproto.server.deactivateAccount
    • xx /xrpc/com.atproto.server.deleteAccount
    • xx /xrpc/com.atproto.server.deleteSession
    • UG /xrpc/com.atproto.server.describeServer
    • xx /xrpc/com.atproto.server.getAccountInviteCodes
    • AG /xrpc/com.atproto.server.getServiceAuth
    • AG /xrpc/com.atproto.server.getSession
    • xx /xrpc/com.atproto.server.listAppPasswords
    • xx /xrpc/com.atproto.server.refreshSession
    • xx /xrpc/com.atproto.server.requestAccountDelete
    • xx /xrpc/com.atproto.server.requestEmailConfirmation
    • xx /xrpc/com.atproto.server.requestEmailUpdate
    • xx /xrpc/com.atproto.server.requestPasswordReset
    • xx /xrpc/com.atproto.server.reserveSigningKey
    • xx /xrpc/com.atproto.server.resetPassword
    • xx /xrpc/com.atproto.server.revokeAppPassword
    • xx /xrpc/com.atproto.server.updateEmail
  • com.atproto.sync
    • UG /xrpc/com.atproto.sync.getBlob
    • UG /xrpc/com.atproto.sync.getBlocks
    • UG /xrpc/com.atproto.sync.getLatestCommit
    • UG /xrpc/com.atproto.sync.getRecord
    • UG /xrpc/com.atproto.sync.getRepo
    • UG /xrpc/com.atproto.sync.getRepoStatus
    • UG /xrpc/com.atproto.sync.listBlobs
    • UG /xrpc/com.atproto.sync.listRepos
    • UG /xrpc/com.atproto.sync.subscribeRepos

Quick Deployment (Azure CLI)#

az group create --name "webapp" --location southcentralus
az deployment group create --resource-group "webapp" --template-file .\deployment.bicep --parameters webAppName=testapp

az acr login --name <insert name of ACR resource here>
docker build -t <ACR>.azurecr.io/testapp:latest .
docker push <ACR>.azurecr.io/testapp:latest

Quick Deployment (NixOS)#

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    bluepds = {
      url = "github:Teqed/bluesky-pds";
    };
  };
  outputs = {
    nixpkgs,
    bluepds,
    ...
  }: {
    nixosConfigurations.mysystem = nixpkgs.lib.nixosSystem {
      modules = [
        ({ pkgs, ... }: {
          config.services.bluepds = {
            enable = true;
            host_name = "pds.example.com";
            listen_address = "0.0.0.0:8000";
            test = "true"; # Set to false for production
          };
        })
      ];
    };
  };
}