Phase 8 fix: preserve config backward compatibility

The Phase 8 refactor moved the connection keys (host, password, Server_id,
etc.) from [general] into a new [connection] section. On servers with an
existing playersync-common.toml this would silently reset:
  - host to 'localhost'
  - password to 'pleaseChangeThisPassword'
  - Server_id to a new random value

The last one is the worst: every player_data row with last_server=<old_id>
would momentarily point to a zombie peer until the next heartbeat tick.

Fix: move every key that already existed in 2.1.4 configs back into
[general]. Only genuinely new keys (save_triggers, sync_toggles,
performance, safety, observability) stay in their new sections. Existing
users upgrading see their old [general] block load correctly; the new
sections get created with defaults on first boot and don't wipe anything.

Also adds modid=PlayerSync.MODID to CommandInit's @EventBusSubscriber
so RegisterCommandsEvent is guaranteed to fire under our mod's bus scope.
This commit is contained in:
laforetbrut 2026-04-22 06:38:27 +02:00
parent c7487196ec
commit d818794a20
2 changed files with 31 additions and 28 deletions

View File

@ -51,7 +51,7 @@ import java.util.concurrent.ThreadPoolExecutor;
* *
* @author vyrriox * @author vyrriox
*/ */
@EventBusSubscriber() @EventBusSubscriber(modid = PlayerSync.MODID)
public class CommandInit { public class CommandInit {
private static final int PERM_OP = 2; private static final int PERM_OP = 2;

View File

@ -11,7 +11,7 @@ import java.util.Random;
public class JdbcConfig { public class JdbcConfig {
public static ModConfigSpec COMMON_CONFIG; public static ModConfigSpec COMMON_CONFIG;
// ----- Connection ----- // ----- Connection (kept under [general] for backward compat with existing config files) -----
public static ModConfigSpec.ConfigValue<String> HOST; public static ModConfigSpec.ConfigValue<String> HOST;
public static ModConfigSpec.IntValue PORT; public static ModConfigSpec.IntValue PORT;
public static ModConfigSpec.ConfigValue<String> USERNAME; public static ModConfigSpec.ConfigValue<String> USERNAME;
@ -19,10 +19,12 @@ public class JdbcConfig {
public static ModConfigSpec.ConfigValue<String> DATABASE_NAME; public static ModConfigSpec.ConfigValue<String> DATABASE_NAME;
public static ModConfigSpec.BooleanValue USE_SSL; public static ModConfigSpec.BooleanValue USE_SSL;
// ----- Core sync behaviour ----- // ----- Core sync behaviour (kept under [general]) -----
public static ModConfigSpec.ConfigValue<List<String>> SYNC_WORLD; public static ModConfigSpec.ConfigValue<List<String>> SYNC_WORLD;
public static ModConfigSpec.BooleanValue SYNC_ADVANCEMENTS; public static ModConfigSpec.BooleanValue SYNC_ADVANCEMENTS;
public static ModConfigSpec.BooleanValue KICK_WHEN_ALREADY_ONLINE; public static ModConfigSpec.BooleanValue KICK_WHEN_ALREADY_ONLINE;
public static ModConfigSpec.ConfigValue<String> KICK_MESSAGE;
public static ModConfigSpec.IntValue KICK_GRACE_PERIOD_MS;
public static ModConfigSpec.BooleanValue USE_LEGACY_SERIALIZATION; public static ModConfigSpec.BooleanValue USE_LEGACY_SERIALIZATION;
public static final ModConfigSpec.ConfigValue<String> ITEM_PLACEHOLDER_TITLE_OVERRIDE; public static final ModConfigSpec.ConfigValue<String> ITEM_PLACEHOLDER_TITLE_OVERRIDE;
public static final ModConfigSpec.ConfigValue<String> ITEM_PLACEHOLDER_DESCRIPTION_OVERRIDE; public static final ModConfigSpec.ConfigValue<String> ITEM_PLACEHOLDER_DESCRIPTION_OVERRIDE;
@ -32,13 +34,13 @@ public class JdbcConfig {
/** Table-name prefix; see {@link vip.fubuki.playersync.util.Tables}. */ /** Table-name prefix; see {@link vip.fubuki.playersync.util.Tables}. */
public static ModConfigSpec.ConfigValue<String> TABLE_PREFIX; public static ModConfigSpec.ConfigValue<String> TABLE_PREFIX;
// ----- Save triggers ----- // ----- Save triggers (new section) -----
public static ModConfigSpec.IntValue AUTO_SAVE_INTERVAL_MINUTES; public static ModConfigSpec.IntValue AUTO_SAVE_INTERVAL_MINUTES;
public static ModConfigSpec.BooleanValue SAVE_ON_DIMENSION_CHANGE; public static ModConfigSpec.BooleanValue SAVE_ON_DIMENSION_CHANGE;
public static ModConfigSpec.BooleanValue SAVE_ON_DEATH; public static ModConfigSpec.BooleanValue SAVE_ON_DEATH;
public static ModConfigSpec.BooleanValue SAVE_ON_RESPAWN; public static ModConfigSpec.BooleanValue SAVE_ON_RESPAWN;
// ----- Sync toggles (per-category opt-out) ----- // ----- Sync toggles (new section) -----
public static ModConfigSpec.BooleanValue SYNC_INVENTORY; public static ModConfigSpec.BooleanValue SYNC_INVENTORY;
public static ModConfigSpec.BooleanValue SYNC_ENDER_CHEST; public static ModConfigSpec.BooleanValue SYNC_ENDER_CHEST;
public static ModConfigSpec.BooleanValue SYNC_XP; public static ModConfigSpec.BooleanValue SYNC_XP;
@ -50,7 +52,7 @@ public class JdbcConfig {
public static ModConfigSpec.BooleanValue SYNC_COSMETIC_ARMOR; public static ModConfigSpec.BooleanValue SYNC_COSMETIC_ARMOR;
public static ModConfigSpec.BooleanValue SYNC_REFINED_STORAGE; public static ModConfigSpec.BooleanValue SYNC_REFINED_STORAGE;
// ----- Performance tuning ----- // ----- Performance tuning (new section) -----
public static ModConfigSpec.IntValue HEARTBEAT_INTERVAL_SECONDS; public static ModConfigSpec.IntValue HEARTBEAT_INTERVAL_SECONDS;
public static ModConfigSpec.IntValue PEER_STALE_THRESHOLD_SECONDS; public static ModConfigSpec.IntValue PEER_STALE_THRESHOLD_SECONDS;
public static ModConfigSpec.IntValue JOIN_POLL_MAX_ATTEMPTS; public static ModConfigSpec.IntValue JOIN_POLL_MAX_ATTEMPTS;
@ -59,14 +61,12 @@ public class JdbcConfig {
public static ModConfigSpec.IntValue HIKARI_POOL_MAX_SIZE; public static ModConfigSpec.IntValue HIKARI_POOL_MAX_SIZE;
public static ModConfigSpec.IntValue HIKARI_LEAK_THRESHOLD_MS; public static ModConfigSpec.IntValue HIKARI_LEAK_THRESHOLD_MS;
// ----- Safety / integrity ----- // ----- Safety / integrity (new section) -----
public static ModConfigSpec.BooleanValue REFUSE_EMPTY_INVENTORY_WRITE; public static ModConfigSpec.BooleanValue REFUSE_EMPTY_INVENTORY_WRITE;
public static ModConfigSpec.IntValue MAX_INVENTORY_SIZE_BYTES; public static ModConfigSpec.IntValue MAX_INVENTORY_SIZE_BYTES;
public static ModConfigSpec.ConfigValue<String> KICK_MESSAGE;
public static ModConfigSpec.IntValue KICK_GRACE_PERIOD_MS;
public static ModConfigSpec.IntValue SKIP_SAVES_WHEN_TPS_BELOW; public static ModConfigSpec.IntValue SKIP_SAVES_WHEN_TPS_BELOW;
// ----- Observability ----- // ----- Observability (new section) -----
public static ModConfigSpec.BooleanValue LOG_STRUCTURED_JSON; public static ModConfigSpec.BooleanValue LOG_STRUCTURED_JSON;
public static ModConfigSpec.IntValue LOG_ROTATION_SIZE_MB; public static ModConfigSpec.IntValue LOG_ROTATION_SIZE_MB;
public static ModConfigSpec.IntValue LOG_ROTATION_MAX_FILES; public static ModConfigSpec.IntValue LOG_ROTATION_MAX_FILES;
@ -75,8 +75,13 @@ public class JdbcConfig {
static { static {
ModConfigSpec.Builder B = new ModConfigSpec.Builder(); ModConfigSpec.Builder B = new ModConfigSpec.Builder();
// ===== Connection ===== // ==========================================================================
B.comment("Database connection").push("connection"); // [general] Every key that already existed in pre-2.1.5 configs MUST stay
// here so existing playersync-common.toml files keep working after an upgrade.
// New settings go into dedicated sections below.
// ==========================================================================
B.comment("General settings").push("general");
HOST = B.comment("The host of the database").define("host", "localhost"); HOST = B.comment("The host of the database").define("host", "localhost");
PORT = B.comment("database port").defineInRange("db_port", 3306, 0, 65535); PORT = B.comment("database port").defineInRange("db_port", 3306, 0, 65535);
USE_SSL = B.comment("whether use SSL").define("use_ssl", false); USE_SSL = B.comment("whether use SSL").define("use_ssl", false);
@ -89,18 +94,15 @@ public class JdbcConfig {
"Leave empty to keep the historical unprefixed names. Example: 'playersync_'.", "Leave empty to keep the historical unprefixed names. Example: 'playersync_'.",
"Only alphanumeric characters and underscores are allowed." "Only alphanumeric characters and underscores are allowed."
).define("table_prefix", ""); ).define("table_prefix", "");
SERVER_ID = B.comment("The server id should be unique across the cluster") SERVER_ID = B.comment("the server id should be unique")
.define("Server_id", new Random().nextInt(1, Integer.MAX_VALUE - 1)); .define("Server_id", new Random().nextInt(1, Integer.MAX_VALUE - 1));
B.pop();
// ===== General behaviour =====
B.comment("General sync behaviour").push("general");
SYNC_WORLD = B.comment("The worlds that will be synchronized. If running on a server, leave array empty.") SYNC_WORLD = B.comment("The worlds that will be synchronized. If running on a server, leave array empty.")
.define("sync_world", new ArrayList<>()); .define("sync_world", new ArrayList<>());
SYNC_ADVANCEMENTS = B.comment("Whether to sync advancements between servers") SYNC_ADVANCEMENTS = B.comment("Whether to sync advancements between servers")
.define("sync_advancements", true); .define("sync_advancements", true);
KICK_WHEN_ALREADY_ONLINE = B.comment("Whether to kick player when already online on another server") KICK_WHEN_ALREADY_ONLINE = B.comment("Whether to kick player when already online on another server")
.define("kick_when_already_online", true); .define("kick_when_already_online", true);
// NEW in 2.1.5 safe to add to [general], unknown keys on old rollbacks just get ignored.
KICK_MESSAGE = B.comment( KICK_MESSAGE = B.comment(
"Custom kick message when a duplicate login is detected. Empty = default message.") "Custom kick message when a duplicate login is detected. Empty = default message.")
.define("kick_message", ""); .define("kick_message", "");
@ -120,10 +122,11 @@ public class JdbcConfig {
ITEM_PLACEHOLDER_DESCRIPTION_OVERRIDE = B ITEM_PLACEHOLDER_DESCRIPTION_OVERRIDE = B
.comment("Override the description of placeholder items which are unavailable on the current server.") .comment("Override the description of placeholder items which are unavailable on the current server.")
.define("item_placeholder_description_override", ""); .define("item_placeholder_description_override", "");
B.pop();
// ===== Save triggers ===== B.pop(); // end [general]
B.comment("When to trigger a save").push("save_triggers");
// ===== [save_triggers] =====
B.comment("When to trigger a save (new in 2.1.5)").push("save_triggers");
AUTO_SAVE_INTERVAL_MINUTES = B.comment( AUTO_SAVE_INTERVAL_MINUTES = B.comment(
"Periodic full-flush interval (minutes). Triggers a complete save (player data +", "Periodic full-flush interval (minutes). Triggers a complete save (player data +",
"backpacks + SS + RS2) for every online player. Set to 0 to disable. Default 10." "backpacks + SS + RS2) for every online player. Set to 0 to disable. Default 10."
@ -141,8 +144,8 @@ public class JdbcConfig {
.define("save_on_respawn", true); .define("save_on_respawn", true);
B.pop(); B.pop();
// ===== Sync toggles ===== // ===== [sync_toggles] =====
B.comment("Per-category sync toggles — disable individual data kinds if your server doesn't need them").push("sync_toggles"); B.comment("Per-category sync toggles — disable individual data kinds if your server doesn't need them (new in 2.1.5)").push("sync_toggles");
SYNC_INVENTORY = B.comment("Sync main inventory + armor + offhand").define("sync_inventory", true); SYNC_INVENTORY = B.comment("Sync main inventory + armor + offhand").define("sync_inventory", true);
SYNC_ENDER_CHEST = B.comment("Sync ender chest contents").define("sync_ender_chest", true); SYNC_ENDER_CHEST = B.comment("Sync ender chest contents").define("sync_ender_chest", true);
SYNC_XP = B.comment("Sync total XP / experience levels").define("sync_xp", true); SYNC_XP = B.comment("Sync total XP / experience levels").define("sync_xp", true);
@ -155,8 +158,8 @@ public class JdbcConfig {
SYNC_REFINED_STORAGE = B.comment("Sync Refined Storage 2 disk contents").define("sync_refined_storage", true); SYNC_REFINED_STORAGE = B.comment("Sync Refined Storage 2 disk contents").define("sync_refined_storage", true);
B.pop(); B.pop();
// ===== Performance ===== // ===== [performance] =====
B.comment("Performance tuning — touch only if you know what you're doing").push("performance"); B.comment("Performance tuning — touch only if you know what you're doing (new in 2.1.5)").push("performance");
HEARTBEAT_INTERVAL_SECONDS = B.comment( HEARTBEAT_INTERVAL_SECONDS = B.comment(
"How often this server writes its heartbeat to server_info (seconds). Pair with", "How often this server writes its heartbeat to server_info (seconds). Pair with",
"peer_stale_threshold_seconds: peers older than threshold are treated as dead.") "peer_stale_threshold_seconds: peers older than threshold are treated as dead.")
@ -184,8 +187,8 @@ public class JdbcConfig {
.defineInRange("hikari_leak_threshold_ms", 25000, 2000, 600000); .defineInRange("hikari_leak_threshold_ms", 25000, 2000, 600000);
B.pop(); B.pop();
// ===== Safety ===== // ===== [safety] =====
B.comment("Safety guards — prevent silent data loss").push("safety"); B.comment("Safety guards — prevent silent data loss (new in 2.1.5)").push("safety");
REFUSE_EMPTY_INVENTORY_WRITE = B.comment( REFUSE_EMPTY_INVENTORY_WRITE = B.comment(
"Refuse to UPDATE player_data with an empty inventory if the DB currently has non-empty", "Refuse to UPDATE player_data with an empty inventory if the DB currently has non-empty",
"data. Last-resort guard against on-disconnect wipes. Set to false only for debugging.") "data. Last-resort guard against on-disconnect wipes. Set to false only for debugging.")
@ -200,8 +203,8 @@ public class JdbcConfig {
.defineInRange("skip_saves_when_tps_below", 0, 0, 20); .defineInRange("skip_saves_when_tps_below", 0, 0, 20);
B.pop(); B.pop();
// ===== Observability ===== // ===== [observability] =====
B.comment("Log file & diagnostics").push("observability"); B.comment("Log file & diagnostics (new in 2.1.5)").push("observability");
LOG_STRUCTURED_JSON = B.comment( LOG_STRUCTURED_JSON = B.comment(
"Emit sync.log entries as JSON objects instead of text. Enables ingestion in", "Emit sync.log entries as JSON objects instead of text. Enables ingestion in",
"Loki / ELK / Splunk pipelines.") "Loki / ELK / Splunk pipelines.")