Bluesky Personal Data Server (PDS)

repository·main·Indexed 25 days ago

https://github.com/bluesky-social/pds

Service entrypoint for the AT Protocol personal data server. This repository provides container images and documentation for hosting a Bluesky PDS, including guides on VPS deployment, DNS configuration, account management via the goat CLI, SMTP setup, and detailed procedures for migrating account repositories and blobs between servers.

Tokens
6.5K
Snippets
13
Records
25
Agent score
82%

What's inside bluesky-social-pds

  1. Finalize account migration

    main

    To complete the migration:

    1. Verify Status: Run com.atproto.server.checkAccountStatus on the new PDS to ensure everything is correct.
    2. Activate Account: Call com.atproto.server.activateAccount on the new PDS. This activates the account and publishes identity update events to the firehose.
    3. Cleanup: Deactivate or delete the account on the old PDS using com.atproto.server.deleteAccount or com.atproto.server.deactivateAccount. If using deactivateAccount, you can provide an optional deleteAfter parameter to delay deletion.
  2. Verify PDS health and WebSocket connectivity

    main

    After installation, verify your PDS is online using the healthcheck endpoint and ensure WebSockets are functional (required for network federation).

    1. Check Health: Visit https://your-domain.net/xrpc/_health in a browser. You should receive a JSON response containing a version number, e.g., {"version":"0.2.2-beta.2"}.

    2. Check WebSockets: Use a tool like wsdump to test the subscription endpoint:

    wsdump "wss://example.com/xrpc/com.atproto.sync.subscribeRepos?cursor=0"

    Note: The command may run without output until new events are created in the PDS.

  3. Migrate repository data and blobs

    main

    Once the account is created on the new PDS, follow these steps to move your data:

    1. Migrate Repository: Call com.atproto.sync.getRepo on the old PDS to get a CAR file. Upload these bytes to the new PDS using com.atproto.repo.importRepo (use application/vnd.ipld.car encoding).
    2. Migrate Blobs:
      • Call com.atproto.sync.listBlobs on the old PDS to discover all CIDs.
      • For each CID, download the content via com.atproto.sync.getBlob from the old PDS.
      • Upload the content to the new PDS via com.atproto.repo.uploadBlob.
    3. Migrate Private State: Call app.bsky.actor.getPreferences on the old PDS and submit the result to app.bsky.actor.putPreferences on the new PDS.

    Verification Tools:

    • Use com.atproto.server.checkAccountStatus on the new PDS to check repo state, indexed records, and expected vs. uploaded blobs.
    • Use com.atproto.repo.listMissingBlobs on the new PDS to identify specific missing blobs.
  4. Install PDS on Ubuntu or Debian

    main

    The installation script is designed for a "fresh" VPS that is not concurrently hosting another web server on ports 80 or 443. The script is interactive and will prompt for your public DNS address, an admin email, and PDS user account details.

    1. Download the installer using curl.
    2. Run the installer with sudo bash.

    Installation Commands:

    curl https://raw.githubusercontent.com/bluesky-social/pds/main/installer.sh > installer.sh
    sudo bash installer.sh
  5. Update DID identity to point to a new PDS

    main

    After data migration, you must update your DID to point to the new credentials (handle, endpoint, signing key, and rotation key).

    For did:plc users

    1. Get Recommendations: Call com.atproto.identity.getRecommendedDidCredentials on the new PDS to get the recommended handle, endpoint, and signing keys. It is recommended to generate a new rotation key and add it to this list.
    2. Request Signature: Call com.atproto.identity.requestPlcOperationSignature on the old PDS. This triggers an email challenge.
    3. Sign Operation: Once you receive the email token, call com.atproto.identity.signPlcOperation on the old PDS, passing the token and the recommended credentials.
    4. Submit Operation: Submit the resulting signed operation to the new PDS using com.atproto.identity.submitPlcOperation. This is safer than submitting directly to plc.directory as the PDS validates the operation.

    For did:web users

    Update the .well-known endpoint for your DID manually.

  6. Deploy a PDS onto a VPS

    main

    To self-host a Bluesky Personal Data Server (PDS), deploy it onto a Virtual Private Server (VPS) with the following requirements:

    Server Requirements:

    • Public IPv4 address
    • Public DNS name
    • Public inbound internet access on ports 80/tcp and 443/tcp

    Recommended Specifications:

    • OS: Ubuntu 24.04
    • RAM: 1 GB
    • CPU: 1 Core
    • Storage: 20 GB SSD
    • Architectures: amd64, arm64

    Security Note: It is recommended to restrict inbound SSH access (22/tcp) to your specific public IP address.

  7. Configure DNS for your PDS domain

    main

    Set up DNS records in your provider's control panel to point to your server's IP address. You must include a wildcard record to allow users to create new accounts on your PDS.

    Required Records:

    NameTypeValue
    example.comAyour-server-ip
    *.example.comAyour-server-ip
    • Replace example.com with your domain.
    • Replace your-server-ip with your server's public IP.
    • A TTL of 600 (10 minutes) is recommended.
  8. Create an account on a new PDS

    main

    To create an account on a new PDS, you must prove control of your DID.

    1. Obtain a JWT: Call com.atproto.server.getServiceAuth from your old PDS. You must provide the aud (audience, the new PDS's DID) and lxm (the permission, e.g., com.atproto.server.createAccount).
    2. Call createAccount: Use the JWT as a Bearer token in the authorization header to call com.atproto.server.createAccount on the new PDS. You may need to provide an inviteCode if required by the new PDS.

    After this step, the account exists on the new PDS but is in a "deactivated" state and has an empty repository.

    // Example of obtaining auth and creating account
    const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({
      aud: newServerDid,
      lxm: 'com.atproto.server.createAccount',
    })
    const serviceJwt = serviceJwtRes.data.token
    
    await newAgent.api.com.atproto.server.createAccount(
      {
        handle: NEW_HANDLE,
        email: NEW_ACCOUNT_EMAIL,
        password: NEW_ACCOUNT_PASSWORD,
        did: accountDid,
        inviteCode: NEW_PDS_INVITE_CODE,
      },
      {
        headers: { authorization: `Bearer ${serviceJwt}` },
        encoding: 'application/json',
      },
    )
  9. Configure SMTP for email verification

    main

    To verify user emails, configure an SMTP server in /pds/pds.env.

    Using an email service (e.g., Resend):

    PDS_EMAIL_SMTP_URL=smtps://resend:<your api key here>@smtp.resend.com:465/
    PDS_EMAIL_FROM_ADDRESS=admin@your.domain

    Using a standard SMTP server:

    PDS_EMAIL_SMTP_URL=smtps://username:password@smtp.example.com/

    Using local sendmail (Postfix/Exim):

    PDS_EMAIL_SMTP_URL=smtp:///?sendmail=true

    Important: After changing these variables, restart the PDS service using sudo systemctl restart pds. If you encounter issues with special characters in passwords, ensure they are percent encoded.

  10. Access the PDS context and account manager

    main

    The pds.ctx object provides access to the service context, which includes:

    • pds.ctx.cfg: The service configuration.
    • pds.ctx.accountManager: An object used to manage and retrieve accounts (e.g., pds.ctx.accountManager.getAccount(handle)).
  11. Configure PDS logging

    main

    By default, logs are sent to stdout and managed by Docker. To persist logs to a file or change verbosity, update /pds/pds.env:

    • Set log destination: LOG_DESTINATION=/pds/pds.log
    • Set log level: LOG_LEVEL=debug (default is info)

    To view Docker logs: sudo docker logs pds.

  12. Initialize and start a PDS instance

    main

    To start a Personal Data Server (PDS), you must read the environment variables, convert them into configuration and secrets objects, and then use PDS.create() to instantiate the service. Once created, call pds.start() to begin operation. The PDS instance also provides an app property (an Express application) which allows you to attach custom routes.

    import { PDS, envToCfg, envToSecrets, readEnv } from "@atproto/pds";
    
    const main = async () => {
      const env = readEnv();
      const cfg = envToCfg(env);
      const secrets = envToSecrets(env);
      
      const pds = await PDS.create(cfg, secrets);
      await pds.start();
      
      // You can attach custom routes to the underlying Express app
      pds.app.get("/custom-route", (req, res) => {
        res.send("Hello from PDS");
      });
    };
    
    main();