atmobin.lol #
A stupid PDS-backed pastebin. Paste text and images; atmobin writes lol.atmobin.paste
records into your atproto repo and proxies them back. No copies kept.
URL scheme #
Everything is atmobin.lol/<did or @handle>/<record nsid>/<rkey> — e.g.
https://atmobin.lol/@chris.pardy.family/lol.atmobin.paste/3k.... Append /raw for
text/plain (or record JSON). Any record type renders (non-pastes get a JSON view).
Record pages carry <link rel="alternate" href="at://..."> per the at-uri page-linking
convention. The only non-proxy routes are / (paste page), /login, /logout,
/oauth/callback, /client-metadata.json, and /jwks.json.
Stack #
- TanStack Start (React 19, Vite 8, nitro) — plain HTML forms, no client JS required
- TypeScript 7, oxlint
node:sqlitefor OAuth state/session + web session storage@atproto/oauth-client-node(confidential client, ES256private_key_jwt)- Fly.io, single shared instance, sqlite on a volume at
/data
Dev #
npm install
npm run dev # http://127.0.0.1:3000 — runs as an atproto loopback OAuth client
npm run lint
npm run typecheck
Use http://127.0.0.1:3000 (not localhost) so the loopback OAuth redirect matches.
Deploy #
npm run generate-key # → fly secrets set OAUTH_PRIVATE_KEY_JWK='...'
fly deploy --ha=false
Env: PUBLIC_URL, DB_PATH, OAUTH_PRIVATE_KEY_JWK, OAUTH_SCOPE
(default atproto repo:lol.atmobin.paste blob:image/* — set to
atproto transition:generic if a PDS doesn't support granular scopes yet).
Lexicon #
lexicons/lol.atmobin.paste.json, published as
com.atproto.lexicon.schema/lol.atmobin.paste in the authority repo, with
_lexicon.atmobin.lol TXT did=did:plc:dp7v5b5kejmmzzpwmqyf5wj6. DNS for the domain is
managed via at.marque.dns records (Marque nameservers).
- TypeScript 94%
- CSS 6%
- Docker <1%