How it works
On this page(7)
Identities
A transfer goes from an old account to a new one, both given by name. For each name the
plugin collects the UUIDs it may be stored under: the one in the server's user cache and the
offline-mode UUID derived from the name. preview prints both identities, so you can see which
UUIDs will be searched and which UUID will be written.
A transfer between the same name and the same UUID is refused, since there is nothing to do. A same-name migration to a different UUID (a non-premium player buying the account) is allowed.
Where the data is found
Every plugin data folder under plugins/ is searched, by three sources that add up:
| Source | Toggle | What it finds |
|---|---|---|
| SnLib convention | discovery.snlib-convention | The database: section of every plugin's config.yml, read with SnLib's own rules (SQLite by default, MySQL when configured) |
| Filesystem scan | discovery.filesystem-scan | SQLite files (by their file header), H2 files (.mv.db), files named after the player's UUID or nick, and shared .yml, .yaml, .json, .properties, .txt and .csv files whose content holds the player |
| MySQL sniffing | discovery.mysql-sniffing | MySQL credentials written in other plugins' config files. A database that cannot be reached is reported and the rest continue |
Each database is inspected for its real tables and columns. profiles.yml can add stores that
discovery cannot find, give table hints or skip a plugin entirely (see Profiles).
What gets replaced
- The old UUID is replaced wherever it appears, in any letter case, with or without dashes. The new value keeps the style found in each cell or file.
- The old nick is replaced only when it is the exact whole value of a database cell, a YAML or
JSON value or key, or a file name. It is never replaced inside free text such as a chat log or a
description.
transfer.name-matching: falseturns nick matching off. - Files named after the player are renamed to the new identity. A player whose name equals a
common plugin file name (such as
configordata) never gets that file renamed; it is listed as an omission instead.
Apply
- Both accounts are kicked if online, and both are blocked from joining until the transfer ends.
- The plugin waits
transfer.grace-period-secondsso the other plugins finish saving the players' quit data. - Everything about to change is backed up to
backups/<id>/, for both accounts. - The safe stores are written immediately. Rows of the new account that collide with the old account's rows are replaced, and the chat says how many.
- The stores that must wait are queued for the next restart.
- Both accounts are unblocked, the chat and console show one line per plugin, and the full report
is written to
reports/<id>.txt.
Each plugin is handled on its own: a plugin that fails (a locked database, wrong credentials) is reported and never stops the others. Each database store is written in one transaction.
Instant and queued stores
| Store | Default |
|---|---|
| SQLite or MySQL database | Instant |
| File named after the player | Instant |
| H2 database | Queued: its owning plugin keeps it locked while the server runs |
| Shared data file | Queued: its owning plugin may keep it in memory and save over it on shutdown |
A profile can force a plugin to instant or queued. With transfer.queue-enabled: false,
queued stores are skipped and reported instead.
Queued work is applied on the next start, before any other plugin enables, so no store is
locked yet. A store that is still locked is retried on the following start and dropped if it fails
again. The result is logged to the console and shown once to the first staff member with
snusertransfer.notify who joins. /sntransfer pending lists the queue and
/sntransfer cancel <queueId> removes an entry before the restart.
Backups and undo
Every apply, undo and restart run first writes its before-image: the affected rows and a full copy
of every touched file. /sntransfer undo <id> restores it, after kicking and blocking both
accounts the same way an apply does. The undo is itself backed up and prints the id that reverses
it.
Backups are deleted after backups.retention-days (30 by default) by a sweep that runs once at
startup. A table with more matching rows than backups.max-rows-per-table is reported and left
untouched, because its before-image is held in memory while it is written.
Supported stores
| Store | Notes |
|---|---|
| SQLite | Any file with a SQLite header, whatever its extension |
| MySQL | Through the SnLib convention, MySQL sniffing or a profile |
| H2 | H2 2.1 files (LuckPerms and other plugins on H2 2.1) and H2 2.2 or later files. Files from H2 1.x are reported as unreadable |
| YAML, JSON, properties, text, CSV | Renamed when named after the player, rewritten when shared |