A CLI tool for Statically Defined PDSes — ship an atproto account & your lexicons with your wisp.place static site.

README.md

Atproto Statically Defined PDSes #

A CLI tool for static site deployments to let them function as a PDS.

Super useful for projects where you need to publish lexicons for your atproto app, but you don't want to set up & manage a connection to a PDS.

This CLI handles:

  • Creating a did:web for you
  • Creating the did.json and atproto-did files needed for an atproto account
  • Static generating the content of your atproto repo (eg. from lexicons/com/example/**.json)
  • Creating the (wisp.place-compatible) _redirects file needed to serve your repo
  • Writing the event log wisp.place serves as your repo's firehose, and asking relays to crawl it from your CI pipeline

Install #

curl -fsSL <binary URL from the release> | gunzip > at-sdp && chmod +x at-sdp

Each release on the tags page lists its binaries, named at-sdp_<os>_<arch>[.exe].gz, where <os>_<arch> is one of linux_amd64, linux_arm64, darwin_amd64, darwin_arm64, windows_amd64.exe, windows_arm64.exe.

Usage #

Once, locally. Commit at-sdp.json; keep the printed private key in a CI secret (AT_SDP_PRIVATE_KEY).

at-sdp init --domain example.com

If you lose the private key, delete at-sdp.json and run init again with the same domain. Your DID stays the same, because did:web follows the domain, and the next deploy moves it to the new key. That makes extra work for the atproto network, since every relay and app that follows you has to look up your identity again, so it's best not to lose the key.

In CI, after the site build:

at-sdp build --out site lexicons
./wisp-cli deploy example.com --path site --site example --password "$WISP_APP_PASSWORD" --yes
at-sdp check --site site --firehose https://example.com
at-sdp notify

Relays reach the repo only when the site is on a custom domain whose owner is a wisp.place supporter.

Records can point at each other by path. A $strongRef becomes the uri and cid of the record it names, keeping its other keys, and the cid follows the target's content on every build:

{
  "$type": "app.example.post",
  "text": "hello",
  "author": { "$strongRef": "author.json" }
}

{"$atUri": "author.json"} becomes just the at-uri string. Paths are relative to the file holding them.

{"$file": path} publishes a local file as a blob, for things like an avatar:

{
  "$type": "app.bsky.actor.profile",
  "displayName": "Blinkers",
  "avatar": { "$file": "avatar.png" }
}

The file becomes a blob ref whose mimeType comes from its extension, through the same table wisp-cli uses. Files are published byte for byte, so EXIF metadata, GPS included, goes out with them. $file publishes whatever path it names, from anywhere the build can read, so only point it at files meant to be public.

build validates every record against its lexicon, and warns instead of failing when a lexicon can't be fetched.

check exits 0 when the live site serves the build, 1 otherwise. check retries 429, 502, 503, 504 and network errors until --timeout, so it can run straight after a deploy.