Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
bluesky-account-migrator
Advanced tools
A CLI for migrating Bluesky accounts from one PDS to another.
A Node.js CLI for migrating Bluesky accounts from one PDS to another.
[!WARNING] This is community-maintained software. It has no affiliation with Bluesky the company. Use at your own risk.
bluesky-account-migrator
is currently in a beta state. If a migration fails, you will have to figure out how to recover on your own. See troubleshooting for more details.
# In a package
npm install bluesky-account-migrator
# Globally
npm install -g bluesky-account-migrator
Or the equivalent incantation using your package manager of choice.
Migrating your Bluesky account is a potentially destructive operation that can result in losing access to the account. This CLI files away some of the rough edges, but it's far from perfect, and can't help you recover if the migration fails (although you may be able to do so yourself, see troubleshooting).
To get a better understanding of the risks and steps involved in account migration, see Bluesky's account migration guide. The implementation of this package is based on the snippet in that guide.
did:plc
Bluesky account
A "custom" handle is a handle that is not a subdomain of the PDS URL. For example,
for a account hosted on bsky.social
, foo.bsky.social
would be a "normal" handle,
whereas foo.bar
would be a custom handle.
Bluesky's PDS implementation currently does not support creating new accounts with custom handles. However, the handle can be updated after migration. If you submit a custom handle as your new handle, the CLI will perform this update at the end of the migration, after activating the new account on the new PDS.
For further deatils, see e.g. this discussion and this issue.
If you are using the pipe
command, you will need to provide the temporary and final
handles in the passed-in credentials, for example:
{
"credentials": {
// The other credentials as normal
// ...
"newHandle": {
"temporaryHandle": "new-temp.pds.com"
"finalHandle": "foo.com",
},
}
}
The CLI has a single command migrate
, which you can run using e.g. npx
:
npx bluesky-account-migrator migrate [--mode <mode>]
The CLI has two modes.
interactive
This will interactively walk you through migrating your Bluesky account from one PDS to another. It will collect most of the necessary information upfront, such as the PDS URLs, account handles, etc., then ask if you want to start the migration:
? Perform the migration with these credentials? (Y/n)
Migrating your account requires completing an email challenge. Assuming all goes well, the migration will run until the challenge email has been sent. You will have to retrieve the confirmation token in this email and provide it to the CLI to complete the migration:
An email should have been sent to the old account's email address.
? Enter the confirmation token from the challenge email
If the challenge token is correct, the migration should complete successfully. At the end of the migration, the private recovery key will be printed to the terminal. You must save this key in a secure location, or you could lose access to your account.
pipe
This causes the CLI to read from stdin
and write to stdout
. It will only output
the results of running the migraiton to stdout
, and any errors or other logs will
be written to stderr
.
Given a file credentials.json
with the following contents:
{
"credentials": {
"oldPdsUrl": "https://bsky.social",
"newPdsUrl": "https://pds.com",
"oldHandle": "old.handle",
"oldPassword": "oldpass123",
"newHandle": { "handle": "new.pds.com" },
"newEmail": "new@email.com",
"newPassword": "newpass123",
"inviteCode": "invite-123"
}
}
The CLI can then be invoked as follows:
cat credentials.json | npx bluesky-account-migrator --mode pipe > result.json
If the credentials are correct, result.json
should look like this:
{
"state": "RequestedPlcOperation",
"credentials": {
"oldPdsUrl": "https://bsky.social",
"newPdsUrl": "https://pds.com",
"oldHandle": "old.handle",
"oldPassword": "oldpass123",
"newHandle": { "handle": "new.pds.com" },
"newEmail": "new@email.com",
"newPassword": "newpass123",
"inviteCode": "invite-123"
}
}
In this state, the migration should have dispatched a challenge email to the email associated with the account on the old PDS. Once you have retrieved the confirmation token from the email, you can complete the migration like so:
cat result.json | \
jq '. + {"confirmationToken": "<Token>"}' | \
npx bluesky-account-migrator migrate --mode pipe > \
finalResult.json
If the confirmation token is correct, finalResult.json
should look like this:
{
"state": "Finalized",
"credentials": {
"oldPdsUrl": "https://bsky.social",
"newPdsUrl": "https://pds.com",
"oldHandle": "old.handle",
"oldPassword": "oldpass123",
"newHandle": { "handle": "new.pds.com" },
"newEmail": "new@email.com",
"newPassword": "newpass123",
"inviteCode": "invite-123"
},
"confirmationToken": "<Token>",
"newPrivateKey": "<PrivateKey>"
}
[!IMPORTANT] If the migration fails the CLI will exit with a non-zero error code, but the result will still be written to
stdout
. This enables retrieving the generated private key, if any.To retrieve the migration output, you must ensure that your script handles failures appropriately. For example, you cannot naively use
set -e
in Bash, since that would prevent capturing the output on failure. Instead, capture the output and check the exit code separately:
output=$(cat result.json | npx bluesky-account-migrator migrate --mode pipe) exit_code=$? if [ $exit_code -ne 0 ]; then echo "Migration failed with exit code $exit_code" >&2 echo "Output was:" >&2 echo "$output" >&2 exit $exit_code fi echo "$output" > finalResult.json
The migration is implemented as a state machine in the form of the Migration
class.
You can run a migration programmatically as follows:
import { Migration, MigrationState } from 'bluesky-account-migrator';
const credentials = {
oldPdsUrl: 'https://bsky.social',
oldHandle: 'old.handle',
oldPassword: 'oldpass123',
inviteCode: 'invite-123',
newPdsUrl: 'https://pds.com',
newHandle: { handle: 'new.pds.com' },
newEmail: 'new@email.com',
newPassword: 'newpass123',
};
const migration = new Migration({ credentials });
let result = await migration.run();
if (result !== 'RequestedPlcOperation') {
// Something has gone extremely wrong if this happens
throw new Error('unexpected migration state');
}
// You have to get this from the challenge email and make it available
// to your program somehow
const confirmationToken = '...';
migration.confirmationToken = confirmationToken;
result = await migration.run();
if (result !== 'Finalized') {
// Again, something has gone extremely wrong if this happens
throw new Error('unexpected migration state');
}
// This is the recovery private key for the account, which must be stored
// somewhere or risk the loss of the account
storeSomewhereSafe(migration.newPrivateKey);
If you need to persist an unfinished migration, e.g. on failure or when getting
the confirmation token, you can use the serialize()
/deserialize()
methods:
const migration = new Migration({ credentials });
await migration.run();
// NOTE: This will output the user's passwords and private key in plaintext,
// if present.
const serialized = migration.serialize();
saveMigration(JSON.stringify(serialized, null, 2));
// Later
const savedMigration = loadMigration();
const migration = Migration.deserialize(JSON.parse(savedMigration));
migration.confirmationToken = confirmationToken;
await migration.run();
[!IMPORTANT] If you encounter any problems with
bluesky-account-migrator
, please file an issue.
If your migration fails, you are alone in strange territory. However, all is not lost.
While bluesky-account-migrator
is not (yet) equipped to resume partial migrations,
the error should tell you where it failed. In addition, the migration is implemented
as a state machine, and you should be able to figure out what's left to do by consulting
this file. In brief, each state maps to an "operation",
which is essentially a function wrapping a set of logically associated API calls. By
identifying the error and the remaining API calls, you can likely compose a script that
completes the rest of the migration.
com.atproto.server.checkAccountStatus
.[0.4.0]
newHandle
credentials property. See documentation
for details.FAQs
A CLI for migrating Bluesky accounts from one PDS to another.
We found that bluesky-account-migrator demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.