Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations. pdsmoover.com
pds atproto migrations moo cow

Looking solid. New flags logic

authored by baileytownsend.dev and committed by

Tangled d9bfde62 2626c957

+105 -88
+12 -2
index.html
··· 71 71 if (!this.confirmation) { 72 72 this.error = 'Please confirm that you understand the risks.' 73 73 } 74 - // Pass the form data to migrate function for future implementation 74 + 75 + window.Migrator.createNewAccount = this.createNewAccount; 76 + window.Migrator.migrateRepo = this.migrateRepo; 77 + window.Migrator.migrateBlobs = this.migrateBlobs; 78 + window.Migrator.migrateMissingBlobs = this.migrateMissingBlobs; 79 + window.Migrator.migratePrefs = this.migratePrefs; 80 + window.Migrator.migratePlcRecord = this.migratePlcRecord; 75 81 this.updateStatusHandler('Starting migration...'); 76 82 this.showStatusMessage = true; 77 83 await window.Migrator.migrate( ··· 84 90 this.updateStatusHandler, 85 91 this.twoFactorCode 86 92 ); 87 - this.askForPlcToken = true; 93 + if (this.migratePlcRecord) { 94 + this.askForPlcToken = true; 95 + } else { 96 + this.updateStatusHandler('Migration of your repo is complete! But the PLC operation was not done so your old account is still the valid one.'); 97 + } 88 98 } catch (error) { 89 99 console.error(error.error, error.message); 90 100 if (error.error === 'AuthFactorTokenRequired') {
+93 -86
src/pdsmoover.js
··· 37 37 this.newAgent = null; 38 38 this.missingBlobs = []; 39 39 //State for reruns 40 - this.oldAccountStatus = null; 41 - this.newAccountStatus = null; 42 40 this.createNewAccount = true; 43 41 this.migrateRepo = true; 44 42 this.migrateBlobs = true; ··· 90 88 safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); 91 89 const newAgent = new AtpAgent({service: newPdsUrl}); 92 90 const newHostDesc = await newAgent.com.atproto.server.describeServer(); 93 - const newHostWebDid = newHostDesc.data.did; 91 + if (this.createNewAccount) { 92 + const newHostWebDid = newHostDesc.data.did; 94 93 95 - safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); 94 + safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); 96 95 97 - const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ 98 - aud: newHostWebDid, 99 - lxm: 'com.atproto.server.createAccount', 100 - }); 101 - const serviceJwt = createAuthResp.data.token; 96 + const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ 97 + aud: newHostWebDid, 98 + lxm: 'com.atproto.server.createAccount', 99 + }); 100 + const serviceJwt = createAuthResp.data.token; 102 101 103 - const createNewAccount = await newAgent.com.atproto.server.createAccount({ 104 - did: usersDid, 105 - handle: newHandle, 106 - email: newEmail, 107 - password: password, 108 - inviteCode: inviteCode, 109 - }, 110 - { 111 - headers: {authorization: `Bearer ${serviceJwt}`}, 112 - encoding: 'application/json', 113 - }); 102 + const createNewAccount = await newAgent.com.atproto.server.createAccount({ 103 + did: usersDid, 104 + handle: newHandle, 105 + email: newEmail, 106 + password: password, 107 + inviteCode: inviteCode, 108 + }, 109 + { 110 + headers: {authorization: `Bearer ${serviceJwt}`}, 111 + encoding: 'application/json', 112 + }); 114 113 115 - if (createNewAccount.data.did !== usersDid.toString()) { 116 - throw new Error('Did not create the new account with the same did as the old account'); 114 + if (createNewAccount.data.did !== usersDid.toString()) { 115 + throw new Error('Did not create the new account with the same did as the old account'); 116 + } 117 117 } 118 - 119 118 safeStatusUpdate(statusUpdateHandler, 'Logging in with the new account'); 120 119 121 120 await newAgent.login({ ··· 123 122 password: password, 124 123 }); 125 124 126 - safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); 127 - const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); 128 - await newAgent.com.atproto.repo.importRepo(repoRes.data, { 129 - encoding: 'application/vnd.ipld.car', 130 - }); 125 + if (this.migrateRepo) { 126 + safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); 127 + const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); 128 + await newAgent.com.atproto.repo.importRepo(repoRes.data, { 129 + encoding: 'application/vnd.ipld.car', 130 + }); 131 + } 131 132 132 133 let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 133 - safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 134 134 135 - let blobCursor = undefined; 136 - let uploadedBlobs = 0; 137 - do { 138 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 135 + if (this.migrateBlobs) { 136 + safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 139 137 140 - const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 141 - did: usersDid, 142 - cursor: blobCursor, 143 - limit: 100, 144 - }); 145 - 146 - for (const cid of listedBlobs.data.cids) { 147 - try { 148 - //TODO may move the status update here but would have it only update like every 10 149 - const blobRes = await oldAgent.com.atproto.sync.getBlob({ 150 - did: usersDid, 151 - cid, 152 - }); 153 - await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 154 - encoding: blobRes.headers['content-type'], 155 - }); 156 - uploadedBlobs++; 157 - if (uploadedBlobs % 10 === 0) { 158 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 159 - } 160 - } catch (error) { 161 - //TODO silently logging for now will do a missing blobs later 162 - console.error(error); 163 - } 164 - } 165 - blobCursor = listedBlobs.data.cursor; 166 - } while (blobCursor); 167 - 168 - newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 169 - if (newAccountStatus.data.expectedBlobs !== uploadedBlobs) { 170 - let totalMissingBlobs = newAccountStatus.data.expectedBlobs - uploadedBlobs; 171 - safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 172 - //Probably should be shared between main blob uploader, but eh 173 - let missingBlobCursor = undefined; 174 - let missingUploadedBlobs = 0; 138 + let blobCursor = undefined; 139 + let uploadedBlobs = 0; 175 140 do { 176 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 141 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 177 142 178 - const missingBlobs = await oldAgent.com.atproto.repo.listMissingBlobs({ 143 + const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 179 144 did: usersDid, 180 - cursor: missingBlobCursor, 145 + cursor: blobCursor, 181 146 limit: 100, 182 147 }); 183 148 184 - for (const cid of missingBlobs.data.cids) { 149 + for (const cid of listedBlobs.data.cids) { 185 150 try { 186 - //TODO may move the status update here but would have it only update like every 10 187 151 const blobRes = await oldAgent.com.atproto.sync.getBlob({ 152 + did: usersDid, 188 153 cid, 189 154 }); 190 155 await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 191 156 encoding: blobRes.headers['content-type'], 192 157 }); 158 + uploadedBlobs++; 193 159 if (uploadedBlobs % 10 === 0) { 194 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${uploadedBlobs}`); 160 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 195 161 } 196 - uploadedBlobs++; 197 162 } catch (error) { 198 - //TODO silently logging for now will do a missing blobs later 199 163 console.error(error); 200 - this.missingBlobs.push(cid); 201 164 } 202 165 } 203 - missingBlobCursor = missingBlobs.data.cursor; 204 - } while (missingBlobCursor); 166 + blobCursor = listedBlobs.data.cursor; 167 + } while (blobCursor); 168 + } 169 + 170 + if (this.migrateMissingBlobs) { 171 + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 172 + if (newAccountStatus.data.expectedBlobs !== newAccountStatus.data.importedBlobs) { 173 + let totalMissingBlobs = newAccountStatus.data.expectedBlobs - newAccountStatus.data.importedBlobs; 174 + safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 175 + //Probably should be shared between main blob uploader, but eh 176 + let missingBlobCursor = undefined; 177 + let missingUploadedBlobs = 0; 178 + do { 179 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 180 + 181 + const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs({ 182 + cursor: missingBlobCursor, 183 + limit: 100, 184 + }); 205 185 206 - } 186 + for (const recordBlob of missingBlobs.data.blobs) { 187 + try { 188 + //TODO may move the status update here but would have it only update like every 10 189 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ 190 + did: usersDid, 191 + cid: recordBlob.cid, 192 + }); 193 + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 194 + encoding: blobRes.headers['content-type'], 195 + }); 196 + if (missingUploadedBlobs % 10 === 0) { 197 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 198 + } 199 + missingUploadedBlobs++; 200 + } catch (error) { 201 + //TODO silently logging for now will do a missing blobs later 202 + console.error(error); 203 + this.missingBlobs.push(cid); 204 + } 205 + } 206 + missingBlobCursor = missingBlobs.data.cursor; 207 + } while (missingBlobCursor); 207 208 208 - const prefs = await oldAgent.app.bsky.actor.getPreferences(); 209 - await newAgent.app.bsky.actor.putPreferences(prefs.data); 210 - this.oldAgent = oldAgent; 211 - this.newAgent = newAgent; 212 - await oldAgent.com.atproto.identity.requestPlcOperationSignature(); 213 - safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); 209 + } 210 + } 211 + if (this.migratePrefs) { 212 + const prefs = await oldAgent.app.bsky.actor.getPreferences(); 213 + await newAgent.app.bsky.actor.putPreferences(prefs.data); 214 + this.oldAgent = oldAgent; 215 + this.newAgent = newAgent; 216 + } 214 217 218 + if (this.migratePlcRecord) { 219 + await oldAgent.com.atproto.identity.requestPlcOperationSignature(); 220 + safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); 221 + } 215 222 } 216 223 217 224 async signPlcOperation(token) {