···6[dependencies]
7atrium-api = { version = "0.25.0", default-features = false, features = ["agent", "bluesky"] }
8atrium-common = "0.1.0"
09atrium-identity = { version = "0.1.1", features = ["doh-handle-resolver"] }
10atrium-xrpc-client = "0.5.11"
011hickory-resolver = "0.25.1"
012tokio = { version = "1.44.0", features = ["macros", "rt-multi-thread"]}
···6[dependencies]
7atrium-api = { version = "0.25.0", default-features = false, features = ["agent", "bluesky"] }
8atrium-common = "0.1.0"
9+atrium-crypto = "0.1.2"
10atrium-identity = { version = "0.1.1", features = ["doh-handle-resolver"] }
11atrium-xrpc-client = "0.5.11"
12+hex = "0.4.3"
13hickory-resolver = "0.25.1"
14+rand = "0.8.5"
15tokio = { version = "1.44.0", features = ["macros", "rt-multi-thread"]}
+142-3
src/main.rs
···5 },
6 app::bsky::actor::{get_preferences, put_preferences},
7 com::atproto::{
8- server::{create_account, get_service_auth},
09 sync::{get_blob, get_repo, list_blobs},
10 },
11 types::string::{Did, Handle, Nsid},
12};
13use atrium_common::resolver::Resolver;
014use atrium_identity::{
15 did::{CommonDidResolver, CommonDidResolverConfig},
16 handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
···327 match new_agent.api.com.atproto.repo.upload_blob(blob).await {
328 Ok(_) => {
329 println!("Blob with CID {:?} migrated", cid)
330- },
331 Err(err) => {
332 println!("com.atproto.repo.uploadBlob at new PDS failed due to error: {err}");
333 return;
···385 match new_agent.api.com.atproto.repo.upload_blob(blob).await {
386 Ok(_) => {
387 println!("Blob with CID {:?} migrated", cid)
388- },
389 Err(err) => {
390 println!("com.atproto.repo.uploadBlob at new PDS failed due to error: {err}");
391 return;
···431 }
432 }
433 println!("Preferences successfully migrated!");
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000434}
···5 },
6 app::bsky::actor::{get_preferences, put_preferences},
7 com::atproto::{
8+ identity::sign_plc_operation,
9+ server::{create_account, deactivate_account, get_service_auth},
10 sync::{get_blob, get_repo, list_blobs},
11 },
12 types::string::{Did, Handle, Nsid},
13};
14use atrium_common::resolver::Resolver;
15+use atrium_crypto::keypair::{Did as _, Export, Secp256k1Keypair};
16use atrium_identity::{
17 did::{CommonDidResolver, CommonDidResolverConfig},
18 handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
···329 match new_agent.api.com.atproto.repo.upload_blob(blob).await {
330 Ok(_) => {
331 println!("Blob with CID {:?} migrated", cid)
332+ }
333 Err(err) => {
334 println!("com.atproto.repo.uploadBlob at new PDS failed due to error: {err}");
335 return;
···387 match new_agent.api.com.atproto.repo.upload_blob(blob).await {
388 Ok(_) => {
389 println!("Blob with CID {:?} migrated", cid)
390+ }
391 Err(err) => {
392 println!("com.atproto.repo.uploadBlob at new PDS failed due to error: {err}");
393 return;
···433 }
434 }
435 println!("Preferences successfully migrated!");
436+437+ // Update identity
438+ println!("Migrating you identity (DID document) ...");
439+440+ let pds_credentials = match new_agent
441+ .api
442+ .com
443+ .atproto
444+ .identity
445+ .get_recommended_did_credentials()
446+ .await
447+ {
448+ Ok(response) => response,
449+ Err(err) => {
450+ println!("com.atproto.identity.getRecommendedDidCredentials at new PDS failed due to error: {err}");
451+ return;
452+ }
453+ };
454+455+ match Did::new(identity.did.clone()).unwrap().method() {
456+ "plc" => {
457+ println!(
458+ "did:plc detected! Creating a recovery key and updating your DID document ..."
459+ );
460+ let recovery_keypair = Secp256k1Keypair::create(&mut rand::thread_rng());
461+ let private_key = hex::encode(recovery_keypair.export());
462+ let mut recovery_keys = vec![recovery_keypair.did()];
463+ if let Some(keys) = pds_credentials.rotation_keys.clone() {
464+ recovery_keys.extend(keys);
465+ }
466+467+ println!("PLC operations are potentially destructive therefore you will need to complete an email challenge with your current PDS");
468+ if let Err(err) = current_agent
469+ .api
470+ .com
471+ .atproto
472+ .identity
473+ .request_plc_operation_signature()
474+ .await
475+ {
476+ println!("com.atproto.identity.requestPlcOperationSignature at current PDS failed due to error: {err}")
477+ };
478+ let challenge_token = match readln(Some(
479+ "Challenge email sent. Please provide the token you where sent over email here",
480+ )) {
481+ Ok(token) => token,
482+ Err(err) => {
483+ println!("Could not read token due to error: {err}");
484+ return;
485+ }
486+ };
487+ println!("Your private recovery key is {private_key}. Please store this in a secure location!!");
488+ if let Err(err) = readln(Some("Press enter once you've saved the key securely")) {
489+ println!("Could not handle enter due to error: {err}");
490+ return;
491+ }
492+493+ match current_agent
494+ .api
495+ .com
496+ .atproto
497+ .identity
498+ .sign_plc_operation(
499+ sign_plc_operation::InputData {
500+ also_known_as: pds_credentials.also_known_as.clone(),
501+ rotation_keys: Some(recovery_keys),
502+ services: pds_credentials.services.clone(),
503+ token: Some(challenge_token.to_string()),
504+ verification_methods: pds_credentials.verification_methods.clone(),
505+ }
506+ .into(),
507+ )
508+ .await
509+ {
510+ Ok(response) => response,
511+ Err(err) => {
512+ println!("com.atproto.identity.signPlcOperation at current PDS failed due to error: {err}");
513+ return;
514+ }
515+ };
516+ println!("DID document successfully updated!");
517+ }
518+ "web" => {
519+ let did = identity.did;
520+ println!("did:web detected! Please manually update your DID document to match these values: {pds_credentials:#?}");
521+ if let Err(err) = readln(Some("Press enter once you've updated your DID document")) {
522+ println!("Could not handle enter due to error: {err}");
523+ return;
524+ }
525+ let mut valid_document = match identity_resolver.resolve(did.as_str()).await {
526+ Ok(response) => response.pds == new_pds_url.to_string(),
527+ Err(err) => {
528+ println!("Couldn't resolve DID {did} due to error: {err}");
529+ return;
530+ }
531+ };
532+533+ while !valid_document {
534+ println!("DID document not updated or updated incorretly! Needed PDS configuration: {new_pds_url}");
535+ if let Err(err) = readln(Some("Press enter once you've updated your DID document"))
536+ {
537+ println!("Could not handle enter due to error: {err}");
538+ return;
539+ }
540+ valid_document = match identity_resolver.resolve(did.as_str()).await {
541+ Ok(response) => response.pds == new_pds_url.to_string(),
542+ Err(err) => {
543+ println!("Couldn't resolve DID {did} due to error: {err}");
544+ return;
545+ }
546+ };
547+ }
548+ }
549+ _ => {
550+ println!("Unknown and invalid DID method found. This should not be possible!");
551+ return;
552+ }
553+ }
554+ println!("Identity migrated successfully!");
555+556+ // Finalise migration
557+ if let Err(err) = new_agent.api.com.atproto.server.activate_account().await {
558+ println!("com.atproto.server.activateAccount at new PDS failed due to error: {err}")
559+ };
560+ if let Err(err) = current_agent
561+ .api
562+ .com
563+ .atproto
564+ .server
565+ .deactivate_account(deactivate_account::InputData { delete_after: None }.into())
566+ .await
567+ {
568+ println!("com.atproto.server.activateAccount at current PDS failed due to error: {err}")
569+ };
570+571+ println!("The account migration was successful!");
572+ println!("The account on your old PDS has been deactivated. Please make sure everything works before fully deleting it in case you need to go back");
573}