Indie Mac/iOS & web developer • Currently building random fun things on ATProto using Ruby and occasionally JavaScript → blue.mackuba.eu "mostly-happy-but-occasionally-grumpy Ruby guy" - Why 🌍 Kraków, Poland 🇵🇱🇪🇺 • ✝️
Skyfall optimizations and a new gem
I’m starting a new blog post series that I’ve named “ATProto in Practice”. I want to go through some practical tasks that you’re likely to need when working with the protocol, using Ruby and my Ruby gems for the examples. These will (hopefully) be a bit shorter than my standard book-length blog posts here 🫠 If you’re new to the AT Protocol and you haven’t read my long Introduction to AT Protocol post that goes through the whole architecture and defines the various pieces of it, I recommend you read that one first, because I won’t be explaining everything from scratch again here. Let’s start with something pretty fundamental: user identity. DIDs & handles Every account has an immutable identifier called a DID (Decentralized Identifier), e.g. did:plc:z72i7hdynmk6r22z27h6tvur. This ID serves a similar role as an UUID might in an SQL database – it’s used everywhere behind the scenes in the protocol for any references between accounts and records, in URIs and so on. But generally every account also has a human-readable handle assigned to it, which can be changed at any time, and this handle is normally what you see in the UI, often in user-facing URLs, what you use to log in with, etc. The handle is a domain name, e.g. @python.org, and any existing domain name you own and use for your website can be used as your handle. As you start building on ATProto, one of the first things you might need to do is convert between these two identifiers. For example, a user logs in to your app using a handle and you need to first “resolve” it to a DID, or a URI or other reference in a record points to a DID, but you want to show the corresponding user’s handle in the UI, and the handle is not included in the data you got. A useful UI tool for quickly looking this up is internect.info. If you look up e.g. “firefox.com” there, you will get a page showing details of Firefox’s ATProto account: the DID (did:plc:m424cqoxwhxgjutbta7jmrur), when it was created, assigned PDS hostname, and so on. Now, how to do the same thing in code? Every DID has a so called DID Document, a JSON file storing some basic info about that identity, and among other things, that document includes the alsoKnownAs field, which lists the assigned handles, in the form of at:// URIs with only the first segment. Almost every account will have exactly one handle assigned, but it might happen that one will have zero or more than one. It might also happen that the array will contain some invalid strings which aren’t proper handles – e.g. don’t include the at:// prefix, or only contain one word and no periods. You need to filter only the valid ones. The assignment is bi-directional – a DID has one (usually) handle assigned in its document, and a handle resolves to a DID. You should ideally check the assignment in both directions, because it could happen that the other sign of the assignment is no longer valid, or worse – has never been real. That is, when you resolve a handle to a DID, check if th…
I had a comments section on this blog since the beginning. I always felt like a comments section was kind of a requirement for a blog: it’s not just a website where you publish posts, it’s a website where you publish posts and other people can comment on them. I strongly disagreed with bloggers who openly declared that they’re removing comments from their blog because their blog is not a place for others to add their remarks, and that they can always write to the author on Twitter if they want to contact them. But one obvious problem with comments on a blog, which got worse and worse over time, was spam. People tried to solve it e.g. with captchas, though as the bots got smarter, captchas had to get more complicated and annoying. For a very long time, I had a super simple captcha in the comment form that just asked: “Are you a human? (yes/no)”. You just had to write “yes” in the text field. And for a very long time it was enough – obviously it’s trivial to make a bot add an additional query parameter, the point was that nobody would think about it when writing generic spambots, and my blog isn’t famous so the bot authors wouldn’t have come across it. Things got worse around 2018-19. I started getting comments (I had email notifications for them) that were spam that obviously got around the captcha. So I started tweaking the captcha, adding multiple slightly more complex questions, but still such that you could answer them if you’re a developer or an Apple user – “What’s the name of the Mac operating system”, “Who is currently the CEO of Apple”, and so on. But it only worked for some time, and then I started getting spam again, while making the questions progressively harder over time: At some point I figured that this had to be actual people, rather than bots, adding the comments (talking in general, not about Jerry here above), people sitting at a computer, browsing websites and filling the forms by hand, because there was no way a bot would figure out that the question was a captcha and that it had to google for a name of a function (note, this was in pre-AI times). The comments were usually either in Russian or Ukrainian, or linking to some local service companies in some random small town in the US. There was no way I could out-captcha an actual human that can google for an answer. But it wasn’t a huge problem, I didn’t get many of those comments, and it was fairly easy to filter them out still by content, so I didn’t bother changing the system further. But here’s the thing: it’s not 2010 or 2018 anymore, and some other things have changed in the meantime too. I think it’s a bit of a truism that blogs and “blogosphere” aren’t what they used to be 15+ years ago. People post their thoughts on social media, Instagrams, Facebooks, LinkedIns, Substacks, record Tiktoks, YouTube videos or podcasts. Reading and writing blogs and commenting on them is a very niche hobby, let’s face it. After 2020-21, when for a while I was getting a ton of comme…
On Apple's CEO change
Man I hate changes
A bit over a year ago, in the first week of January 2025, I migrated my main Bluesky account to my own PDS on a Netcup VPS. It’s been quite easy to set up using the official installer, and it’s been running pretty much without any problems or maintenance the whole year. Despite that, I haven’t been 100% happy with this setup for one reason: Docker. So I decided to try to take it out of the box, and I made it run first on the same VPS installed separately, and then moved it to another machine this month with a clean install. This blog post is a guide to how I did this, if you’re interested. There are a few existing posts about this already: https://benharri.org/bluesky-pds-without-docker/ https://char.lt/blog/2024/10/atproto-pds/ https://cprimozic.net/notes/posts/notes-on-self-hosting-bluesky-pds-alongside-other-services/ But I figured it doesn’t hurt to make another one that does things slightly differently again. “There are many like this, but this one is mine”. (I mostly followed the benharri.org version.) Note: I’m describing what I did to migrate an existing PDS from in-Docker to outside-Docker, so I already had existing data and pds.env config; if you wanted to install one from scratch this way, you’d probably need to also set up the config manually. You might be asking: why? And that’s a good question. I mostly wouldn’t recommend this setup over the standard Docker one by default, unless you know what you’re doing. The standard installation is literally running one command and answering some questions, and then it auto-updates and manages everything. My reason is that I’m generally pretty familiar with installing things on Linux servers manually, but I’m completely unfamiliar with Docker. I always wanted to do some modifications on the PDS, but I didn’t know how, because the Docker setup basically takes over the whole server for itself. I don’t know where it pulls code from, I don’t know where it puts it, and I don’t know when it can overwrite any changes I make. I don’t feel in control. (And to be clear, this is likely a me problem.) So here’s what I did (this setup is for Ubuntu 24.04 Noble): Install Nginx The standard PDS distribution uses Caddy, but I use Nginx everywhere and I have configs built for it, so I’ve set up Nginx: # install Nginx sudo apt-get install --no-install-recommends nginx-light # enable HTTP on the firewall sudo ufw allow http/tcp sudo ufw allow https/tcp # if you haven't enabled ufw before: sudo ufw limit log ssh/tcp sudo ufw enable Also here’s a standard thing I do on VPSes to let me install webapps in /var/www from my account: # set up environment for webapps sudo groupadd deploy sudo adduser psionides deploy sudo chown root:deploy /var/www sudo chmod 775 /var/www I also need Certbot for LetsEncrypt: # install Certbot sudo apt-get install --no-install-recommends certbot python3-certbot-nginx sudo certbot plugins --nginx --prepare certbot plugins --nginx --prepare does some initial setup of …
qqqqqq
More ATProto stuff
140 commits later
Skythread, Svelte, blogs… and more
(Last update: 16 Aug 2026.) I come across a lot of blog posts about the AT Protocol and Bluesky technicals – both on Bluesky official blogs and those of the team members, and by independent developers from the community. So many people are blogging now (especially now that Leaflet got popular in these circles) that I started using an RSS reader again just to keep up with everything. These posts are usually shared widely for a day or two, and then kind of forgotten – but a lot of them contain some valuable knowledge that is still relevant much later. Even if someone remembers that something like this has been written, it’s not always easy to dig it out from the archive. I thought it would be nice to have one place collecting those old and newer blog posts to make them easier to find. So I went through those RSS feeds, my like archives and other places, and collected everything I could find here in an organized list. I also included the documents from the “Proposals” GitHub repo, and various posts from the “Discussions” section in the ATProto repo. This is a subjective selection – from many blogs I skipped some less relevant posts or only included a couple out of many – so if you’re interested, click through to the home page from any post and look for the other posts there. Search posts by title: Bluesky official sources atproto.com articles Atproto for distributed systems engineers (Sep 2024) Atproto Ethos (Apr 2025) bsky.social/about/blog (non-technical blog) Composable Moderation (Apr 2023) How to verify your Bluesky account (Apr 2023) Federation Architecture Overview (May 2023) Bluesky: An Open Social Web (Feb 2024) Bluesky’s Stackable Approach to Moderation (Mar 2024) Tips and Tricks for Bluesky Search (May 2024) Bluesky Welcomes Mike Masnick to Board of Directors (Aug 2024) Bluesky Announces Series A to Grow Network of 13M+ Users (Oct 2024) 2024 In Review (Dec 2024) Bluesky’s Patent Non-Aggression Pledge (Oct 2025) What’s Next at Bluesky (Jan 2026) A New Chapter for Bluesky (Mar 2026) Bluesky’s 2025 $100M Series B Lays Foundation for Open Social Web (Mar 2026) Bluesky names Toni Schneider CEO (Jul 2026) atproto.com/blog (dev blog) Click to expand 2023 Why are blocks on Bluesky public? (Jun 2023) Featured Community Project: Skyfeed (Aug 2023) Posting via the Bluesky API (Aug 2023) Updates to Repository Sync Semantics (Aug 2023) Rate Limits, PDS Distribution v3, and More (Sep 2023) Bluesky BGS and DID Document Formatting Changes (Oct 2023) 2023 Protocol Roadmap (Oct 2023) Download and Parse Repository Exports (Nov 2023) Featured Community Project: Bridgy Fed (Dec 2023) 2024 Early Access Federation for Self-Hosters (Feb 2024) Announcing AT Protocol Grants (Mar 2024) Bluesky’s Moderation Architecture (Mar 2024) Meet the second batch of AT Protocol Grant Recipients (Apr 2024) 2024 Protocol Roadmap (May 2024) Labeling Services Microgrants (May 2024) Typescript API Package Auth Refactor (Aug 2024) OAuth for AT Protocol (Sep 2024)…
On switching from micro.blog
for testing
About a new tool I've built, which lets you search for text in all the posts you've liked, reposted, quoted or bookmarked