Migrating from lb-phone
Import lb-phone's phone_* tables with one console command.
iFold imports lb-phone's phone_* tables with one console command. The source tables are never modified, every imported row is recorded so a rerun skips it, and the mapping files are open Lua you can adjust before running.
Before you start
- Back up the database.
- Install iFold next to lb-phone's tables (same database). Start the server once so the
ifold_*schema exists. - Keep the item metadata keys at their lb-phone defaults in
config/config.item.luaso existing phone items open their imported phones. - Stop lb-phone; the numbers become iFold's.
The command
From the server console (txAdmin console or the terminal), with nobody holding a phone:
ifold:migrate lb-phone --dry-run
ifold:migrate lb-phone| Flag | Effect |
|---|---|
--dry-run | Reads and maps every row, writes nothing, prints the counts |
--only=phones,messages | Runs only these groups (their dependencies are included) |
--since=YYYY-MM-DD | Skips rows older than the date in tables that have a timestamp |
--reset-passwords | Replaces every imported account password with a hash of the owner's phone number instead of importing the lb-phone value |
--limit=N | Reads at most N rows per table (for a staged test) |
--force | Runs while players have phones open |
The command refuses to run before iFold is ready or while phones are open (without --force), checks which phone_* tables exist, and prints a line per table as it pages through it. The summary lists, per group, the rows read, inserted, skipped and failed, followed by the first twenty reasons.
Rerunning is safe: ifold_migration_log records every imported source row, so a second run only imports what is new. Delete rows from that table to force a re-import of a group after fixing a mapper.
What is imported
| Group | lb-phone tables | iFold tables |
|---|---|---|
| phones | phone_phones, phone_last_phone | ifold_phones, ifold_numbers, ifold_last_phone, default ifold_app_installs |
| contacts | phone_phone_contacts, phone_phone_blocked_numbers | ifold_contacts, ifold_blocked |
| messages | phone_message_channels, _members, _messages, _reactions | ifold_conversations, _members, ifold_dm_index, ifold_messages, _reactions |
| calls | phone_phone_calls | ifold_calls |
| voicemail | phone_phone_voicemail | ifold_voicemails (imported as heard) |
| media | phone_photos, phone_photo_albums, _album_photos, _album_members | ifold_media, ifold_albums, _media, _members |
| notes, alarms, notifications | phone_notes, phone_clock_alarms, phone_notifications | ifold_notes, ifold_alarms, ifold_notifications |
| accounts | phone_twitter_accounts, phone_instagram_accounts, phone_tiktok_accounts, phone_darkchat_accounts, phone_mail_accounts, phone_logged_in_accounts | ifold_accounts, ifold_account_logins |
| birdy | phone_twitter_tweets, _retweets, _likes, _follows, _follow_requests, _messages, _notifications | ifold_social_posts, _hashtags, _likes, _follows, _threads, _dms, _notifications |
| instapic | phone_instagram_posts, _stories, _stories_views, _comments, _likes, _follows, _follow_requests, _messages, _notifications | the social tables plus ifold_social_comments, _views |
| trendy | phone_tiktok_videos, _likes, _views, _saves, _comments, _comments_likes, _follows, _channels, _messages, _notifications, _pinned_videos | the social tables plus ifold_social_saves |
| spark | phone_tinder_accounts, _swipes, _matches, _messages | ifold_spark_profiles, _swipes, _matches, _messages |
phone_mail_messages, phone_mail_deleted | ifold_mail, ifold_mail_deleted | |
| darkchat | phone_darkchat_channels, _members, _messages | ifold_darkchat_channels, _members, _messages |
| music | phone_music_playlists, _saved_playlists, _songs | ifold_music_playlists, _saved, _playlist_songs |
| voicememos | phone_voice_memos_recordings | ifold_voice_memos |
| wallet | phone_wallet_transactions | ifold_wallet_transactions (kind external) |
| crypto | phone_crypto, phone_crypto_coins | ifold_crypto_holdings, ifold_crypto_coins |
| marketplace, pages | phone_marketplace_posts, phone_yellow_pages_posts | ifold_marketplace_listings, ifold_pages_ads |
| services | phone_services_channels, _messages | ifold_service_threads, _messages |
| maps | phone_maps_locations | ifold_maps_places |
Not imported: phone_backups, phone_twitter_hashtags and phone_twitter_promoted (hashtags are re-extracted from the posts; promotions expire), phone_tiktok_unread_messages and the denormalised counters (recomputed after each group).
What changes
- Numbers are stored digits-only. Rows whose number does not normalise, or whose phone does not exist, are skipped with a reason.
- Ids are new. Every child row (likes, replies, reactions, album entries, members) resolves its parent through the migration log; a child whose parent was not imported is retried once at the end of its table and then skipped.
- Passwords are imported as
legacy$<value>and verified with the lb-phone value (bcrypt through a bcrypt resource when one is started, otherwise a plain compare) on the first login, which re-hashes them. With--reset-passwordsevery account password becomes a hash of the owner's phone number; tell your players. - Media URLs are kept as they are; the upload provider is not involved.
- Stories get an expiry of
Config.Social.instapic.storyHoursafter their timestamp; already expired ones are swept by the running server. - Crush (
spark) profiles get a fresh public id;is_maleand the interest flags becomegenderandinterested. - Alarms repeat every day (lb-phone alarms have no weekday mask).
- Wallet history rows import as kind
externalwith the company as the title. - Music playlist entries are kept only for song ids that exist in
Config.Music.songs. - Services messages are split into customer and company sides by the sender; lb-phone's location token is decoded into coordinates.
- Usernames and channel names are lower-cased.
Adjusting a mapping
The mappers live in ifold/migrate/lb-phone/<group>.lua and are escrow-ignored. Each one registers a group:
Migrate.define('notes', {
order = 30,
depends = { 'phones' },
sources = {
{
table = 'phone_notes',
idColumn = 'id',
since = 'timestamp',
map = function(row, ctx)
local number = ctx.number(row.phone_number)
if not number or not ctx.phones[number] then return nil end
return {
table = 'ifold_notes', remember = true,
values = { number = number, title = row.title, content = row.content, created_at = ctx.ts(row.timestamp) },
}
end,
},
},
after = function(ctx) return { 'UPDATE ... counters ...' } end,
})map returns nil to skip the row, Migrate.DEFER to retry it once at the end of the table (a parent that is not imported yet), one operation, or a list of operations. An operation names the target table and its column values; remember = true records the source row in the log with the inserted id so children can find it (ctx.mapId(sourceTable, sourceId)), onDuplicate = 'ignore' turns the insert into INSERT IGNORE, and Migrate.LAST_ID inside a later operation's values is replaced by the first operation's insert id. ctx offers number, username, ts, attachments, json, decode, password, appOf, thread, dm, query, scalar, warn and phones (the set of numbers that exist in iFold).
