GeistHaus
log in ยท sign up

Bailey's Retrospective

Part of leaflet.pub

Originally, this was for a weekly update on what I am working on. Now, a bit of a catchall and retrospective over more specific things like updates or major bug fixes.

stories
Badger Badger Badger
A short write up about what Bailey had around his neck at #AtmosphereConf 2026
Show full content

In a stroke of genius @brookie.blog created my favorite atproto app to use irl, youandme.at. And she solved a problem for me, finding a cool way to show off physical hardware using atproto in a fun, interactive way. For those who don't know what youandme.at is. It's a fun atproto app that when you meet someone, you each scan the QR code. It then makes a "connection" showing the person's handle and pfp from Bluesky, which is a great icebreaker if you know someone online, but may not irl.

Meet the Badger 2350

The Badger 2350 is a little hardware device that is an eink badge meant to be taken to conferences, sit on your desk, or whatever made by the great folks at @pimoroni.com. The brains behind the badger is a Raspberry Pi 2350, or sometimes known by the dev board, the Raspberry Pi Pico. This little $7 computer is called a microcontroller; it does not technically run a full operating system, but just a single program you put on it. And in this case, MicroPython, a subset/version of Python created to run on resource constrained devices and interact with low level hardware APIs. This firmware controls all the hardware. Pimoroni has also abstracted a lot of the hard stuff already and given it a sort of framework that handles the RTC, button presses, writing to the screen, LEDs, Wifi/Bluetooth, and the i2C port. I usually like to write my firmware for these kind of boards in rust, but I have to give it to Pimoroni. They know how to make something easy, fun, and educational. That's hard to beat. To add an app to the Badger 2350 it's as easy as connecting the badger to your computer, clicking a button on the back twice, and it shows up as an external drive, allowing you to edit the code on the device with your favorite editor.

Adding a new app is as easy as adding a new folder, an icon, and a __init__.py. Can see the exact directions here. Once you do that, it should show up on the home page app selector.

What's the atproto part?

So, like I said @brookie.blog really nailed the IRL social aspect of youandme.at. Before I wanted to find some kind of fun "guest book" where folks could log in, write a lexicon to their repo, and it shows up on my badge. It was halfway done, overly complicated, and I had decided to focus on my at://advent workshop instead, which was the better choice' cause Brook knocked it out of the ballpark with youandme.at. Every single person used it at Atmosphere Conf 2026.

So we ended up with this little guest book on my badge that showed who all scanned my youandme.at qr code, as an added bonus, it showed them in order with the newest at the top. Thanks to my previous badger guest book app I had made, it was very easy to adapt it to work with youandme.at and could hack that together pretty quickly.

The up and down buttons let me scroll the guest book by advancing or going back with the cursors to constellation. Then, when I pressed the B button, it showed the QR code, which is a URL with your did like https://youandme.at/connect?did=did:plc:rnpkyqnmsw4ipey6eotbdnnf. All the โœจatproto magicโœจ of writing records happens on the person's phone who scans the qr code like it does on the web app.

The details

The at.youandme.connection lexicon has a nice subject field in it which makes it super easy to get a list of dids that have scanned my badge via constellation. That lexicon looks like this:

{
  "$type": "at.youandme.connection",
  "subject": "did:plc:dfkjiu36xs6ogt7pux7i7o2b",
  "createdAt": "2026-03-25T21:41:10.235Z"
}

And the constellation URL that returns the total count of how many people have scanned my badge and who it was

https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=did:plc:rnpkyqnmsw4ipey6eotbdnnf&source=at.youandme.connection:subject

To break that down:

  • subject - What you are looking for inside the records. In this case, someone's did. But this can be any at-uri, or even uri like.

  • source

    • at.youandme.connection - The lexicon to search for the subject in.

    • subject - The field to check for the subject. You can also check against child fields with something like this app.bsky.feed.like:subject.uri which is used to show who all liked a bluesky post

This returns JSON with how many records have my did as the subject matching the above criteria, along with which records that is.

{
  "total": 66,
  "records": [
    {
      "did": "did:plc:lrphxvv25aibthe7xoc2eeyy",
      "collection": "at.youandme.connection",
      "rkey": "3miak7y64az2k"
    },
   ],
  "cursor": null
}

From here it was just using slingshot (everybody tell @bad-example.com thank you) to grab something called a minidoc which is just a mini version of your did document that has your handle in it, making it an easy single web request to go did -> handle on a little resource constrained device. That URL is https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=did:plc:rnpkyqnmsw4ipey6eotbdnnf

and the mini did looks like this

{
  "did": "did:plc:rnpkyqnmsw4ipey6eotbdnnf",
  "handle": "baileytownsend.dev",
  "pds": "https://selfhosted.social",
  "signing_key": "zQ3shVDFvzBZvw4mhzEQo593bhBGus4TKQcahCaGi9YE7Cyqb"
}

The whole thing ended up being about 215 loc since the Badger has a nice framework for the logic of the display, wifi, and web requests. Can see the whole program for it on this tangled string.

Then to connect to the wifi I just cheated and ran a hotspot off my phone anytime I showed it off. Here's a picture of me cheeseing showing it off to fig.

I also was not the only one there with a cool Raspberry Pi eink badge, @jimray.locket.computer rolled up with a PDS in his shirt pocket ๐Ÿคฏ. I even heard he migrated his account mid workshop.

Yeah that's it

Thanks for reading! I hope y'all enjoyed this quick write up about it, and it was so great to meet everyone last week. Already can't wait for the next Atmosphere conf when we all get to hang out again. I will also keep my ๐Ÿ‘€ out for y'all to outdo me on a badge next year.

https://retrobailey.leaflet.pub/3mikpzhs5y224
PDS MOOver: The mobile app?
Questionnaire to help shape the direction of PDS MOOver's mobile app
Show full content

Hey! I'm @baileytownsend.dev, I am the creator of pdsmoover.com. If you do not know what PDS MOOver is, it's a website that lets you move your account you usually use on Bluesky to other providers like Blacksky, Northsky, or Eurosky. Or you may also know PDS MOOver because of this very cute cow.

I am just now getting back from the Atmosphere Conf today, and if you do not know what that is. It's a gathering of our overall community that works on atprotocol, the underlying technology that runs Bluesky. This was the second meetup of this kind, and there were 350 in attendance across 4 days of us all hanging out and learning from each other. One of the things that I took away from it was that some people want to have an easy and local way to back up your account's identity and data (posts, pictures, etc). Then, if you ever need to restore your account, you can directly from your phone.

If you have already moved to one of the other provider's PDSs from Bluesky's servers, then you may have used my tool or one provided by Blacksky, Northsky, or Eurosky. I want to explore the possibility of creating a PDS MOOver app and I want to hear from you. The benefit of having a tool like this as an app is that it lets you backup your account to your phone and allows you to migrate between services easily if you ever need or want to. No more worrying about whether you have the recovery key saved anywhere. This will work for anyone, no matter where their account is, and keeps you fully independent if you ever need it. Which if you are already on one of those services, you are already in the best hands our community has, and I would happily host my account on any of them. But never hurts to have multiple backups that you fully control.

Why?

Bluesky is not the only website/app that uses this tech; atprotocol. A quick primer on the atprotocol. You can use your same exact account to login to stream.place to watch streamers, or you may like to write blog articles like this one on leaflet.pub, or missing the glory days of foursquare and want to use beaconbits.app or maybe want to have a private convo in DMs with someone with germnetwork.com. It's all the same account; it's all your social identity. It is the same login, and your data is saved in the same place, your PDS.

The promise of atprotocol is that NO SINGLE PERSON OR GROUP can say you are banned completely from the network. Now individual apps or PDSs may ban you from using their portion of the network. For instance, Bluesky could ban you today, and it is harder for you to interact with people again, but that does not mean other applications have banned you. You can also even use Bluesky in a limited capacity with things like reddwarf.app. There is also the risk that something could happen to your PDS provider.

How?

A PDS MOOver app fits into this by putting YOU fully in control of your social identity and data by backing it up to your phone, as well if you are ever banned from a service/PDS, you can pack your bags and moove to another one. It will do this in three ways.

The plan is to make this promise of atprotocol as easy and accessible to everyone as possible. No matter what community you are a part of, PDS MOOver will allow you to have full control in a way that makes sense and is easy for you to use.

The questions

Okay, now onto the questions with an important note. Much like your Bluesky posts, your answers are public and tied to your identity. This will mostly be yes/no questions, but I want to make sure you know this.

If there were a PDS MOOver app, would you use it to backup your account?

If you answered yes, do you have an iPhone or an Android?

Your account backup can be decently large, with mine being close to 1.5 GB. Is this a concern?

It should be noted that most of this size is in pictures and videos. So if you have a lot of those, the larger it is.

With your recovery key, would you feel comfortable if it were password protected and stored on PDS MOOver servers?

The key would be encrypted by a password, meaning we have no way to view it. It would be stored on your phone first and foremost, but it would have the option to backup to our servers by a password in case something ever happens to your cellphone.

Would you prefer to use your password to login or OAuth

For example, when you log in to Bluesky, the app asks you for your password. But if you use stream.place it redirects you to your PDS to log in. Each has its own pros and cons, with a password login meaning it is easier to stay logged in and ensure your settings are backed up (saved feeds, mutes, etc). It should also be noted with a password login this will only ever communicate directly with your PDS, never PDS MOOver servers.

Are you interested in advanced features that tie the recovery key to your device and it only ever stays there?

This is usually called a secure enclave. Meaning unless someone has physical access to your unlocked phone they cannot use this key to recover your account. The downside is every time you get a new phone, you have to change that key out and it cannot be backed up. But is very secure.

If you've made it this far. Thank you! These answers will help me a ton in deciding the best way to make the app to serve you best. Also, if you are interested in sponsoring some of this work. Please let me know! You can drop me a dm on Bluesky @baileytownsend.dev.

https://retrobailey.leaflet.pub/3mifnt6y5nc27
Selfhosted Postmortem, Jan 13th, 2026
A trip through relay hell
Show full content

Cheating a bit by writing this before everything is fully back, but it should be soon, hopefully. First of all, I am sorry about the extended downtime. Not at all what I would have wanted, and I have tried everything in my power to speed it up to get y'all back to posting.

We had a user who backfilled 20k+ records in the last 24 hours and it caused us to get limited by Bluesky's relay(the thing that takes our posts and shows them on bsky.app). This is not the user's fault, but mine. I found out today that PDS rate limits are not on by default (they are now); you have to set the env variable PDS_RATE_LIMITS_ENABLED for API limits. With that set, a user could not generate enough records to cause the relay to rate limit us. The user was backfilling an account with their historical twitter history, and it was nothing malicious and I want to reiterate it's not their fault and I should have had the proper protections in place so that the user knew the rate limits and would have stopped anything before it affected everyone. That is now set, and this should not happen again. I am deeply sorry and hope this is the last of our growing pains as a PDS.

The extend of the outage is waiting on Bluesky's relay to catch up with the current firehose events emitted from the PDS.

A timeline of events

  • 16:10 UTC: a user reports something is off and I notice it as well. Posts are delayed and not always going through

  • 16:20 UTC: Troubleshooting and finding the account and seeing that they are backfilling an atproto account bsky records that match their twitter history (again, not their fault, the PDS should have been giving them rate limit headers and stopping it long ago)

  • 16:40 UTC: I wrote up this GitHub issue asking if someone could look into it since a similar issue had happen on another PDS (also not that users fault either)

  • 17:14 UTC: Reached out to the user who had created the account and asked if they could stop the script while we figure out what is going on. Very nice and apologize, was not malicious at all.

  • 18:05 UTC: Made use of the com.atproto.admin.sendEmail endpoint for the first time which let me email everyone the following message. If you did not get it may check that you have an email on your account so you can be reached if something like this happens again. The script if you ever need it

  • Hey everyone, This is @baileytownsend.dev. 
    Sorry to bother y'all with an email, but I am using the PDS's
    built in email notification system to let y'all know that 
    selfhosted.social is having some issues right now with 
    bluesky. It appears we have hit the relay limits. I have 
    reached out to support via email and made a github issue, I 
    am hoping to hear back soon, and this will be a onetime 
    problem. Once resolved, it will not happen again. 
    I am hoping to hear back soon. Again, I am sorry about the 
    inconvenience and reaching out via email. 
    https://github.com/bluesky-social/pds/issues/306. 
    If you are needing some more details or help can reach out 
    at modmail@selfhosted.social.
  • 18:23 UTC: Bryan was kind enough to reply to the GitHub issue and let me know he had up the rate of firehose events for us which would allow us to get going faster

  • 18:30ish UTC: I had dug some more and found that it does not appear api rate limits were on by default like I expected(they are now). Set PDS_RATE_LIMITS_ENABLED and rebooted the server. Does not really help us now since it's the event's coming from the firehose via the relay that is limited atm, but stops this from happening again.

  • 20:00 UTC: I tried a takedown on the account that had created the records in the hope that if a takedown happen it would stop the backlog for that account. Didn't work. I assume the relay was so backlogged that it wouldn't see the takedown till much later.

  • 23:00 UTC: Writing this up while waiting on the backlog on the relay to catch up and let everyone post again. sorry gang :(

  • 00:24 UTC: About to post this now before we're out of relay hell.

  • 2:38 UTC: We have made it through relay hell ๐Ÿ”ฅ. Sorry again everyone. Rate limits are in place so this doesn't happen again and they even made a rule about us.

https://retrobailey.leaflet.pub/3mcdw5o2ufk2f
Oct 26th ๐ŸŽƒ - Nov 2nd ๐Ÿฆƒ
What if PDS MOOver was in SvelteKit?
Show full content

Hey yโ€™all sorry itโ€™s been a minute. My last retrospective was in September. I had a couple of weeks where I didnโ€™t really have a muse for writing, then I had an all out dash from 10/12 - 10/20 of doing the PDS MOOver: The Next Generation update, where I did write up some about it, but no retrospective. Then last week I mostly rested and didnโ€™t look at any code for a while. Well, now Iโ€™m back. The plan this week is to work on PDS MOOver again.

I'm writing this intro on October 26th and going to roughly layout my goals for the week, it will be fun to see how they change

  • Move the UI code from a askama/alpine.js mash up into a project using SvelteKit

  • Move all the migration, plc, backup, etc logic code into a node module so I can publish it for others to create their own ui's for PDS migrations

  • Change the automated backups from every 24hrs to every 6hrs

  • general clean up

May only care about certain things. So this is a bit of a guide on which days I talk about what if you wanted to skim to those parts (it was actually just all PDS MOOver this week, but you gotta keep a format)

  • ๐Ÿฎ MOOve to SvelteKit - All week

  • ๐Ÿ“ฆ npm module - All week


Sunday, October 26th, 2025

I played video games all weekend. It was wonderful. I've been pretty hooked on Plan B: Terraform on steam lately. It also helps that lately I've been reading the Mars Trilogy, which without spoiling too much have similar plots ๐Ÿ‘€


Monday, October 27th, 2025

askama, axum, alpine.js

So. Yeah. I went to add OAuth to the backups page and just kind of looked at the code and said WTF.

A bit of background, the ordinal PDS MOOver was just a index.html and some js attached using vite. The frontend was in alpine.js, which worked great. I like alpine.js, simple and lets you add in a sprinkle of reactivity here and there on your frontend. So with PDS MOOver going to a full backend I thought server side rendering of pages and alpine.js would be the move since it would strengthen the weak spots of the previous setup. Lack of routing, can have different pages easier, etc. Well it does, but the developer experience sucks. I'm just going make a bullet list of what I ran into.

  • askama is fantastic for what it is, but I can never get syntax highlighting work in anything I try and it just makes me feel like I'm writing PHP in Dreamweaver again (great crate and checks every other box tho)

  • Before when working with just plain html I had helpers for things like x-show="someValue" and I could click the someValue and see it in my script tag have code completion all that great stuff. Doesn't like the askama.html templates and broke all the IDE helpers for syntax.

  • Lack of components you can load in with alpine.js really left me with two choices for breaking up long pages. askama partials or a lot of x-show. ehh....

  • Had to switch to using build:watch on js changes and cargo watch on axum, combined with a page refresh and about 3 seconds of turn around, it was not ideal to see changes and was a bad workflow

All of those points combined I really wasn't doing "Keep It Simple Software" like I had said in a previous retrospective. With new features being added, bad developer experience It just felt like It was becoming instant legacy code.

Sept 14th - Sept 20th 2025 - Bailey's Weekly Retrospective

Stitches and missing blobs

SvelteKit

I kind of been having my eye on Svelte lately. Especially thanks to doing the dashboard for selfhosted.social forked from scientific-witchery/pds-dash. Everyone knows the best way to make a decision is to flip a coin, but I went with the second best for this. A pro/con list. (actually it's easier to explain to others on paper than flipping a coin, and probably not best to leave technical decisions to the heart)

Pros

  • Being a full fledged framework I have better control over components, rendering them, routes. etc. It lets me write code that is easier to read

  • My tools just work. RustRover, vite, etc. It all worked and I had syntax highlighting again which is huge for me

  • I was doing some weird Window.JSClassName loading before, can write cleaner code there as well now

  • Everything with the backend is already an XRPC service so no logic change there.

  • Can allow more people to jump in and do contributions since it is not a cursed dev setup

Cons

  • It is a rewrite, and that takes time that I could spend else where

  • Using SvelteKit it will most likely be another docker container in production, meaning I'll probably have to get semi creative with Caddy in front of the XRPC service and the new container running the new UI

  • I don't actually know svelte

I haven't regret the move to Svelte yet. It's still early in the rewrite of the UI, but there's not a ton of UI code thankfully, just hide this, show that. Taking a bit, but I think in the long run it's going to be the best move since I have some other plans for PDS MOOver


Tuesday, October 28th, 2025

PDS MOOver in Svelte

You could say things are really mooving along. I think I got a working knowledge of sveltekit down. So far I have moved the main MOOver page (normal migrations) over and everything seems to be working pretty well. It was also my first test to see if I can divide up some of the code. I moved the "Sign the papers"(PLC Operation) to it's own component and it has helped a lot with readability of the code.The {#if blah } syntax is also starting to grow on me. Makes it much easier to read.

Top form is collapsed in both and still in the parent component, but this should show it pretty well what I mean. What it was before

What it is now

I also moved the Rotation Key display to a svelte component and got rid of an awful window.handle I was using to share state between two askama partials since now I can just pass it down to a partial easily. So yay no more weird global variable

PDS MOOver: The library

The current thinking now is to do a release of PDS MOOver 1:1 with the current version, but in SvelteKit, and a release of the node module(package? Library? Thingie ma bob?). Since this is the bulk of the logic I am leaving it in JS for now since I know it works instead of going to TypeScript, but I did go through and add types to it and working on the JSDocs for it as well. Not much to say there other than this is my first attempt at a true node module with the intent to release, so has been interesting to learn how to work with it. The workflow I have is a bit awkward, but it's working.

1. In my svelte project I have it imported as "@pds-moover/moover": "file:../packages/moover"

2. run a npm run build for vite to build out the library.

3. run a npm run gen:types (tsc -p) to create the type files

4. run a npm install on the svelte project

And I got the changes. It feels a bit awkward, and I think there is probably a better way. But not a ton of changes and it is working so far.


Wednesday, October 29th, 2025

PDS MOOver in Svelte

It's still going. I moved the missing tool over and so far so good.I'm feeling that the code is a bit cleaner and easier to read. Before I had two different forms for the current login and old login, I was able to combine those into one form.

I also got the Turn Off tool ported over, it's the one that helps deactivate your old account if it was left activated for some reason. Which happens a surprising amount.

PDS MOOver: The library

There was a lot more work on this front today. I wrapped up all the JSDoc's so it should be pretty well documented to help others out. I also got the the lexicons uploaded to npm at @pds-moover/lexicons. I'm thinking the current version of @pds-moover/moover is probably going to be the 1.0.0, but I'm holding off till the rewrite of PDS MOOver is completed to make sure there are no bugs.

I also played around a bit with @atpkgs.easrng.net which I think I am also going to upload the modules to. Which they're acutally already there, just you won't know that till this blog is out ๐Ÿ‘€


Thursday, October 30th, 2025

PDS MOOver: The library

As I was building out the new PDS MOOVer UI I realized things like the did:web, plc directory, and PDS MOOver host were a bit awkward to set. So I made sure to add constructors for that. So no matter the flavor of atproto you're making a PDS migration tool for, PDS MOOver should work for you.

PDS MOOver in Svelte

You'll be surprise to know. It's still going. I think I have all the tools ported over now. I just need to run through testing them all again, which takes a long time. Then onto DevOps, which I am hoping to get to tonight or tomorrow-ish. I will have to get a new container going for the new Svelte assets, probably going just rock a node server incase I ever need the backend. Then Caddy to tie it in with XRPC service all on pdsmoover.com

I do have a good example of how Svelte is making it easier for me to make a better tool. I found a bug on the restore where if the user has a HEX based rotation key (I've actually only ever seen these in use on the pds.env file) it would load it in and not work if it was a secp256k1 key, which is what I've seen all the pds.env rotation keys in. With Multibase you can automatically pick out automatically which one it is, and the library I wrote did use that. But with HEX base you have to specify.

So thanks to svelte being a bit easier to work with I added in where if the key is detected to be in HEX format to prompt the user for the type. Small little feature, but a great example of showing what Svelte will let me do else where with the UI easier.


Halloween ๐ŸŽƒ, 2025

It's Halloween! Mostly doing some final testing on the recovery/restore process. I've also reworked the advance options a bit.

I wanted to match a bit more of what the migration does for advance options. The "Recover the did doc & account." will attempt to change the did doc to a temp one with a new verification key to be able to create a new account on the new PDS with the user's did, then signs it over to the recommended one from the PDS along with the user's rotation key. "Restore files from backup" Will restore the repo and blobs from the back up taken on PDS MOOver. Ideally this should make it a bit easier for users to run each individual option and rerun the restore if it is needed, like missed blobs.

What's Halloween without something a bit scary? Well in my testing I was wanting to use the HEX base keys from the pds.env to make sure that works as expected since that is a very possible scenario. Well, I accidentally signed a did doc with a HEX key that was to a PDS with the same rotation key which led me to find out that the PLC is 100% fine with that, but other tooling is not. The first tip off was on pdsls.

And then on boat when I tried to manually fix the document

So that was a bit spooky. But something I am glad to find in testing. I made sure to add client side checking on that.


Saturday, November 1st, 2025

Got a late start on everything today, I mostly worked on devops and did some bug patching here and there. I did have a time trying to get a new docker container to build using npm. Ended up just switching the container build for the new front end to use pnpm, problem solved!


Sunday, November 2nd, 2025

It's here! Release day. I did some final testing today and the actual release. Here's what all changed

New Features:

  • Moved backups from every 24hrs to every 6hrs

  • The front end is now in SvelteKit

  • All the atproto logic is powered by the new @pds-moover/moover node package allowing other developers to create their own PDS MOOver

  • Options on restore page allowing you to do just the PLC operations or restore from a backup

  • Added a new VerifyBackups command for the admin tool allowing me to verify backups are there for the whole server

  • Disable buttons and added loading spinners in a few places when doing an action

Bug fixes

  • Fixed a bug on restore where when using a HEX format key it would not always pick the proper type. This is now radio button if you enter a hex key

  • Added a check on restore to check for duplicate rotation keys on the edge case you are restoring an account to a PDS with the same rotation key

  • Fixed an issue where when recording a repo in the database it would count it multiple times resulting in showing backup size for the server and repos wrong, along with blob counts

  • Fixed a edge case when recording blobs it could miss blobs for a user if there was already a blob backed up with the same cid

Next I'm planning on building out a dedicated migration page with the new node library for selfhosted.social, work on adding a new gatekept route on PDS Gatekeeper to block creating new accounts unless it is a migrations. Then back to PDS MOOver to rework the backups screen with OAuth login for sign ups and checking your stats.

https://retrobailey.leaflet.pub/3m4omf22vc22q
PDS MOOver: The Next Generation
PDS MOOver now supports automated backups and account recovery
Show full content

The Next Generation of PDS MOOver is here! While PDS MOOver started out as a very simple Index.html page to help users migrate to a new PDS it has evolved a bit beyond that. We still handle all your atproto events like migrating, finding missing blobs, etc client side and our server never sees your passwords. But, I had to build a whole backend XRPC service(like an AppView) to handle the biggest and newest feature. PDS MOOver can now handle backs up for your ATProto account and recovery in the event your PDS goes down, or an adversarial event. This is by far the biggest new feature, but it's not the only one!

Special thanks to Bluesky PBC. They were kind enough to award me a grant for my work on pdsmoover.com and selfhosted.social. Thanks to the grant, it really unlocks the next level of development possibilities and lets me dream bigger than before with a only self funded budget, and that's how we got this newest PDS MOOver update.

Straight to the point, what's new?
  • The PDS MOOver kingdom has been united and all the one off tools are under one roof with a navbar for easy finding

  • You can now create rotation keys during migration and manually enter any you may already have

  • Backups! These happen every 24 hours on sign up. And are completely free to you as a user. Can sign in as well to see stats about your backups and if you have any missing blobs

  • On the backups sign up can also register a rotation key (which is needed for recovery/restore)

  • PDS MOOver also supports PDS Level backups. This will check the PDS everyday for new accounts, and back them up. There will be a future update as well to allow PDS Admin to restore accounts. If you're interested in this please let me know. All I ask for is a small monthly sponsorship to help cover server costs. Most cases this is $2, but please let me know how big your blob directory/S3 is for pricing.

  • Restore/recovery, no longer need to fear if your PDS goes down or is beyond recovery. You can now use PDS MOOver to restore your account from our backup service and migrate to a new PDS with your rotation key

  • We now speak XRPC! You can view all supported lexicons. This includes getRepo and getBlob so you can use other popular atproto tools to access your backups

  • Don't trust me? Self host your own PDS MOOver. Along with the source code being open source I have also provided a single docker compose file to allow you to run your own PDS MOOver stack to handle your backups.

With these changes I hope that it provides a somewhat user friendly ways to keep their atproto identity protected, and peace of mind knowing they have daily backups to restore their social life if it's ever needed.


Alright, what's the details?

This will be a somewhat deeper dive of the new features along with talking a bit about the tech behind each of them

Migration changes

There's a couple of new things to migrations to help keep an account safe. The first big one is you can now select to create a rotation key on signup, along with signing up for backups. The rotation key is an important part to account recovery. Without you having your own if your PDS ever disappears or crashes you cannot recover your account. This is a very important thing to have if you ever move to another PDS, and to understand.

When you download this key file it will contain three things. Your did that you will enter if your PDS is down during a restore, your PublicKey if you ever want to verify it's registered with the PLC for your account, and the most important part, the private key. It is important to keep this key and file in a secure location if you ever need it. In the wrong hands this can also let someone take over your account, even if they do not know your password.

The second thing is the ability to add up to 4 rotation keys during the migration. 5 is the limit and you want your PDS to have one. PDS MOOver still does not merge these together to help keep it a bit simple, but the hope is this will cover most use cases and there may even be a more advance PLC tool in the future.

Backups

Backups are for sure the big headliner of this update and took the most time. Backups currently only cover your blobs(pictures/videos) and your repo(posts, following, likes, etc), but I am exploring the possibility of adding your preferences(holds things like your saved feeds), but since this is private data and requires authentication I need to think on it a bit more. Your backups are currently taken every 24 hours automatically. We are hoping as we onboard more users and see how the service works to move that down to shorter intervals. When we pull the backup we only pull the repo if it changes, and only pull new blobs.

Your backups are compressed via zstd and stored on a S3 bucket on UpCloud's Object Storage compressed, then when you restore or need something you can call the PDS MOOver's new XRPC endpoints com.atproto.sync.getRepo and com.atproto.sync.getBlob just like you would on a PDS, it decompresses it and streams the response much like the PDS.

This compression does not actually give us a lot of space savings on blobs, but surprisingly it gave us over 50% compression on CAR exports. You can see in the picture below of my .car export at 22 MiB uncompressed and with zstd compression it is sitting at 8.88 MiB

I made sure to build out everything where it can scale as well. The web front end is behind a load balancer allowing us to scale it out to multiple instances, all the actual backup logic is handled by workers using the Rust crate apalis allowing us to scale to multiple worker nodes during busy hours or on boarding a larger PDS. The hope is with this any surge in traffic or backups can be easily handled with adding a couple of servers here and there to future proof the project for some time to come. Can check out the network diagram for a better idea on how it all works togather.

Special thanks to Orual for the network drawing

You can also login after sign up and see some stats about your backups as well as requesting a manual backup, or to delete them completely and remove yourself from it. There's also a helpful count to show any missing blobs on your repo from a migration that may have missed them.

Restore/Recovery

Not much of a point to a backup if you can't use it is there? One major goal of this release was to make backups and account recovery something anyone can do from a web interface. It's not the most polished user experience, nor does it set out to be. But it brings this ability to everyday users allowing them to protect and backup their digital social identity. With PDS MOOver you can now recover your identity and restore a backup in the event your PDS goes down, or if a malcious actor tries and takes control of your identity on the PLC. To my knowledge this is currently the only web UI that does this, and it is currently tightly coupled with a restore from PDS MOOver, but I am planning on bringing BYOB(bring your own backups) to allow you to recover from any backup you may have. It is important to know you got to have a Rotation Key that you control. This can be setup during migration, or sign up for PDS MOOver backups. Your PDS Admin can also use web ui to restore your account on a new PDS as well with their rotation key in the event it is needed. The recovery process is based off of the Adversarial ATProto PDS Migration blog post.

This feature has been tested to the best of my ability and can see the first successful recovery with this account.

We speak lexicon

PDS MOOver's backend is implemented as an XRPC service. This means you can do any action the front does, but via an XRPC request with your favorite atproto library. You can view all the current and future endpoints in our tangled repo.

This is also how you get your backups when restoring an account. We support both com.atproto.sync.getBlob and com.atproto.sync.getRepo with the hopes to make it easier for other migration tools to interface with a PDS MOOver instance

Can click this link to view a blob that was backed up with PDS MOOver.

https://next.pdsmoover.com/xrpc/com.atproto.sync.getBlob?did=did:plc:l7n7hdzux5ub6yye4du6ifrv&cid=bafkreihferaklovh7rbbmgtde7fzmtkiy27h6yeusz4p3vx6tm7le5r2qe

Future Items
  • Rest, I'm probably not going to touch a atproto code base for at least a week (pending major bugs)

  • Clean up and refactor some of the code. I'm coming off of a week long crunch to get this out, So I want to look at the codebase with fresh eyes

  • Condense the JS logic into an external npm package so that any one can use it to migrate, create rotation keys, restore, or sign up for backups easily in their projects. What I consider a big cornerstone of PDS MOOver and sady broke that a bit with this release for speed of development.

  • Better documentation on running your own instance of PDS MOOver

  • Support backing up of preferences

  • PDS Host restores. This will let you run a restore of all the repos of a PDS in the event of a PDS goes down and you are the PDS Admin

  • PLC editor to change your rotation keys, services, etc.

  • BYOB (bring your own backup) to restores

  • PLC watcher to notify if any PLC operations happen against your account

  • Real time backups via subscribing to the fire hoses from a PDS. You create a new record or add a new blob, we see it and add it to the backup as soon as it happens.

https://retrobailey.leaflet.pub/3m3ntnjv3hc2x
Sept 21st - Sept 27th 2025
Easy goes it
Show full content

A very light update this week. I've been taking it a bit easy and working on other hobbies and just general relaxing. It has felt a bit like one frying pan to the next lately so I wanted to take it easy this past week. I wouldn't say I'm quite burned out, but I have been getting close and slightly discouraged in other areas. So, it's always good to take a break, work on some lower profile things or just have fun for a bit, and that's what I did.

What days did I work on what?

May only care about certain things. So this is a bit of a guide on which days I talk about what if you wanted to skim to those parts

  • โ“ Missing Blobs - Sunday

  • ๐Ÿ‘พ Hollow Knight - Sunday

  • ๐ŸŽต teal.fm - Monday, Wednesday

  • ๐Ÿ‘พ GBA Development - Thursday

  • โฉ Future plans - Friday


Sunday, September 21st, 2025Missing Blobs

Had one small issue with missing.pdsmoover.com. There was a user who had a custom domain name set via /.well-known/atproto-did only and could not do a DNS record. I see these pop up a lot like neocities.org domains where users can have their own mini site on a website so they add the atproto-did json so they can use it as an atproto handle. Since they usually don't have full control of that domain CORS disallows calls to it. And since the missing tool runs completely in the web browser it cannot resolve that handle. I usually consider these bit of a fun loophole. I think I've seen a total of 3 show up in my atproto time. You can also set logins to use the did instead of handle. So I just allowed users to use did or handle to login to resolve the issue for the user

Hollow Knight

Finally sat down and played a bit more Hollow Knight as well. I finally got the dash and feeling like I can move around a bit more. I'm starting to enjoy the game a lot more than I was. I think it works well for something to play for 30 mins to an hour. Take a 10 min break, then come back. Also finding benches more often than I thought I would so feeling like I can be a bit more casual with playing.


Monday, September 22nd, 2025teal.fm

I'm working on teal.fm a tiny bit this week. @natalie.sh asked for a review on a piper pr. So I took a look and tested out the Spotify side since I think I'm the only one who uses Spotify working on piper. All looked great and the PR has a very cool new feature added to piper!

For those who don't know teal.fm is an atproto powered music tracking service like last.fm. Piper is the part that checks your music streaming service and gets what you're listening to and creates the records on your atproto repo. Currently it just supports Spotify and last.fm. But I have the hopes to eventually add Apple Music to it one of these days. So while I was reviewing the pr and testing I figured no time like the present to at least make a start towards that.

Part of that is adding in some UI via a js library. Piper currently just renders out its UI's via strings like below. Simple and works well.

But it's easy to get turned around, so I've been moving them over to their own html files before I start making some UI changes for the Apple sign in.

Hoping with these now being in their own files it will be easier to make UI changes and will allow others who may not be as familiar with Go or backend development to make changes as well.


Tuesday, September 23rd, 2025

Didn't really do a lot of coding besides my day job. But I did go on a 2mi walk, played Skate, and ate spaghetti. So that was a pretty good day in my book


Wednesday, September 24th, 2025teal.fm

Finished up moving all the html strings found in the handlers to their own .gohtml template files. I also setup tailwindcss for the project as well for styling. I used the tailwindcss cli for that since it was the simplest approach. It does feel a bit odd to bring in a node-ish dependency when it was not needed before, but it does give developers working on the UI a familiar tool and I think that's pretty important. I also learned today with the new cli tool it just searches all your files if it's not in the .gitignore (or a few other places)for tailwindcss names, if it sees one it adds it to the compiled CSS, and it's FAST. Wrapped it all up and made this PR. Wasn't the most interesting work ever done, but I am hoping it's a big steppingstone for a new UI and implementing Apple Music easier on down the road.


Thursday, September 25th, 2025GBA Development

Came back to working on my GBA game. I didn't do a ton besides load in the sprite I'm using as the player, This lower res goose that I animated and took down to 16x16. Added in a bit of a loop so I can have a start, playing, and end screen. Looks a bit like this

loop {
    match game.game_state {
        GameState::Start => {
            button_controller.update();

            if let Some(letter) = text_layout.next() {
                text_renderer.show(&mut bg, &letter);
            }
            if button_controller.is_just_pressed(Button::START) {
                game.game_state = GameState::Playing;
            }
            let mut gfx = gba.graphics.get();
            let mut frame = gfx.frame();

            bg.show(&mut frame);

            frame.commit();
        }
        GameState::Playing => {
            game.run(&mut gba);
        }
        GameState::GameOver => {}
    }
}

The idea is there is a main game loop (what's shown here). Once the game starts it goes into another loop that is the actual "game play" loop where the goose is flapping around.Then when you loose it flips the GameState to GameOver :( and does a break to the top loop again. The gameplay currently kind of looks like this

And that's about where I am now. I guess next would be doing the actual start screen besides just the proof of concept I have there. Then onto finishing out the actual gameplay. Leaning towards trying my hand at making my own sprites for the rest of the game. I'm thinking the bottom pipes will be skyscrapers, and maybe clouds or cranes as the top ones? Not sure. Still thinking on it.


Friday, September 26th, 2025Future Plans

Surprise, taking it easier again today. This weekend I am also going to be mostly afk, so the retrospective is getting another early publish.

I know it's a retrospective, but I thought I would take the time to lay out my shortlist of future work I have planned. No real order.

  • Finish the GBA game

  • Clean up and finish the 1.0.5 AT Toolbox release. This will bring some bugfixes and constellation ๐ŸŒŒ shortcut actions. Mostly done just have to add in bsky video posting, quote option for bsky posts, test, and write up some new example actions

  • Write up a Terms of service and Privacy policy for selfhosted.social. I want to open the door to more people with the selfhosted.social PDS, but I am a bit nervous to do so in some aspects. So I plan on drafting these up, get the input from current residents and see about opening it up more.

  • Work on at://advent. Winter is coming.

  • Add Apple Music support to teal.fm

  • Learn a bit more Svelte and turn the landing page on selfhosted.social into a bit of a mini app view just for the PDS. I want to add like counts, comments on recent posts, etc.


Saturday, September 27th, 2025

https://retrobailey.leaflet.pub/3lzrflm2rg22u
Sept 14th - Sept 20th 2025
Stitches and missing blobs
Show full content

Another week, another set of open source work to do. Earlier in the week was the bulk of my atproto work, then I took it pretty easy after Wednesday focusing on learning some GBA development with rust. Next week might be a bit similar as well, I'm a bit tired and need a little break. But who knows, may get a second wind come Monday and knock out a lot of stuff! But that's the future, let's take a look at what I did this past week.

What days did I work on what?

May only care about certain things. So this is a bit of a guide on which days I talk about what if you wanted to skim to those parts

  • ๐Ÿงต Stitch Counter(side quest) - Sunday, Monday, Wednesday, Saturday

  • โ“ Missing Blobs - Monday, Tuesday, Wednesday

  • ๐Ÿ˜˜ Keep it Simple Software(opinion) - Wednesday

  • ๐Ÿ“„ pdsadmin Web UI Spec Draft - Thursday

  • ๐Ÿ‘พ GBA Development(learning) - Thursday, Friday


Sunday, September 14th, 2025Stitch Counter

I love tangled.sh. It has easily become one of my favorite parts of the atmosphere. Both @icyphox.sh and @oppi.li have done a fantastic job with it and every week bring great new features.

I have been playing around with the idea of creating a tangled.sh trending bot so I decided to give it a try with Stitch Counter.

I created the project from my Rust atrium template. Although I did butchered it some. This is a project I wrote to solve a problem I had, so it's not the cleanest code. Some other fun tidbits.

  • Uses rocketman as the JetStream listener

  • Uses constellation ๐ŸŒŒ for counting the stars on a repo

  • Uses slingshot ๐ŸŽฏ to grab the record for the repo so I don't have to resolve the did doc

  • chromiumoxide for a headless chrome to render the preview image, which is just the HTML card for trending on the tangled timeline

Currently the way it finds if something is "trending" is if a repo got x stars during y hours it will make a post about it as long as it hasn't in z hours. Which at the time of writing this it's 2 stars within 4 hours. All of those are set via env variables and the stars and when a post was made is kept in a sqlite DB. Expecting to tweak those thresholds, but will probably leave it there for now so it should make a few posts a day.

Pretty happy with how it has turned out so far!


Monday, September 15th, 2025Stitch Counter

I ran Stitch Counter on my laptop for most of the day and it has been working pretty well! The plan is to eventually put it in a docker container and run from a Raspberry Pi at my house. Just testing right now to make sure thresholds work as expected and no other bugs. I had some concerns since it depended on headless chrome for building those card images in the post. Seems like an awful pretty big dependency to get that. Along with not sure how well it would run as a long term process, error handling, etc.

Was pretty worried about it till @chadtmiller.com had replied to a post and pointed out that thanks to his work with @slices.network he had an endpoint already creating nice open graph images of a tangled.sh repo and said I could use that! I am super appreciative to him for letting me know and allowing me to use that resource. I was happy to drop headless chrome and his preview images do look really nice. Hoping to move Stitch Counter to the rpi sometime this week.

Missing Blobs

We had A LOT of people decide to migrate to a new PDS last week. Because of this a few users were met with rate limiting during the migration process no matter the migration tool they used. Still not sure if it was fully rate limiting, or timeouts on the PDS side, or possibly a PDS was sharing the rate limit across the board (taking a guess here). Either way, we ended up with a sizable chunk of users who were missing some of their blobs all at once. We noticed it once the CDN cache for profile pictures for a few users would expire and they were asking for help a few days after the migration. Very thankful that we just deactivate old accounts on the Bluesky hosted PDS instead of deleting on migrations. Thanks to that it means we can grab the missing blobs from their old repo.

So I started working on missing.pdsmoover.com a tool for users to check if their account is missing blobs, and if so to import them from their previous PDS. Itโ€™s a pretty simple tool very similar to pdsmoover.com and pdsmoover.com/turnoff.html. Just a simple alpine.js website to help those users out.

Some tidbits

  • I found out that you do not have to re activate the previous account to export the blobs like I expected. Being authenticated worked. Was super happy to find that out and not have to do any juggling of activating and deactivating the old account

  • Logins into your current PDS and checks to see it you are missing blobs

  • Grabs the previous PDS you were on via the PLC log. Or has an advance button to input the PDS url if you know it, or the tool has trouble finding it

  • Uses your did as the identifier when logging into your old PDS since we know it from the first login. So just need your old password to login into the old account.

Also we have a new variant of the PDS MOOver cow!


Tuesday, September 16th, 2025Missing Blobs and counting them

So like most things, the real world is never as nice and neat as your test environment. The PDS has a /xrpc/com.atproto.server.checkAccountStatus endpoint for use during migrations. Two properties are useful for blobs. expectedBlobs for getting how many blobs your account expects you to have, as in how many atproto records have a blobRef. importedBlobs is how many blobs that have been imported(uploaded) to your account. I found during testing that those would always match up pretty well, but in real life not so much. My best guess is that improtedBlobs shows temp uploads that have not cleared yet, so could not always trust that to know if users had missing blobs. Add that along with some of these users migrated over a week ago that count could really be off. Decided to switch to using /xrpc/com.atproto.repo.listMissingBlobs as the official check of if a user is missing blobs or not. I just do a check with a limit of 10, If it returns an empty array the user is good to go.

Keep It Simple Software

You've probably noticed that the pdsmoover.com tools are all pretty simple. Not a lot of moving parts, not always the most user friendly. etc. Well, I thought I would talk about that a bit. It's not complete laziness, although that is a bit of it. It takes a lot of energy to create great software for free, and I'll be the first to tell you that. Not being spiteful, just honest. I am extremely blessed to be able to do that and help others. It brings me a lot of joy to be able to help others out and make a change in the world for the better. Even if it's a small one. But there is a price to the work.

I keep these tools simple with the idea of less moving parts, less logic, and less just general UI moving around means less to break and easier to test. For example pdsmoover.com is just one long form you fill out. Doesn't really hold your hand or do a ton of checks like, is it actually a PDS, is that handle available, selectable domain endings, does it require an invite code, and etc. Instead, it fails and give yous an error if one of those edge cases are met. All of that is accessible, and there are other migration tools out there like atpairport.com and tektite.cc that do a fantastic job of integrating that which ends up providing a much cleaner user experience.

Well why don't I integrate that into PDS MOOver? Well, when I created the tool I took a look around at everything offered and the one tool I used with the most success and everyone recommended was the goat cli. But it wasn't without it's faults (minor and still a goat of a cli tool). 1, a higher level of entry with it being a cli tool. 2, a lot of times the PLC token you entered at the start would be expired by the time it uploaded all your data to the new account. I thought, what if there was a similar tool like goat, but it was more accessible to end users. goat proved that a simple approach would work perfectly for account migrations in the atmosphere.

So I set out to check these boxes for a new tool

  • Simple logic

  • Everything to happen client side

  • Fail early if there's an issue

  • Some retry logic

  • Would request a PLC token after the time consuming uploads are done

  • A pun

And that's how pdsmoover.com came to be! It's not the best tool out there, it's not perfect, but that was never the goal. The goal was to make something that would work the majority of the time for the majority of users. It has caused a bit more work on support, but overall I've been very happy with the result and have seen fewer than expected edge cases of it not working for users.


Wednesday, September 17th, 2025Missing Blobs

I talked a bit yesterday about the different tactics of deciding how to decide if the user's account was missing blobs. I decided to go all in on the listMissingBlobs endpoint and just hide from the UI the counts everywhere but at the very end of the process. I'm hoping this makes it a bit easier on users and give a clearer "Yes you are missing blobs" or "No you are not missing blobs". Especially at the end. I saw a lot of users getting the try again even tho they had all the blobs, just the imported vs expected was off so it was always saying they should try again.

  • Login check now calls listMissingBlobs. If array is not empty tells them are missing blobs

  • Calls a 1,000 limit on the first list call. Uses that count for the "Migrating blobs: x/y" status update. If the user has over 1,000 missing it updates that second count. Was getting the missing count from the expected - imported and was off a lot of the time for users

  • At the end I did leave the count if they were still missing the blobs. I figured a screenshot from the user would be handy for troubleshooting if we needed to

Stitch Counter Docker Image

Not much to say here other than @stitch.selfhosted.social is now up 24/7 and safely on a raspberry pi in my laundry room and I "unhide" the tangled.org repo. Aka changed the description from "Don't worry about it". Can find the code here.


Thursday, September 18th, 2025

Not a ton of "work" happened today. Mostly just bebopped around.

pdsadmin UI

One of the goals I have for PDS gatekeeper is to host a pdsadmin UI screen that users can sign into via their atproto account and manage the PDS instead of relying on the admin password only. I wrote up a rough draft of my idea of how that would work.

PDS gatekeeper's PDS admin idea

Pardon me if I over explain, but I want to make sure to give a lot of base context for anyone reading this

GBA Development

It's been a minute since I've done any no_std rust and I also recently found out that agbrs has completed their online book on getting started. I figured it was time to check it out again.

About 2 years ago when I first heard about agb their book had about 1 or 2 chapters on building a pong clone on the GBA. I had read through it and somewhat finished it out to learn agb. Can see that attempt here. I have to say it was such an amazing feeling to see the GBA boot screen right before something I wrote.

Since then I've came back to agb with varying success various times, as seen below.

I'm taking it slow coming back. Feels like a lot in the crate has changed, but may just be I haven't looked at the code seriously in a while. I'm working through the new-ish agb book. Then will probably make a smallish game with the goal of completing it fully. I'm thinking a flappy bird clone. Nice and easy to do (famous last words).


Friday, September 19th, 2025

A chill day. Didn't do any open source per say, but I did continue learning more GBA development stuff

GBA Development

Not a ton to say other than I finished up the pong example in the agbrs book. It was a lot of fun! Was a good refresher on everything and got to learn some new things like using Rect for collisions. I can't remember if it was there before or not, but last time I messed around with agbrs I was doing the collisions checks on my own. Was nice to have a helper method for that.


Saturday, September 20th, 2025Stitch Counter

When I wrote Stitch Counter I didn't really add a ton of error handling (whoops). So due to the minor constellation outage (ty fig for all you do with that). Then finding out it was possible for some images for repos to not be rendered there was an outage for a while for the bot and the bot missed a few new trending posts. If the bot could not find one or the other it would just not send the post. No biggie. Just made those optional and if the bot can't find them then the show goes on without them being added! I also switched to the community maintained JetStream (ty again fig), while also giving the ability to set a custom one from the env. Can check out that work in this commit.

And that was my week! Not sure what next week holds, not making any plans with the hope to take it a bit easier. But weโ€™ll see! Thanks for reading, see ya next Sunday-ish!

https://retrobailey.leaflet.pub/3lzclps6wmc2m
Sept 7th - Sept 13th 2025
The rest week that wasn't
Show full content

I've really enjoyed checking in on @bad-example.com weekly updates on microcosm. So I thought I might give it a try as well with some of my open source work! Also gives me a chance to try out leaflet.pub. I will most likely keep these semi short and a bit more casual with the idea I spend about 30 minutes writing up the retrospective of the previous day.

What days did I work on what?

May only care about certain things. So this is a bit of a guide on which days I talk about what if you wanted to skim to those parts

  • ๐Ÿ“… at://advent - Sunday

  • ๐Ÿ”’ PDS gatekeeper - Monday

  • ๐Ÿ‘พ Hollow Knight - Monday, Thursday

  • ๐Ÿงฐ AT Toolbox - Monday, Tuesday, Wednesday, and Thursday

  • ๐Ÿฎ PDS MOOver support - Wednesday

  • ๐ŸŒƒ Blacksky.community - Wednesday

  • ๐Ÿงน PDS Dashboard (side quest) - Friday

  • โœ๏ธ Wrote a Blog Post (side quest) - Saturday


Sunday, September 7th, 2025at://advent challenges framework

A bit of a foreword if you're not familiar with at://advent. It's a project me and a few others are working on. The goal is Code of Advent like thing, but for atproto! The hope is some fun challenges to help people learn more about the protocol and have some fun.

I finished up my PR for the challenges framework for at://advent. Took an LOT longer than I was expecting. But I finally got it in! Can see what that looked like over at @oppi.li/at-advent PR #3. Ended up being a lot of code, but the end goal was to make a simple way for challenge creators to a.) use rust code to create logic to verify a challenge has been completed. b.) an easy way to create custom views to show those challenges.

The TLDR for each is I used a trait for the rust code so the challenge creator has full control over how it works, gives some helpers, and allows re usability on the web routes. Can see that trait here and an example of how it is used here. The view for the challenges themselves are just in markdown and you can use handlebars templating syntax to customize them a bit, can see one here


Monday, September 8th, 2025

I spent a good chunk of time and energy on the at://advent challenges framework over the weekend and last week. So this week I'm going to take things a bit easier (HA HA HA HA - Bailey with 20/20 vision on Sunday morning before publishing)

AT Toolbox

I've had my eye on wanting to do a small AT Toolbox update lately to add a few new shortcut actions. @bad-example.com kicked it off by kindly sharing a bug they found in the action to get a serviceAuth token (JWT token signed by your user that is verifiable on protocol)

So I figured it was only fair since I nerd snipped them last week to return the favor and be nerd snipped this week. So thus started "the fig update" for AT Toolbox! Main goals were fixing that bug and adding shortcut actions for constellation ๐ŸŒŒ

Work done:

  • Fixed the bug found. in my getServiceAuth endpoint. I was using the user's did as the audience parameter instead of a did:web provided by the user ๐Ÿ˜ฌ

  • Cleaned up a swift client I had written a while back for constellation ๐ŸŒŒ api end points

PDS gatekeeper

@rude1.blacksky.team reached out about a bug on PDS gatekeeper. I had implemented rate limits on the sign in endpoints since they do verify password hashing. It's using tower_governor which works in allowing bursts. Example, given a burst of 5 and 60 seconds as settings this means that after 5 tries in 60 seconds it takes one off and you can try again, or if you wait 5 minutes can try all 5 at once.

Well it appeared that it was being a bit more aggressive on those limits. Turns out since it was being a reverse proxy by caddy it was using that local IP (127.0.0.1) as the key for rate limiting, so everyone on the server shared the same rate limit (whoops). Was a pretty easy patch and got that out the door. Can see that here.

Hollow Knight

I never played Hollow Knight, but with all the hype around Silk Song I wanted to give it a shot and playing a bit here and there. Sat down Monday night and defeated the first boss. So far I like the game, just wish it was a smidge more accessible for casual gamers. The fear of playing and not finding a bench to save before I have to go is real, but that is also some of the charm of it.


Tuesday, September 9th, 2025AT Toolbox

I finished implementing the constellation ๐ŸŒŒ endpoints into shortcut actions. I'm pretty happy with them all. The /links/all endpoint is a bit awkward since it returns a dynamic object with the collections as the keys, and the keys of it as paths. So the shortcut version returns counts, a semi friendly way for shortcuts to view that info as Collection: com.my.collection, Path: .subject.uri along with the JSON response. The hope there is it is enough they can get the collections and paths to know what to use on the other constellation ๐ŸŒŒ endpoints, and the full JSON response is there if they need it.

Here's an example of what a shortcut to see how many people have verified a user may look like demoing a new action that uses constellation ๐ŸŒŒ.

I also started playing around with the ability to make custom xrpc request from an action. The idea is this will let users create requests for other /xrpc endpoints that are not implemented yet (like app.bsky.bookmark.getBookmarks). I did a bit of a sneak peak here of it

couple of info bites:

  • When you select a user it uses that users PDS as the baseurl

  • the lexicon method then appends /xrpc/{lexicon method}

  • GET queries and POST bodies can be passed in via shortcut dictionaries.

  • Audience allows you to set the atproto-proxy to a did web with a service like did:web:myneatapp.view#service_name so that the request can be proxied to another appview as described here

Still heavily wip and hoping to do some more testing on Wednesday and maybe even get a Test Flight build out for some extra eyes on it.


Wednesday, September 10th, 2025AT Toolbox

I had written most of the code yesterday for XRPC proxying so today was doing testing and bug patching A fun little bug I hit was I kept getting "Bad Token Method" for trying to proxy a request to the chat.bsky.* before I realized that I had not set "Allow access to your direct messages" when creating the App Password ๐Ÿ˜ฌ. Took me reading the source code before I realized. In my defense, the error code did make it sound like it might have been something else and I would expected an error about permissions...

pipethrough.ts#L65

if (
  credentials.type === 'access' &&
  !isAccessPrivileged(credentials.scope) &&
  PRIVILEGED_METHODS.has(lxm)
) {
  throw new InvalidRequestError('Bad token method', 'InvalidToken')
}

I did get a build submitted to Apple for Test Flight, so I am hoping it is approved soon. And actually as I am writing this it was approved ๐Ÿ˜ณ. Was a 35 minute turn around on that review. So now all that is left in 1.0.5 is testing, write some new documents for the new actions, and some new examples. It's close!

PDS Migrators

With the events of the day both in the world as well as the atmosphere many users were ready to make a jump to a third party PDS not hosted by Bluesky. Last month I created the PDS migration tool pdsmoover.com to help users do just that. To this day it is probably my proudest atproto application. It is the one that has had the most impact on helping users feel safer and protect their online identity by moving to their own or another hosted PDS. So I spent a good bit of the night helping users with any issues they might of had as well as watching the unique visitor count skyrocket for pdsmoover.com.

Blacksky.community

The PDS blacksky.app supports two endings for handles .blacksky.app or .blackskycomra.de. This caused an issue where some users would accidentally change to one or sign up with one and cannot change to the other.

The official Bluesky client does not support multi domain handle changes in the settings. I'm guessing it just may have been an oversight since they only support .bsky.social. But they do support it on account creation. So I made a PR to blacksky.community to copy that functionality to the change handle screen in settings.

PR can be found here


Thursday, September 11th, 2025AT Toolbox

I ended up not working a lot on side projects. I did get some feedback back from the Test Flight build. So far so good! No major bugs reported. I am going to spend some time this weekend on documentation and examples for the new actions. I debated about not doing new examples for this release, but itโ€™s really the best way to test the shortcut actions to make sure they work as expected. Hoping to have it submitted to the App Store by Saturday-ish.

Hollow Knight

I played a little bit more of Hollow Knight as well. Still donโ€™t think Iโ€™m cut out for it, but it has been pretty fun! I got my second charm, the one that allows collection of more souls.


Friday, September 12th, 2025PDS Dashboard (a side quest)

Everyone loves a good side quest. What I thought would take me a couple of hours ended up being a couple more on top of that. But selfhosted.social now has a landing page! The witches over at @witchcraft.systems has probably the coolest looking landing page for their PDS(pds.witchcraft.systems) in the atmosphere. While looking at it today I found out that it is open source, so I decided to fork it and change it a bit for the selfhosted.social PDS.

You can find my fork at @baileytownsend.dev/pds-dash-fork.

A few of the changes I made:

  • Added a simple cache in localStorage for some of the repeat calls like did -> handle, and the user's profile pdsfetch.ts#L432

  • Changed loading the blobs from com.repo.sync.getBlob to use Bluesky's cdn PostComponent.svelte#L99

  • A new theme that is somewhat similar to daisyui's dark theme dark/theme.css

  • Reply to now shows the author's name instead of the did since those are cached and easier to load in

On the UI side I added the ability to load in a user's most recent xyz.statusphere.status and teal.fm play record

I added RichText support as well for the posts so they would render hyperlinks for link facet's and mentions

This was also my first time using Svelte and first time using Deno since 2022-ish(when fresh launched).

Svelte I really liked the template rendering. I've always found the closer it is to HTML the easier it is for me to read. I think I'm going have to add it to my list of things to read up on.

    <div id="postText">
        {#each post.richText.segments() as segment}
            {#if segment.mention}
                <a href="{Config.FRONTEND_URL}/profile/{segment.mention.did}"
                    >{segment.text}</a
                >
            {:else if segment.link}
                <a style="text-decoration: underline" href="{segment.link.uri}">{segment.text}</a>
            {:else if segment.text}
                {segment.text}
            {/if}
        {/each}
    </div>

Deno was also nice! My favorite part about it was besides the commands I had no idea I was using Deno. This may sound like an insult, but it was so nice to just have it plug and play with the tools I am familiar with. This was not the case 3 years ago. I am going to be reaching for Deno a lot more when developing projects going forward I think.


Saturday, September 13th, 2025

MigrationMania

I think a lot of atproto users took the weekend to migrate. Was an increase in mentions in people needing help and thanking me for PDS MOOver. Love to get those, it's really nice to see something I've written help others. Here's a 7 day graph to show just the uptick in interest for users going to pdsmoover.com

The hell is the atmosphere anyway?

I wanted to take a stab at explain what atproto was to a larger audience so I wrote the leaflet What the hell is the atmosphere anyway. I'm pretty happy with it! The goal was to take some of the basic concepts and break them down for a larger audience to digest.


And that's my week! Was kind of expecting to be a bit more laid back, didn't quite happen. But that's okay. Was a fun week and excited to see what next week brings. Thank you for reading if you made it this far! I plan on putting out a similar post to do this next Sunday-ish!

https://retrobailey.leaflet.pub/3lysr3i3vc22e