`smirk-admin migrate-legacy --source-url <pg> [--commit] [--limit N]` copies
user identity from a legacy v0.2.x backend database into this v0.3 DB, so users
keep their handle (and NIP-05 name@domain) when they move to a v0.3 client.
- Identity-only, joined on the derivation-independent seed_fingerprint; also
carries pubkey_hash (v3 wallets reclaim their row with no rotation step),
username, nostr_pubkey, wallet_birthday and chain restore heights. Socials/
tips/keys/wallets are intentionally not carried (no v0.3 home, or re-registered
by the client on first unlock).
- pubkey_hash + seed_fingerprint are peppered at rest, so the importer builds its
target with the real SEED_FINGERPRINT_PEPPER (run with the v0.3 .env).
- Idempotent (skips users already present by either peppered identity column),
dry-run unless --commit, best-effort per row: a transient error or a concurrent
native onboarding is reconciled, never aborting the pass. Reserved/taken/
malformed usernames and already-linked npubs are dropped; the user still lands.
- Exports validate_username so the importer applies the identical format +
reserved-name rules the HTTP handler does.
- docs/operations/LEGACY_MIGRATION.md + scripts/test-legacy-migration.sh, a
self-contained E2E that seeds alice/bob/carol plus edge cases and asserts
import, peppering, collision handling, and idempotency.
Reviewed adversarially (correctness / security / operational) before commit.