From 0f931222306b97b2402d5834160d6a5a149daba8 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 8 Aug 2025 12:05:43 -0500 Subject: [PATCH] missing blobs and better upload status --- index.html | 4 ++-- src/pdsmoover.js | 55 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index 8580d5a..ea1c8d0 100644 --- a/index.html +++ b/index.html @@ -169,9 +169,9 @@
-

Please enter your PLCToken you received in an email

+

Please enter your PLC Token you received in an email

- +
diff --git a/src/pdsmoover.js b/src/pdsmoover.js index 157604a..ed55de3 100644 --- a/src/pdsmoover.js +++ b/src/pdsmoover.js @@ -35,6 +35,7 @@ class Migrator { constructor() { this.oldAgent = null; this.newAgent = null; + this.missingBlobs = []; } /** @@ -77,7 +78,7 @@ class Migrator { await oldAgent.login({identifier: oldHandle, password: password, authFactorToken: twoFactorCode}); } - safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS'); + safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); const newAgent = new AtpAgent({service: newPdsUrl}); const newHostDesc = await newAgent.com.atproto.server.describeServer(); const newHostWebDid = newHostDesc.data.did; @@ -110,7 +111,7 @@ class Migrator { await newAgent.login({ identifier: usersDid, - password, + password: password, }); safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); @@ -119,18 +120,20 @@ class Migrator { encoding: 'application/vnd.ipld.car', }); + let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); let blobCursor = undefined; let uploadedBlobs = 0; do { - safeStatusUpdate(statusUpdateHandler, `Migrating blobs, ${uploadedBlobs}/${uploadedBlobs + 100}`); - uploadedBlobs += 100; + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); + const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ did: usersDid, cursor: blobCursor, limit: 100, }); + for (const cid of listedBlobs.data.cids) { try { //TODO may move the status update here but would have it only update like every 10 @@ -141,6 +144,10 @@ class Migrator { await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { encoding: blobRes.headers['content-type'], }); + uploadedBlobs++; + if (uploadedBlobs % 10 === 0) { + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); + } } catch (error) { //TODO silently logging for now will do a missing blobs later console.error(error); @@ -149,7 +156,45 @@ class Migrator { blobCursor = listedBlobs.data.cursor; } while (blobCursor); - //TODO NEED to do some checking on the missing blobs here + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); + if (newAccountStatus.data.expectedBlobs !== uploadedBlobs) { + let totalMissingBlobs = newAccountStatus.data.expectedBlobs - uploadedBlobs; + safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); + //Probably should be shared between main blob uploader, but eh + let missingBlobCursor = undefined; + let missingUploadedBlobs = 0; + do { + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); + + const missingBlobs = await oldAgent.com.atproto.repo.listMissingBlobs({ + did: usersDid, + cursor: missingBlobCursor, + limit: 100, + }); + + for (const cid of missingBlobs.data.cids) { + try { + //TODO may move the status update here but would have it only update like every 10 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ + cid, + }); + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { + encoding: blobRes.headers['content-type'], + }); + if (uploadedBlobs % 10 === 0) { + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${uploadedBlobs}`); + } + uploadedBlobs++; + } catch (error) { + //TODO silently logging for now will do a missing blobs later + console.error(error); + this.missingBlobs.push(cid); + } + } + missingBlobCursor = missingBlobs.data.cursor; + } while (missingBlobCursor); + + } const prefs = await oldAgent.app.bsky.actor.getPreferences(); await newAgent.app.bsky.actor.putPreferences(prefs.data); -- 2.43.0 From 580db7687e1b78754605521f2ec1be1ef4fa5502 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 8 Aug 2025 12:26:57 -0500 Subject: [PATCH] new flags no logic --- index.html | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ public/style.css | 10 ++++++++ src/pdsmoover.js | 9 ++++++++ 3 files changed, 78 insertions(+) diff --git a/index.html b/index.html index ea1c8d0..ec8fd14 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,8 @@ + + PDS MOOver @@ -32,6 +34,17 @@ askForPlcToken: false, plcToken: null, plcStatus: null, + //advance + showAdvance: false, + createNewAccount: true, + migrateRepo: true, + migrateBlobs: true, + migrateMissingBlobs: true, + migratePrefs: true, + migratePlcRecord: true, + toggleAdvanceMenu() { + this.showAdvance = !this.showAdvance; + }, updateStatusHandler(status) { console.log("Status update:", status); document.getElementById("status-message").innerText = status; @@ -151,6 +164,52 @@ x-model="inviteCode" required>
+
+ +
+
+ Pick and choose which actions to run +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
-
- Pick and choose which actions to run +
+

Pick and choose which actions to run

+

Useful if a migration failed and you want to have a bit more manual control

diff --git a/public/style.css b/public/style.css index d883558..cac6fa1 100644 --- a/public/style.css +++ b/public/style.css @@ -102,6 +102,11 @@ button:focus-visible { margin-top: 30px; } +/* Left align the advance options section */ +div[x-show="showAdvance"].section { + text-align: left; +} + h1 { font-size: 3.2em; line-height: 1.1; -- 2.43.0 From de1107453495d6dcaa8da7a6fd728939be72c164 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 8 Aug 2025 13:18:52 -0500 Subject: [PATCH] Looking solid. New flags logic --- index.html | 14 +++- src/pdsmoover.js | 179 ++++++++++++++++++++++++----------------------- 2 files changed, 105 insertions(+), 88 deletions(-) diff --git a/index.html b/index.html index bdff99c..aebc6e4 100644 --- a/index.html +++ b/index.html @@ -71,7 +71,13 @@ if (!this.confirmation) { this.error = 'Please confirm that you understand the risks.' } - // Pass the form data to migrate function for future implementation + + window.Migrator.createNewAccount = this.createNewAccount; + window.Migrator.migrateRepo = this.migrateRepo; + window.Migrator.migrateBlobs = this.migrateBlobs; + window.Migrator.migrateMissingBlobs = this.migrateMissingBlobs; + window.Migrator.migratePrefs = this.migratePrefs; + window.Migrator.migratePlcRecord = this.migratePlcRecord; this.updateStatusHandler('Starting migration...'); this.showStatusMessage = true; await window.Migrator.migrate( @@ -84,7 +90,11 @@ this.updateStatusHandler, this.twoFactorCode ); - this.askForPlcToken = true; + if (this.migratePlcRecord) { + this.askForPlcToken = true; + } else { + this.updateStatusHandler('Migration of your repo is complete! But the PLC operation was not done so your old account is still the valid one.'); + } } catch (error) { console.error(error.error, error.message); if (error.error === 'AuthFactorTokenRequired') { diff --git a/src/pdsmoover.js b/src/pdsmoover.js index 1a35636..6b0c04b 100644 --- a/src/pdsmoover.js +++ b/src/pdsmoover.js @@ -37,8 +37,6 @@ class Migrator { this.newAgent = null; this.missingBlobs = []; //State for reruns - this.oldAccountStatus = null; - this.newAccountStatus = null; this.createNewAccount = true; this.migrateRepo = true; this.migrateBlobs = true; @@ -90,32 +88,33 @@ class Migrator { safeStatusUpdate(statusUpdateHandler, 'Checking that the new PDS is an actual PDS (if the url is wrong this takes a while to error out)'); const newAgent = new AtpAgent({service: newPdsUrl}); const newHostDesc = await newAgent.com.atproto.server.describeServer(); - const newHostWebDid = newHostDesc.data.did; + if (this.createNewAccount) { + const newHostWebDid = newHostDesc.data.did; - safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); + safeStatusUpdate(statusUpdateHandler, 'Creating a new account on the new PDS'); - const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ - aud: newHostWebDid, - lxm: 'com.atproto.server.createAccount', - }); - const serviceJwt = createAuthResp.data.token; + const createAuthResp = await oldAgent.com.atproto.server.getServiceAuth({ + aud: newHostWebDid, + lxm: 'com.atproto.server.createAccount', + }); + const serviceJwt = createAuthResp.data.token; - const createNewAccount = await newAgent.com.atproto.server.createAccount({ - did: usersDid, - handle: newHandle, - email: newEmail, - password: password, - inviteCode: inviteCode, - }, - { - headers: {authorization: `Bearer ${serviceJwt}`}, - encoding: 'application/json', - }); + const createNewAccount = await newAgent.com.atproto.server.createAccount({ + did: usersDid, + handle: newHandle, + email: newEmail, + password: password, + inviteCode: inviteCode, + }, + { + headers: {authorization: `Bearer ${serviceJwt}`}, + encoding: 'application/json', + }); - if (createNewAccount.data.did !== usersDid.toString()) { - throw new Error('Did not create the new account with the same did as the old account'); + if (createNewAccount.data.did !== usersDid.toString()) { + throw new Error('Did not create the new account with the same did as the old account'); + } } - safeStatusUpdate(statusUpdateHandler, 'Logging in with the new account'); await newAgent.login({ @@ -123,95 +122,103 @@ class Migrator { password: password, }); - safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); - const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); - await newAgent.com.atproto.repo.importRepo(repoRes.data, { - encoding: 'application/vnd.ipld.car', - }); + if (this.migrateRepo) { + safeStatusUpdate(statusUpdateHandler, 'Migrating your repo'); + const repoRes = await oldAgent.com.atproto.sync.getRepo({did: usersDid}); + await newAgent.com.atproto.repo.importRepo(repoRes.data, { + encoding: 'application/vnd.ipld.car', + }); + } let newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); - safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); - - let blobCursor = undefined; - let uploadedBlobs = 0; - do { - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); - const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ - did: usersDid, - cursor: blobCursor, - limit: 100, - }); + if (this.migrateBlobs) { + safeStatusUpdate(statusUpdateHandler, 'Migrating your blobs'); - for (const cid of listedBlobs.data.cids) { - try { - //TODO may move the status update here but would have it only update like every 10 - const blobRes = await oldAgent.com.atproto.sync.getBlob({ - did: usersDid, - cid, - }); - await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { - encoding: blobRes.headers['content-type'], - }); - uploadedBlobs++; - if (uploadedBlobs % 10 === 0) { - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); - } - } catch (error) { - //TODO silently logging for now will do a missing blobs later - console.error(error); - } - } - blobCursor = listedBlobs.data.cursor; - } while (blobCursor); - - newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); - if (newAccountStatus.data.expectedBlobs !== uploadedBlobs) { - let totalMissingBlobs = newAccountStatus.data.expectedBlobs - uploadedBlobs; - safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); - //Probably should be shared between main blob uploader, but eh - let missingBlobCursor = undefined; - let missingUploadedBlobs = 0; + let blobCursor = undefined; + let uploadedBlobs = 0; do { - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); - const missingBlobs = await oldAgent.com.atproto.repo.listMissingBlobs({ + const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ did: usersDid, - cursor: missingBlobCursor, + cursor: blobCursor, limit: 100, }); - for (const cid of missingBlobs.data.cids) { + for (const cid of listedBlobs.data.cids) { try { - //TODO may move the status update here but would have it only update like every 10 const blobRes = await oldAgent.com.atproto.sync.getBlob({ + did: usersDid, cid, }); await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { encoding: blobRes.headers['content-type'], }); + uploadedBlobs++; if (uploadedBlobs % 10 === 0) { - safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${uploadedBlobs}`); + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${uploadedBlobs}/${newAccountStatus.data.expectedBlobs}`); } - uploadedBlobs++; } catch (error) { - //TODO silently logging for now will do a missing blobs later console.error(error); - this.missingBlobs.push(cid); } } - missingBlobCursor = missingBlobs.data.cursor; - } while (missingBlobCursor); - + blobCursor = listedBlobs.data.cursor; + } while (blobCursor); } - const prefs = await oldAgent.app.bsky.actor.getPreferences(); - await newAgent.app.bsky.actor.putPreferences(prefs.data); - this.oldAgent = oldAgent; - this.newAgent = newAgent; - await oldAgent.com.atproto.identity.requestPlcOperationSignature(); - safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); + if (this.migrateMissingBlobs) { + newAccountStatus = await newAgent.com.atproto.server.checkAccountStatus(); + if (newAccountStatus.data.expectedBlobs !== newAccountStatus.data.importedBlobs) { + let totalMissingBlobs = newAccountStatus.data.expectedBlobs - newAccountStatus.data.importedBlobs; + safeStatusUpdate(statusUpdateHandler, 'Looks like there are some missing blobs. Going to try and upload them now.'); + //Probably should be shared between main blob uploader, but eh + let missingBlobCursor = undefined; + let missingUploadedBlobs = 0; + do { + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); + + const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs({ + cursor: missingBlobCursor, + limit: 100, + }); + for (const recordBlob of missingBlobs.data.blobs) { + try { + //TODO may move the status update here but would have it only update like every 10 + const blobRes = await oldAgent.com.atproto.sync.getBlob({ + did: usersDid, + cid: recordBlob.cid, + }); + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { + encoding: blobRes.headers['content-type'], + }); + if (missingUploadedBlobs % 10 === 0) { + safeStatusUpdate(statusUpdateHandler, `Migrating blobs: ${missingUploadedBlobs}/${totalMissingBlobs}`); + } + missingUploadedBlobs++; + } catch (error) { + //TODO silently logging for now will do a missing blobs later + console.error(error); + this.missingBlobs.push(cid); + } + } + missingBlobCursor = missingBlobs.data.cursor; + } while (missingBlobCursor); + + } + } + if (this.migratePrefs) { + const prefs = await oldAgent.app.bsky.actor.getPreferences(); + await newAgent.app.bsky.actor.putPreferences(prefs.data); + this.oldAgent = oldAgent; + this.newAgent = newAgent; + } + + if (this.migratePlcRecord) { + await oldAgent.com.atproto.identity.requestPlcOperationSignature(); + safeStatusUpdate(statusUpdateHandler, 'Please check your email for a PLC token'); + } } async signPlcOperation(token) { -- 2.43.0 From cd0df485cb838f73fcbcf09e611c86f93af20b77 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 8 Aug 2025 14:34:50 -0500 Subject: [PATCH] got to reboot --- index.html | 5 +-- public/info.html | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ public/style.css | 1 + 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 public/info.html diff --git a/index.html b/index.html index aebc6e4..2e1a72b 100644 --- a/index.html +++ b/index.html @@ -124,7 +124,7 @@
Cartoon milk cow
- + Idk if I trust a cow to move my atproto account to a new PDS
@@ -227,7 +227,8 @@ here) and that the creator or host of this migration tool is not liable and will not be to help you in the event something goes - wrong. + wrong. I also have read over the extended information from PDS MOOver about account + migrations.
diff --git a/public/info.html b/public/info.html new file mode 100644 index 0000000..c0839d0 --- /dev/null +++ b/public/info.html @@ -0,0 +1,90 @@ + + + + + + + + + PDS MOOver Info + + + + + +
+

PDS MOOver

+
+ Cartoon milk cow +
+
+

This page is to help you decide if you want to use PDS MOOver to move your ATProto(Bluesky) account to a new + PDS. + One way or the other. TLDR (You should still read the whole thing), at least read and follow the precautions section.

+ + +
+

Alpha

+

PDS MOOver is a no frills' PDS account migration tool. Meaning you will find few bells and whistles to + simplify the process and hopefully allow more successful account migrations for most users. The creator + or host of this tool will not be able to help you recover your account if something goes wrong. So be + advised you and your PDS admin may be on your own besides helpful answers and understand the risk you + take in doing an account movement. The tool has been tested, and it has worked well, but it is still + important to understand this. If you follow precautions section this will also greatly increase the odds of + recovering from any issues. You will still need to have some know how, but it is very possible with a + backup rotation key and backup of your account. I also recommend trying out an alt before you do your + main account so you know what to expect.

+
+ + + + +
+

Precautions

+

Before you even run PDS MOOver I STRONGLY suggest you add a rotation key and have a back up of your repo + and blobs. With both of these you are not guarantee a full recovery if something goes wrong. But it will + greatly increase your odds.

+

Rotation Key Backup

+

I recommend using atpairport.com to add a new rotation + key to your PLC. or you can follow this blog + post to do it with goat. PDS MOOver will not use this rotation key at this time, but if + something happens, it helps to have this in the meantime. +

+

Repo & Blob backup

+ +

I also recommend taking a back up of your account with boat. You + want to "Export repository" and "Export + blobs". Both just take your + current handle and download those exports to your computer. These are your posts, pictures, and videos. +

+
+ + +
+

!!!!!HELP!!!!!

+ If you're having issues with PDS MOOver first of all, I'm very sorry. I have tested this to the best of my + ability, but PDS migrations do come with risks. I would recommend getting with the owner of the PDS and + seeing where the account stands with tools like pdsls +
+
+ +
+ + + diff --git a/public/style.css b/public/style.css index cac6fa1..5e38ca3 100644 --- a/public/style.css +++ b/public/style.css @@ -17,6 +17,7 @@ a { font-weight: 500; color: #646cff; text-decoration: inherit; + text-decoration: underline; } a:hover { -- 2.43.0 From 26a64d4a0abde764992c55b6aec68b87c71ff121 Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 8 Aug 2025 15:28:21 -0500 Subject: [PATCH] info --- public/info.html | 62 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/public/info.html b/public/info.html index c0839d0..2ac1ed8 100644 --- a/public/info.html +++ b/public/info.html @@ -44,13 +44,10 @@
  1. Precautions
  2. !!!!!HELP!!!!!
  3. - -
  4. Before you begin (recommended prep)
  5. -
  6. How the migration works
  7. -
  8. Retries and partial runs
  9. -
  10. Why it can be slow
  11. -
  12. Liability and support
  13. -
  14. For developers
  15. +
  16. Why doesn't PDS MOOver have xyz?
  17. +
  18. Alright account migrated, now what?
  19. +
  20. Why is it so SLOW?
  21. +
  22. Can I check out the code anywhere?
@@ -75,12 +72,55 @@

-

!!!!!HELP!!!!!

- If you're having issues with PDS MOOver first of all, I'm very sorry. I have tested this to the best of my - ability, but PDS migrations do come with risks. I would recommend getting with the owner of the PDS and - seeing where the account stands with tools like pdsls +

If you're having issues with PDS MOOver first of all, I'm very sorry. I have tested this to the best of + my + ability, but PDS migrations do come with risks. I would recommend getting with the owner of the PDS and + seeing where the account stands with tools like pdsls.

+ +

The tool is designed to be able to be re ran IF you set the Advance Options flags.For example, lets say + if it created the account, repo is there but some blobs are missing. You can uncheck everything but + "Migrate Missing Blobs", "Migrate Prefs", and "Migrate PLC record" and it will pick up after the account + repo migration. It is odd in the fact that all the fields are required. That's just to cut down on logic + to hopefully cut down on bugs. If you don't ever see the "Please enter your PLC Token" you can just + forget about it and call it a day if it's too much. Your old account is still active and working.

+
+ +
+

Why doesn't PDS MOOver have xyz?

+

PDS MOOver was designed to pretty much be the goat account migration with a UI. Like in this post. Keeping it simple and hard fails if + anything + goes wrong + to + hopefully cover most use cases. Rule of thumb if reading the goat migration blog post makes you nervous, + you may not want to move to a new PDS with PDS MOOver till it has been battle tested.

+
+ +
+

Alright account migrated, now what?

+

Welcome to your new PDS! I recommend now re running the steps in precautions section along with keeping regular backups of your account. With + the rotation key and backups you can almost always recover even if your new PDS disappears overnight. It + may take a bit of know how, but it is very possible.

+
+ +
+

Why is it so SLOW?

+

Everything happens client side, and the blob uploads take a while. Nothing runs in parallel. Blob uploads + happen one at a time; once one is done, the next goes. This is done just to keep it as simple as + possible and to hopefully limit the chance of failures on uploads. My personal account takes about + 20-30ish mins to move with 1,700ish blobs at 800mb on a 1gig internet connection.

+
+ +
+

Alright account migrated, now what?

+

Yep! PDS MOOver is 100% open source and can find the code on tangled.sh. Also, if you're a + developer, + and you want to fork the code for a new UI. PDS MOOver's logic is all in one js file. Just take it and + its dependencies and have at it.

-- 2.43.0 From 5173a12683c2555122f88ba23b9e2851251dacec Mon Sep 17 00:00:00 2001 From: Bailey Townsend Date: Fri, 8 Aug 2025 15:46:40 -0500 Subject: [PATCH] done? --- README.md | 4 ++-- index.html | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f43f78c..9518316 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,5 @@ ![moo](./public/moo.webp) -Use at your own risk, I will not host till I have a bit more validation and retry logic in place. -AND it will still be very much so, use at your own Risk. \ No newline at end of file +A no frills client side PDS account migrator for ATProto. For more info check out +[pdsmoover.com](https://pdsmoover.com) \ No newline at end of file diff --git a/index.html b/index.html index 2e1a72b..05daa28 100644 --- a/index.html +++ b/index.html @@ -110,6 +110,7 @@ this.plcStatus = 'PLC operation signed successfully! Your account has been MOOved to the new PDS.'; // this.askForPlcToken = false; } catch (error) { + this.error = error.message; console.error(error); } } -- 2.43.0