Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations. pdsmoover.com
pds atproto migrations moo cow
pds-moover web-ui README.md
7.1 kB
Markdown
at main

PDS MOOver web ui #

A SvelteKit app that moves an Atmosphere account from one PDS to another. The user's current account is signed in through their own PDS with atproto OAuth and granular permission scopes instead of a password. Everything runs in the browser; the server only serves pages and the OAuth client metadata. The migration logic itself lives in @pds-moover/moover.

What it does #

  • /moover – migrate an account (create the account on the new PDS with a service-auth token, import the repo, copy blobs and preferences, sign the PLC operation, activate, deactivate the old account). The user picks a password for the new account. Deactivating the old account needs the account:status?action=manage OAuth permission; PDSs that don't allow it get the UI explaining how to do it by hand.
  • /missing-blobs – copy blobs a previous migration missed from the old PDS to the current one.
  • /rotation-key – add a rotation key the user controls to their did:plc identity.

OAuth scopes #

Each page requests only what it needs; /oauth-client-metadata.json advertises the union.

Page Scope
Migration atproto account:email rpc:com.atproto.server.createAccount?aud=* rpc:app.bsky.actor.getPreferences?aud=did:web:api.bsky.app#bsky_appview identity:* account:status?action=manage
Missing blobs atproto blob:*/*
Rotation key atproto identity:*
Backups atproto identity:* rpc:com.pdsmoover.backup.getRepoStatus?aud=did:web:xrpc.pdsmoover.com#repo_backup rpc:com.pdsmoover.backup.signUp?aud=did:web:xrpc.pdsmoover.com#repo_backup rpc:com.pdsmoover.backup.requestBackup?aud=did:web:xrpc.pdsmoover.com#repo_backup rpc:com.pdsmoover.backup.removeRepo?aud=did:web:xrpc.pdsmoover.com#repo_backup
Admin atproto rpc:com.pdsmoover.pds.checkAdminAuth?aud=did:web:xrpc.pdsmoover.com#repo_backup rpc:com.pdsmoover.pds.listPds?aud=did:web:xrpc.pdsmoover.com#repo_backup rpc:com.pdsmoover.pds.searchPds?aud=did:web:xrpc.pdsmoover.com#repo_backup rpc:com.pdsmoover.pds.updatePds?aud=did:web:xrpc.pdsmoover.com#repo_backup

See src/lib/oauth/scopes.ts for what each permission unlocks.

Development #

This is one member of the repo's pnpm workspace, so install from the repo root. The commands below work from the root (all packages) or from this directory (just the web ui).

pnpm install
pnpm dev            # http://127.0.0.1:5173 (must be 127.0.0.1, see below)
pnpm check          # svelte-check
pnpm lint
pnpm test           # vitest
pnpm test:e2e       # Playwright, see below

Locally the app uses an atproto loopback OAuth client: the redirect URI and scope are encoded in the client_id, and authorization servers only accept 127.0.0.1 as the redirect host. Vite is pinned to 127.0.0.1:5173 for that reason; opening localhost will not sign in.

End-to-end tests #

e2e/ runs the app in Chromium against @pds-moover/mock-atmosphere: two mock PDSs (each its own OAuth server), plc.directory, slingshot and the PDS MOOver directory API, all in memory. Specs sign in through the real OAuth client; nothing reaches the internet.

pnpm --filter web-ui exec playwright install chromium   # once
pnpm test:e2e                                           # from the repo root: builds the mock, then runs the specs

The suite starts its own dev server on 127.0.0.1:5183, so it does not disturb one you have running on 5173. E2E_DEBUG=1 logs the browser console and every proxied request.

Deploying (Docker) #

The ui ships as a Docker image built from the repo root (it needs the workspace packages):

just test-build-ui   # builds moover_ui:test for this machine, nothing is pushed
just run-test-ui     # runs it on http://127.0.0.1:3001, where OAuth sign-in works without a domain
just release         # multi-arch build and push of every image, the ui included

The image runs node build (@sveltejs/adapter-node) and reads its settings at start-up, so one image works for any domain:

Variable Value
PORT port to listen on (the composes use 3001)
PUBLIC_APP_URL https://<your domain> – builds the client_id and redirect URI
ORIGIN same URL, required by @sveltejs/adapter-node
PUBLIC_XRPC_BASE host (no scheme) of the PDS MOOver XRPC API for backups/restore/stats; defaults to xrpc.pdsmoover.com
PUBLIC_HUBBLE_URL base URL of the Hubble mirror used as an alternate restore source; defaults to the test instance

Destination PDSs are listed by the public com.pdsmoover.pds.listPds/searchPds endpoints on the same PUBLIC_XRPC_BASE API.

After deploying, https://<your domain>/oauth-client-metadata.json must be reachable from the internet: that URL is the OAuth client_id, and every PDS a user signs in through fetches it. Without PUBLIC_APP_URL the ui falls back to a loopback OAuth client, which only works when it is opened on http://127.0.0.1:<port>.

License #

MIT, see the LICENSE.md at the repo root.