Alternative ATProto PDS implementation
Rust 89.8%
Nix 3.0%
Bicep 2.6%
Dockerfile 0.2%
Other 4.4%
203 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

Bluesky PDS#

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

This is an implementation of an ATProto PDS, built with Axum and Atrium. Heavily inspired by David Buchanan's millipds. This implementation forked from the azure-rust-app starter template.

This PDS implementation uses a SQLite database to store private account information and file storage to store canonical user data.

If you want to see this PDS in action, there is a live account hosted by this PDS at @test.justinm.one!

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 Azure)#

This is how much it costs to host the @test.justinm.one account:

  • $20/mo
    • $13/mo: Azure Application Service
    • $5/mo: Azure Container Registry
    • $1/mo: Azure Storage Account

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#

Teqed's#

  • 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
  • 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#

  • Authentication
  • 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
          };
        })
      ];
    };
  };
}