Identities for entities did.bot

feat: run your own PDS on a VPS with `didbot deploy vps`, and check it from outside #2507

Open permadeath.com
targeting main from claude/onboarding
Open opened by and assigned to permadeath.com
targeting main from claude/onboarding

A real didbot PDS on an ordinary VPS, crawlable and on vibescrobble.com, from your own machine. didbot deploy vps root@<ip> --hostname pds.example.com --operator <handle> --image <image> first asks public resolvers for the hostname, a wildcard name and one _acme-challenge.pds.example.com NS pds.example.com delegation, and refuses with the exact records when they are wrong. Over SSH it installs Docker if needed, the unit, the converger from terraform-aws-didbot-pds (reading /etc/didbot/release in place of SSM: upgrade in place, roll back a release that fails /health) and a daily stop-and-archive backup, to a local path or S3-compatible storage. Then it checks HTTPS from outside. The server gets its <zone> + *.<zone> certificate over DNS-01 by answering that one delegated name itself on port 53 ([tls] acme_challenge = "served"), so no DNS provider credential exists; per-name HTTP-01 would hit Let's Encrypt's per-domain limit within days. [relay] check_on_boot asks each relay (bsky.network, relay.vibescrobble.com) for the host once serving and requests a crawl if it was dropped. didbot deploy check runs what a stranger would: DNS, TLS on a wildcard name, the claim, getHostStatus (requesting a crawl if needed), and an agent's handle, its repository on each relay and its place on vibescrobble.com. deploy init is now deploy aws, and the quickstart is gone from this PR. "Run your own PDS" leads with the VPS path, then AWS.

Verified in a dry run on Docker (192.88.99.0/24, because the SSRF guard rightly refuses a private-address operator): a systemd "VPS" with its own dockerd and sshd, Pebble as the CA, CoreDNS as the operator's DNS host with the real NS delegation, unbound following it, and a mock relay that only reports repositories it saw as #commits on the server's firehose. From an empty VPS it took 142 s to 12 of 12 deploy check passes. An unchanged rerun leaves the server running, a new image upgrades, a broken one is refused and rolled back, backup and restore keep every account, a relay that forgot the host is asked again at boot, and a missing delegation is refused with the records. Tests: served DNS over UDP and TCP, the relay boot check, config parsing, the CLI. Hickory is 0.26 because 0.25 carries RUSTSEC-2026-0118 with no patched release; deny.toml skips the rand 0.10 line it brings, with the reason. Round 3 rebases onto main and folds in audit B's notes on this path: the VPS log and blob budgets come from the host's disk, the journal is persistent, the release waits are flags with the module's ranges, the release's config is gzipped as the AWS converger reads it, the container mounts only its config and a trust directory, the image's healthcheck uses TLS on 443, extra trusted CAs are logged at startup, and the listening (tls) line both convergers wait on is marked as their contract. Round 4 rebases onto main, whose image healthcheck now verifies the certificate against the server's public name: the VPS unit hands it https://<hostname>/health, and the dry run reports it healthy.

Not done from here, because it needs a new host and spend: one install on a real VPS against Let's Encrypt and bsky.network, following docs/run-your-own-pds.md. infra/pds/templates/didbot.toml.tftpl gains four commented keys, so pds.did.bot's next apply writes a new release and restarts once.

Before (round 1) and after:

before after

🤖 Generated with Claude Code

Commits 0