Script to migrate back to bsky.social PDS from a third-party one
migrate-back-to-bsky-social.ts
edited
1// this code sucks
2
3// don't blame me if this kills your plc identity <3
4// - kris
5
6import { ACCESS_JWT, EMAIL, HANDLE, NEW_PDS, OLD_PDS, PASSWORD, REFRESH_JWT } from "./secrets"
7import { AtpAgent } from "@atproto/api";
8import { Secp256k1Keypair } from '@atproto/crypto'
9import { sleepSync } from "bun";
10import { writeFileSync } from "fs";
11import * as ui8 from 'uint8arrays'
12const oldAgent = new AtpAgent({ service: OLD_PDS })
13const newAgent = new AtpAgent({ service: NEW_PDS })
14
15console.log(`logging in on old pds - ${OLD_PDS}`)
16await oldAgent.login({
17 identifier: EMAIL,
18 password: PASSWORD,
19});
20
21const accountDid = oldAgent.session?.did
22if (!accountDid) {
23 throw new Error('wtf?')
24}
25
26console.log(`logging in on new pds - ${NEW_PDS}`)
27
28newAgent.resumeSession({
29 accessJwt: ACCESS_JWT,
30 refreshJwt: REFRESH_JWT,
31 handle: HANDLE,
32 did: accountDid,
33 active: false,
34 status: "deactivated"
35})
36
37// try {
38// await newAgent.login({
39// identifier: EMAIL,
40// password: PASSWORD
41// })
42// } catch {
43// console.log(`ok nvm it needs ur email`)
44// sleepSync(1000)
45// await newAgent.login({
46// identifier: EMAIL,
47// password: PASSWORD,
48// authFactorToken: prompt("enter code:")!
49// })
50// }
51
52console.log("get repo");
53
54const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: accountDid });
55
56console.log("import repo");
57
58await newAgent.com.atproto.repo.importRepo(repoRes.data, {
59 encoding: 'application/vnd.ipld.car',
60})
61
62console.log("get missing blobs");
63
64let missingBlobs: string[] = [];
65
66console.log("list blobs new pds");
67
68await (async () => {
69 let blobCursor: string | undefined = undefined
70 do {
71 const blobs = await newAgent.com.atproto.repo.listMissingBlobs({
72 cursor: blobCursor,
73 });
74 missingBlobs = [...blobs.data.blobs.map(a=>a.cid), ...missingBlobs]
75 blobCursor = blobs.data.cursor
76 } while (blobCursor)
77})()
78
79console.log("missing blobs:", missingBlobs);
80
81for (const blob of missingBlobs) {
82 console.log("get blob", blob);
83 const blobRes = await oldAgent.com.atproto.sync.getBlob({
84 did: accountDid,
85 cid: blob,
86 })
87 console.log("upload blob", blob);
88 await newAgent.com.atproto.repo.uploadBlob(blobRes.data, {
89 encoding: blobRes.headers['content-type'],
90 })
91}
92
93console.log("sync prefs");
94
95const prefs = await oldAgent.app.bsky.actor.getPreferences()
96await newAgent.app.bsky.actor.putPreferences(prefs.data)
97
98console.log("create secp256k1 keypair")
99
100const recoveryKey = await Secp256k1Keypair.create({ exportable: true })
101const privateKeyBytes = await recoveryKey.export()
102const privateKey = ui8.toString(privateKeyBytes, 'hex')
103
104writeFileSync("DID_PLC_RECOVERY_KEY",privateKeyBytes)
105writeFileSync("DID_PLC_RECOVERY_KEY.hex",privateKey)
106
107console.log("saved the private keys, do not share!!!")
108
109const getDidCredentials = await newAgent.com.atproto.identity.getRecommendedDidCredentials()
110
111const rotationKeys = getDidCredentials.data.rotationKeys ?? []
112if (!rotationKeys) {
113 throw new Error('no rotation key, wtf???')
114}
115
116console.log("new pds's recommended did creds", rotationKeys)
117
118const credentials = {
119 ...getDidCredentials.data,
120 rotationKeys: [recoveryKey.did(), ...rotationKeys],
121}
122
123console.log("new creds", credentials)
124
125console.log("requesting key")
126
127await oldAgent.com.atproto.identity.requestPlcOperationSignature()
128
129const TOKEN = prompt("enter key in email:")
130
131console.log("signing plc op")
132
133const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({
134 token: TOKEN!,
135 ...credentials,
136})
137
138console.log(
139 `❗ Your private recovery key is: ${privateKey}. Please store this in a secure location! ❗`,
140)
141
142console.log("Signed PLC operation",plcOp.data)
143console.log("!!! Are you 100% sure you want to migrate your new PLC identity?")
144
145const yN = prompt("Migrate PLC identity? [yN]");
146if (!yN || !yN.toLowerCase().startsWith("y")) {
147 process.exit(1);
148}
149
150const yN2 = prompt("Are you sure? [yN]");
151if (!yN2 || !yN2.toLowerCase().startsWith("y")) {
152 process.exit(1);
153}
154
155const yN3 = prompt("THERE IS NO GOING BACK AFTER THIS. Are you really really sure? [yN]");
156if (!yN3 || !yN3.toLowerCase().startsWith("y")) {
157 process.exit(1);
158}
159
160
161console.log("You have 5 seconds to CTRL+C to abort!")
162sleepSync(1000)
163console.log("You have 4 seconds to CTRL+C to abort!")
164sleepSync(1000)
165console.log("You have 3 seconds to CTRL+C to abort!")
166sleepSync(1000)
167console.log("You have 2 seconds to CTRL+C to abort!")
168sleepSync(1000)
169console.log("You have 1 second to CTRL+C to abort!")
170sleepSync(1000)
171
172const yN4 = prompt("Final chance, are you actually sure? [yN]");
173if (!yN4 || !yN4.toLowerCase().startsWith("y")) {
174 process.exit(1);
175}
176
177console.log("Welp, no going back now!")
178console.log("submitting plc operation")
179
180await newAgent.com.atproto.identity.submitPlcOperation({
181 operation: plcOp.data.operation,
182})
183
184console.log("activate new pds acc")
185await newAgent.com.atproto.server.activateAccount()
186console.log("deactivate new old acc")
187await oldAgent.com.atproto.server.deactivateAccount({})
188
189console.log("successfully migrated pds! DO NOT SHARE THE SECRET KEYS, YOU HAVE BEEN WARNED.")