PoV
June 17, 2025, 2:24am
51
Hello! Just thought I’d share some early work I’ve done on adding FEP-ef61 support to my fork of Bluesky’s PDS server with ActivityPub support. More information on that project can be found here:
main ← mikekasprzak:activitypub-mainline
opened 03:58AM - 09 Jun 25 UTC
No joke, this PR adds preliminary ActivityPub (i.e. Mastodon) support to the PDS… . :exploding_head:
**IMPORTANT**: You shouldn't expect full interoperability with this alone! At most, you should expect that your public posts get shared with your followers on Mastodon (i.e. an outbox). While it should be capable of receiving likes and replies, there's no current way to expose those to the Bluesky apps.
---
## :warning: IMPORTANT: THIS IS A PROOF OF CONCEPT/WORK IN PROGRESS :warning:
Likes are appreciated, but please **ONLY POST** if you have something to say regarding this PR.
:warning: No feature requests! No asking when it'll be ready! Be kind to my alerts, thanks! :warning:
---
You might have seen [this skeet](https://bsky.app/profile/distraction.engineer/post/3lql7jiqaac2a):

At the time of this writing, you should be able to search-for and find `@chaos.distraction.engineer` on Bluesky and `@chaos@distraction.engineer` on Mastodon/ActivityPub. This is my modified PDS server `manhattan.distraction.engineer` that's able to communicate using both the AtProto and ActivityPub protocols... err, well the ActivityPub part is a work in progress.
:warning: **YOU CAN'T CURRENTLY FOLLOW THE MASTODON USER, ONLY THE BLUESKY USER** :warning:
This draft PR is a place for me to share my PDS changes thus far. This isn't my primary branch (that would be [this one over here](https://github.com/mikekasprzak/atproto/tree/activitypub)), but in that one I've had to break a number of things to make my own [docker images](https://github.com/mikekasprzak/pds/pkgs/container/pds) available for testing.
**I DON'T RECOMMEND USING MY DOCKER IMAGES**
## How to build
:warning: Advanced users only! Not recommended for production! :warning:
* Install these codegen patches. These changes were necessary to author the Lexicon files correctly.
* #3932
* #3938
* alternatively my [testing branch](https://github.com/mikekasprzak/atproto/tree/activitypub-testing) has these patches applied
* run `pnpm install` to install the npm packages
* run `pnpm precodegen` to build the codegen tool
* run `pnpm install` to update the npm packages and point at our built version of the codegen tool
* run `pnpm codegen` to generate code
* run `pnpm build` or `pnpm dev`
### Testing
Add an entry for `test` to your `/etc/hosts` file. It should look something like this:
```bash
127.0.0.1 localhost
::1 localhost
127.0.1.1 pop-os.localdomain mypcname
127.0.0.1 alice.test bob.test carla.test triage.test mod.test admin-mod.test
127.0.0.1 test
```
To confirm it's working, ping it.
```bash
ping test
# PING test (127.0.0.1) 56(84) bytes of data.
# 64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.096 ms
# 64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.090 ms
# 64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.083 ms
# ...
```
Start the development server.
```bash
pnpm dev
```
Wait a while, as it needs to start and restart `dist/bin.js` several times before its happy.
Then, to confirm that the local server is running, in a separate tab perform a webfinger request on one of the test users. As far as the development environment is concerned, the dummy user `alice.test` is considered to have the ActivityPub handle `alice@test`.
```bash
curl http://test:2583/.well-known/webfinger?resource=acct:alice@test
# Or the pretty version
curl -s http://test:2583/.well-known/webfinger?resource=acct:alice@test|jq
```
If it's working, you'll get a response like so:
```javascript
{
"subject": "acct:alice@test",
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "http://test:2583/xrpc/org.w3.activitypub.getActor?repo=did:plc:cltjd4ihxplfp76w3szxc5l2"
}
]
}
```
:warning: Not all endpoints work! See below for a list of what's working or not! :warning:
### Deploying
:warning: NOT RECOMMENDED! ALSO NOTE: MY DOCKER IMAGES ARE NOT UP TO DATE! :warning:
Deploy this as you would any PDS server (i.e. adding `_atproto` TXT records to your domain). Assuming you're not doing anything out of the ordinary (i.e. using names under a different subdomain), it should just work. :crossed_fingers:
:warning: Right now ActivityPub support is always enabled, but it may not be if this is merged into mainline. :warning:
If you want to change the root domain used by your users, you can set `PDS_HOSTNAME_ALT` in your `.env` file.
Doing this will also require setting an HTTP 301 redirect for `newdomain.com/.well-known/webfinger` (or the whole `/.well-known/` folder).
As an example, my PDS server is on <https://manhattan.distraction.engineer>. Typical users are created as subdomains, so `chaos.manhattan.distraction.engineer`. I set my alt domain to <https://distraction.engineer>, which is a regular static website hosted on Cloudflare. I was able to make my `/.well-known/` folder redirect by creating a file `_redirects` containing the following script.
```bash
/.well-known/* https://manhattan.distraction.engineer/.well-known/:splat 301
```
I then changed my PDS username from `chaos.manhattan.distraction.engineer` to `chaos.distraction.engineer`, which appears as `chaos@distraction.engineer` on Mastodon. :partying_face:
## Background
This wacky ride started [at the end of may](https://bsky.app/profile/distraction.engineer/post/3lqdcxzzbw22j). About 3 days later I was able to make the skeet [snapshotted above](https://bsky.app/profile/distraction.engineer/post/3lql7jiqaac2a).
While I originally planned to "staple-on" Mastodon-like endpoints to the PDS server, as I grew more familiar with the PDS internals, I realized I could make it work the same as the rest of the server: using `xrpc`. That's what I've been working on since, and what leads me to this PR.
## TODO
* [x] Rewrite code to use Lexicons and run over `xrpc`
* [x] Build core Lexicons for the ActivityStreams, ActivityPub, and JoinMastodon namespaces
* [x] Webfinger (`/.well-known/webfinger?resource=`)
* [x] Add `/xrpc/org.w3.activitypub.getActor` endpoint
* [x] Return an `Activity#Create` document with an `Actor#Person` inside
* [x] Use `icon` and `image` fields for avatar and profile image respectfully
* [x] Add `/xrpc/org.w3.activitypub.getOutbox` endpoint
* [x] GET version (public)
* [x] When no page is specified, return an `OrderedCollection`
* [ ] Use correct number of items
* [ ] When page is specified, return an `OrderedCollectionPage`
* [ ] Add Paging
* [ ] POST version (private)
* [ ] Add `/xrpc/org.w3.activitypub.getResource` endpoint
* [ ] Allow fetching posts (Notes) by id
* [ ] Allow fetching activities by id
* [ ] TBD: Should this endpoint handle fetching collections too? i.e. likes, followers, etc.
* [ ] If so, add Paging
* [ ] Add `/xrpc/org.w3.activitypub.putInbox` endpoint
* [ ] Accept follower requests.
* [ ] Store followers ... somewhere
* [ ] Add support for the `sharedInbox` endpoint (for delivering to all members of a server)
* [ ] Send (forward) messages to followers
* ~~[ ] Consider switching endpoints over to the `org.joinmastodon` namespace as TECHNICALLY I want to support Mastodon style ActivityPub, as that's the most popular. Mastodon isn't necessarily the most "spec compliant", and including "Mastodon hacks" in `org.w3.activitypub` doesn't feel right.~~
* [ ] Public and Private keys (RSA)
* [ ] Generate keypairs
* [ ] Import keypairs
* [ ] Store and fetch your keys in such a way that they persist in your backups
* [ ] Expose public key via getActor
* [ ] Expose public key via webfinger?
* [ ] Explore exposing the public key via https://plc.directory
* [ ] Sign messages sent
* [ ] [FEP EF61 "Portable Object" support](https://codeberg.org/fediverse/fep/src/branch/main/fep/ef61/fep-ef61.md)
* [ ] AtProto DID to ActivityPub FEP DID conversion
* [ ] `ap://` URLs
* [ ] `/.well-known/apgateway` support
* [x] Consider adding **Mastodon Client** endpoints for accessing your ActivityPub inbox, EFFECTIVELY turning a PDS into a ... well, a **Masttodon Client** :exploding_head:
* [ ] TODO: task breakdown
* [ ] ...
### Known Bugs
* [ ] `/xrpc/org.w3.activitypub.getOutbox` unnecessarily includes at AtProto `$type` field
* [ ] ...
## Wrapup
I'll be updating this PR as I go, and expanding on things. It's a draft PR, so please cut me some slack. :wink:
Anyway, as for FEP-ef61 support, it’s not in my draft PR yet, but below you can find a few sample screenshots of responses. Notably, my in implementation I convert AtProto DID’s into ActivityPub DID’s by storing the AtProto DID’s in did:key’s with the MultiDID encoding (as a base58 string).
The first is my updated webfinger, and a placeholder apgateway showcasing DID conversion.
Second, an A/B comparison of what my standard actor profile response looks like, and what it looks like in “FEP ef61” format. It’s currently only available behind a query parameter, at least until I finish the gateway.
EDIT: Click the GitHub link above. I’m not allowed to post 2 images or more than 2 links as a new user.
3 Likes