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

missing blobs and better upload status

+52 -7
+2 -2
index.html
··· 169 </form> 170 <div x-show="askForPlcToken" class="section"> 171 <form @submit.prevent="await signPlcOperation()"> 172 - <h2>Please enter your PLCToken you received in an email</h2> 173 <div class="form-group"> 174 - <label for="plc-token">PLCToken:</label> 175 <input type="text" id="plc-token" name="plc-token" x-model="plcToken" required> 176 </div> 177 <div x-show="error" x-text="error" class="error-message"></div>
··· 169 </form> 170 <div x-show="askForPlcToken" class="section"> 171 <form @submit.prevent="await signPlcOperation()"> 172 + <h2>Please enter your PLC Token you received in an email</h2> 173 <div class="form-group"> 174 + <label for="plc-token">PLC Token:</label> 175 <input type="text" id="plc-token" name="plc-token" x-model="plcToken" required> 176 </div> 177 <div x-show="error" x-text="error" class="error-message"></div>
+50 -5
src/pdsmoover.js
··· 35 constructor() { 36 this.oldAgent = null; 37 this.newAgent = null; 38 } 39 40 /** ··· 77 await oldAgent.login({identifier: oldHandle, password: password, authFactorToken: twoFactorCode}); 78 } 79 80 - safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS'); 81 const newAgent = new AtpAgent({service: newPdsUrl}); 82 const newHostDesc = await newAgent.com.atproto.server.describeServer(); 83 const newHostWebDid = newHostDesc.data.did; ··· 110 111 await newAgent.login({ 112 identifier: usersDid, 113 - password, 114 }); 115 116 safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); ··· 119 encoding: 'application/vnd.ipld.car', 120 }); 121 122 safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 123 124 let blobCursor = undefined; 125 let uploadedBlobs = 0; 126 do { 127 - safeStatusUpdate(statusUpdateHandler, `Migrating blobs, ${uploadedBlobs}/${uploadedBlobs + 100}`); 128 - uploadedBlobs += 100; 129 const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 130 did: usersDid, 131 cursor: blobCursor, 132 limit: 100, 133 }); 134 for (const cid of listedBlobs.data.cids) { 135 try { 136 //TODO may move the status update here but would have it only update like every 10 ··· 141 await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 142 encoding: blobRes.headers['content-type'], 143 }); 144 } catch (error) { 145 //TODO silently logging for now will do a missing blobs later 146 console.error(error); ··· 149 blobCursor = listedBlobs.data.cursor; 150 } while (blobCursor); 151 152 - //TODO NEED to do some checking on the missing blobs here 153 154 const prefs = await oldAgent.app.bsky.actor.getPreferences(); 155 await newAgent.app.bsky.actor.putPreferences(prefs.data);
··· 35 constructor() { 36 this.oldAgent = null; 37 this.newAgent = null; 38 + this.missingBlobs = []; 39 } 40 41 /** ··· 78 await oldAgent.login({identifier: oldHandle, password: password, authFactorToken: twoFactorCode}); 79 } 80 81 + safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); 82 const newAgent = new AtpAgent({service: newPdsUrl}); 83 const newHostDesc = await newAgent.com.atproto.server.describeServer(); 84 const newHostWebDid = newHostDesc.data.did; ··· 111 112 await newAgent.login({ 113 identifier: usersDid, 114 + password: password, 115 }); 116 117 safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); ··· 120 encoding: 'application/vnd.ipld.car', 121 }); 122 123 + let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 124 safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); 125 126 let blobCursor = undefined; 127 let uploadedBlobs = 0; 128 do { 129 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 130 + 131 const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ 132 did: usersDid, 133 cursor: blobCursor, 134 limit: 100, 135 }); 136 + 137 for (const cid of listedBlobs.data.cids) { 138 try { 139 //TODO may move the status update here but would have it only update like every 10 ··· 144 await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 145 encoding: blobRes.headers['content-type'], 146 }); 147 + uploadedBlobs++; 148 + if (uploadedBlobs % 10 === 0) { 149 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); 150 + } 151 } catch (error) { 152 //TODO silently logging for now will do a missing blobs later 153 console.error(error); ··· 156 blobCursor = listedBlobs.data.cursor; 157 } while (blobCursor); 158 159 + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); 160 + if (newAccountStatus.data.expectedBlobs !== uploadedBlobs) { 161 + let totalMissingBlobs = newAccountStatus.data.expectedBlobs - uploadedBlobs; 162 + safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); 163 + //Probably should be shared between main blob uploader, but eh 164 + let missingBlobCursor = undefined; 165 + let missingUploadedBlobs = 0; 166 + do { 167 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); 168 + 169 + const missingBlobs = await oldAgent.com.atproto.repo.listMissingBlobs({ 170 + did: usersDid, 171 + cursor: missingBlobCursor, 172 + limit: 100, 173 + }); 174 + 175 + for (const cid of missingBlobs.data.cids) { 176 + try { 177 + //TODO may move the status update here but would have it only update like every 10 178 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ 179 + cid, 180 + }); 181 + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { 182 + encoding: blobRes.headers['content-type'], 183 + }); 184 + if (uploadedBlobs % 10 === 0) { 185 + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${uploadedBlobs}`); 186 + } 187 + uploadedBlobs++; 188 + } catch (error) { 189 + //TODO silently logging for now will do a missing blobs later 190 + console.error(error); 191 + this.missingBlobs.push(cid); 192 + } 193 + } 194 + missingBlobCursor = missingBlobs.data.cursor; 195 + } while (missingBlobCursor); 196 + 197 + } 198 199 const prefs = await oldAgent.app.bsky.actor.getPreferences(); 200 await newAgent.app.bsky.actor.putPreferences(prefs.data);