Last active
October 20, 2024 19:29
-
-
Save haileyok/67bede50387c3dc57448e716c0a9be6e to your computer and use it in GitHub Desktop.
Migrate PDS script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AtpAgent from '@atproto/api' | |
import { Secp256k1Keypair } from '@atproto/crypto' | |
import * as ui8 from 'uint8arrays' | |
const OLD_PDS_URL = 'https://bsky.social' | |
const NEW_PDS_URL = 'https://pds.haileyok.com' | |
const CURRENT_HANDLE = 'haileyok.com' | |
const CURRENT_PASSWORD = '' | |
const NEW_HANDLE = 'newphone.pds.haileyok.com' | |
const NEW_ACCOUNT_EMAIL = '' | |
const NEW_ACCOUNT_PASSWORD = '' | |
const NEW_PDS_INVITE_CODE = '' | |
const TOKEN = '' // Use `getEmail` first, and set this to the token that you receive. | |
// You probably need to bump your blob size if you're a real poster. | |
// Add to /pds/pds.env | |
// PDS_BLOB_UPLOAD_LIMIT=1000000000 | |
// Save the private key that's output below. If you trust your PDS opsec more, add the new key | |
// to the end (push). | |
// If you trust your own password manager more, put it in front [newKey, ...keys] | |
const getEmail = async () => { | |
const oldAgent = new AtpAgent.AtpAgent({ service: OLD_PDS_URL }) | |
await oldAgent.login({ | |
identifier: CURRENT_HANDLE, | |
password: CURRENT_PASSWORD, | |
}) | |
await oldAgent.com.atproto.identity.requestPlcOperationSignature() | |
} | |
const migrateAccount = async () => { | |
const oldAgent = new AtpAgent.AtpAgent({ service: OLD_PDS_URL }) | |
const newAgent = new AtpAgent.AtpAgent({ service: NEW_PDS_URL }) | |
await oldAgent.login({ | |
identifier: CURRENT_HANDLE, | |
password: CURRENT_PASSWORD, | |
}) | |
const accountDid = oldAgent.session?.did | |
if (!accountDid) { | |
throw new Error('Could not get DID for old account') | |
} | |
// Create account | |
// ------------------ | |
const describeRes = await newAgent.api.com.atproto.server.describeServer() | |
const newServerDid = describeRes.data.did | |
const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({ | |
aud: newServerDid, | |
}) | |
const serviceJwt = serviceJwtRes.data.token | |
await newAgent.api.com.atproto.server.createAccount( | |
{ | |
handle: NEW_HANDLE, | |
email: NEW_ACCOUNT_EMAIL, | |
password: NEW_ACCOUNT_PASSWORD, | |
did: accountDid, | |
inviteCode: NEW_PDS_INVITE_CODE, | |
}, | |
{ | |
headers: { authorization: `Bearer ${serviceJwt}` }, | |
encoding: 'application/json', | |
}, | |
) | |
await newAgent.login({ | |
identifier: NEW_HANDLE, | |
password: NEW_ACCOUNT_PASSWORD, | |
}) | |
// Migrate Data | |
// ------------------ | |
const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: accountDid }) | |
await newAgent.com.atproto.repo.importRepo(repoRes.data, { | |
encoding: 'application/vnd.ipld.car', | |
}) | |
let blobCursor = undefined | |
do { | |
const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ | |
did: accountDid, | |
cursor: blobCursor, | |
}) | |
for (const cid of listedBlobs.data.cids) { | |
const blobRes = await oldAgent.com.atproto.sync.getBlob({ | |
did: accountDid, | |
cid, | |
}) | |
await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { | |
encoding: blobRes.headers['content-type'], | |
}) | |
} | |
blobCursor = listedBlobs.data.cursor | |
} while (blobCursor) | |
const prefs = await oldAgent.api.app.bsky.actor.getPreferences() | |
await newAgent.api.app.bsky.actor.putPreferences(prefs.data) | |
// Migrate Identity | |
// ------------------ | |
const getDidCredentials = | |
await newAgent.com.atproto.identity.getRecommendedDidCredentials() | |
console.log(JSON.stringify(getDidCredentials)) | |
// @NOTE, this token will need to come from the email from the previous step | |
const keypair = await Secp256k1Keypair.create({ exportable: true }) | |
const privateKey = await keypair.export() | |
const rotationKey = keypair.did() | |
console.log('SAVE THIS PRIVATE KEY!!!') | |
console.log('rotation key: ', rotationKey) | |
console.log('private key: ', ui8.toString(privateKey, 'hex')) | |
if(getDidCredentials.data.rotationKeys == null) { | |
console.log("Nope") | |
return | |
} | |
getDidCredentials.data.rotationKeys = [rotationKey, ...getDidCredentials.data.rotationKeys] | |
const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({ | |
token: TOKEN, | |
...getDidCredentials.data, | |
}) | |
await newAgent.com.atproto.identity.submitPlcOperation({ | |
operation: plcOp.data.operation, | |
}) | |
// Finalize Migration | |
// ------------------ | |
await newAgent.com.atproto.server.activateAccount() | |
await oldAgent.com.atproto.server.deactivateAccount({}) | |
} | |
// STEP ONE IS HERE | |
getEmail() | |
// STEP TWO IS HERE. COMMENT OUT THE ABOVE AND UNCOMMENT THIS ONCE YOU HAVE YOUR CODE | |
// migrateAccount() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
oh yea, there's been a lot of changes to the api package since i made this...should probably update it lol