Migrate builder and bau worlds to shared world records

- add SQL-backed world entities with archive and rename support
- route builder, bau, deploy, and GDPR flows through world storage
- keep legacy folder support for existing worlds
This commit is contained in:
2026-05-09 16:37:15 +02:00
parent 8ade5180cb
commit 936ee38503
6 changed files with 314 additions and 57 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ if __name__ == "__main__":
with open(configfile, 'r') as file:
gamemode = yaml.load(file)
builderworld = path.expanduser(f'/worlds/builder{version}/{worldname}')
builderworld = sys.argv[4] if len(sys.argv) > 4 else path.expanduser(f'/worlds/builder{version}/{worldname}')
arenaworld = f'/servers/{gamemode["Server"]["Folder"]}/arenas/{worldname}'
if path.exists(arenaworld):
@@ -52,11 +52,9 @@ public class ServerStarter {
private static final String EVENT_PATH = TMP_DATA + "event/";
public static final String TEMP_WORLD_PATH = TMP_DATA + "arenaserver/";
private static final String WORLDS_FOLDER = "/worlds";
public static final String WORLDS_BASE_PATH = WORLDS_FOLDER + "/userworlds";
public static final String BUILDER_BASE_PATH = WORLDS_FOLDER + "/builder";
private static final String WORLDS_STORAGE_BASE_PATH = "/mnt/storage/worlds/userworlds";
public static final String WORLDS_BASE_PATH = SteamwarWorld.WORLD_STORAGE + "/";
public static final String LEGACY_WORLDS_BASE_PATH = "/worlds/userworlds";
public static final String LEGACY_BUILDER_BASE_PATH = "/worlds/builder";
private File directory = null;
private String worldDir = null;
@@ -152,23 +150,15 @@ public class ServerStarter {
public ServerStarter build(ServerVersion version, UUID owner) {
this.version = version;
directory = version.getServerDirectory("Bau");
worldDir = version.getWorldFolder(WORLDS_BASE_PATH);
worldName = String.valueOf(SteamwarUser.get(owner).getId());
SteamwarUser user = SteamwarUser.get(owner);
SteamwarWorld world = SteamwarWorld.getOrCreateBauWorld(user, version.getVersionSuffix());
worldDir = WORLDS_BASE_PATH;
worldName = world.getUuid().toString();
checkpoint = true;
build(owner);
worldSetup = () -> {
File world = new File(worldDir, worldName);
if (!world.exists()) {
File storage = new File(version.getWorldFolder(WORLDS_STORAGE_BASE_PATH), worldName);
if(storage.exists())
node.execute("mv", storage.getPath(), world.getPath());
else
copyWorld(node, new File(directory, "Bauwelt").getPath(), world.getPath());
}
};
worldSetup = () -> world.setupAndGetStoragePath(new File(directory, "Bauwelt"));
// Send players to existing server
startCondition = () -> {
@@ -224,8 +214,9 @@ public class ServerStarter {
public ServerStarter builder(ServerVersion version, String map, File generator) {
this.version = version;
directory = version.getServerDirectory("Builder");
worldDir = version.getWorldFolder(BUILDER_BASE_PATH);
worldName = map;
SteamwarWorld world = SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix());
worldDir = WORLDS_BASE_PATH;
worldName = world.getUuid().toString();
serverNameProvider = port -> "*" + map;
checkpoint = true;
constructor = (serverName, port, builder, shutdownCallback, failureCallback) -> new Builderserver(serverName, worldName, port, builder, shutdownCallback, failureCallback);
@@ -243,13 +234,15 @@ public class ServerStarter {
if(generator != null) {
worldSetup = () -> {
File leveldat = new File(new File(worldDir, worldName), "level.dat");
File leveldat = new File(world.setupAndGetStoragePath(), "level.dat");
try {
Files.copy(generator.toPath(), leveldat.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
};
} else {
worldSetup = () -> world.setupAndGetStoragePath(legacyBuilderWorld(version, map));
}
return this;
@@ -350,6 +343,22 @@ public class ServerStarter {
node.execute("cp", "-r", template, target);
}
private static File legacyBauWorld(ServerVersion version, SteamwarUser user) {
File legacyIdWorld = new File(version.getWorldFolder(LEGACY_WORLDS_BASE_PATH), String.valueOf(user.getId()));
if(legacyIdWorld.exists())
return legacyIdWorld;
File legacyUuidWorld = new File(version.getWorldFolder(LEGACY_WORLDS_BASE_PATH), user.getUUID().toString());
if(legacyUuidWorld.exists())
return legacyUuidWorld;
return new File(version.getServerDirectory("Bau"), "Bauwelt");
}
public static File legacyBuilderWorld(ServerVersion version, String map) {
return new File(version.getWorldFolder(LEGACY_BUILDER_BASE_PATH), map);
}
private interface ServerConstructor {
Subserver construct(String serverName, int port, ProcessBuilder builder, Runnable shutdownCallback, Consumer<Exception> failureCallback);
}
@@ -390,4 +399,4 @@ public class ServerStarter {
}
}
}
}
@@ -34,6 +34,7 @@ import de.steamwar.network.packets.server.BaumemberUpdatePacket;
import de.steamwar.persistent.Bauserver;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.velocitycore.*;
import de.steamwar.velocitycore.inventory.SWInventory;
import de.steamwar.velocitycore.inventory.SWItem;
@@ -41,6 +42,7 @@ import de.steamwar.velocitycore.network.NetworkSender;
import de.steamwar.velocitycore.util.BauLock;
import de.steamwar.data.BauLockState;
import java.io.File;
import java.util.Collection;
import java.util.function.Consumer;
@@ -220,14 +222,20 @@ public class BauCommand extends SWCommand {
public void delete(PlayerChatter sender, ServerVersion version) {
SWInventory inventory = new SWInventory(sender, 9, new Message("BAU_DELETE_GUI_NAME"));
inventory.addItem(0, new SWItem(new Message("BAU_DELETE_GUI_DELETE"), 10), click -> {
String world = version.getWorldFolder(ServerStarter.WORLDS_BASE_PATH) + sender.user().getId();
SteamwarWorld world = SteamwarWorld.getBauWorld(sender.user(), version.getVersionSuffix());
VelocityCore.schedule(() -> {
Bauserver subserver = Bauserver.get(sender.user().getUUID());
if(subserver != null)
subserver.stop();
SubserverSystem.deleteFolder(VelocityCore.local, world);
if(world != null) {
world.markDeleted();
SubserverSystem.deleteFolder(VelocityCore.local, world.getStorageDirectory().getPath());
} else {
File legacyWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), String.valueOf(sender.user().getId()));
SubserverSystem.deleteFolder(VelocityCore.local, legacyWorld.getPath());
}
sender.system("BAU_DELETE_DELETED");
}).schedule();
@@ -21,7 +21,7 @@ package de.steamwar.velocitycore.commands;
import de.steamwar.sql.GameModeConfig;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.SchematicType;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.velocitycore.ArenaMode;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
@@ -34,11 +34,11 @@ import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.UserPerm;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
@Linked
public class BuilderCloudCommand extends SWCommand {
@@ -49,13 +49,18 @@ public class BuilderCloudCommand extends SWCommand {
@Register(value = "create", description = "BUILDERCLOUD_CREATE_USAGE")
public void create(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map, @OptionalValue("") @Mapper("generator") @AllowNull File generator) {
mapFile(version, map).mkdir();
if(mapExists(version, map)) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix()).setupAndGetStoragePath();
sender.withPlayer(p -> new ServerStarter().builder(version, map, generator).send(p).start());
}
@Register(description = "BUILDERCLOUD_USAGE")
public void start(PlayerChatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if(!mapFile(version, map).exists()) {
if(!mapExists(version, map)) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
@@ -65,36 +70,32 @@ public class BuilderCloudCommand extends SWCommand {
@Register(value = "rename", description = "BUILDERCLOUD_RENAME_USAGE")
public void rename(Chatter sender, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String oldName, String newName) {
File oldMap = mapFile(version, oldName);
if(!oldMap.exists()) {
SteamwarWorld oldMap = builderWorld(version, oldName);
if(oldMap == null) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
File newMap = mapFile(version, newName);
if(newMap.exists()) {
if(mapExists(version, newName)) {
sender.system("BUILDERCLOUD_EXISTING_MAP");
return;
}
try {
Files.move(oldMap.toPath(), newMap.toPath());
} catch (IOException e) {
throw new SecurityException(e);
}
oldMap.rename(newName);
sender.system("BUILDERCLOUD_RENAMED");
}
@Register(value = "deploy", description = "BUILDERCLOUD_DEPLOY_USAGE")
public void deploy(Chatter sender, @Mapper("nonHistoricArenaMode") GameModeConfig<String, String> arenaMode, @ErrorMessage("BUILDERCLOUD_VERSION") ServerVersion version, @Mapper("map") String map) {
if(!mapFile(version, map).exists()) {
SteamwarWorld builderWorld = builderWorld(version, map);
if(builderWorld == null) {
sender.system("BUILDERCLOUD_UNKNOWN_MAP");
return;
}
VelocityCore.schedule(() -> {
VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map);
VelocityCore.local.execute("deployarena.py", arenaMode.configFile.getName(), Integer.toString(version.getVersionSuffix()), map, builderWorld.setupAndGetStoragePath());
ArenaMode.init();
sender.system("BUILDERCLOUD_DEPLOY_FINISHED");
}).schedule();
@@ -112,13 +113,23 @@ public class BuilderCloudCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 1);
String[] files;
if(folder == null || (files = folder.list()) == null)
ServerVersion version = getVersion(previousArguments, 1);
if(version == null)
return Collections.emptyList();
return Arrays.stream(files).filter(file -> new File(folder, file).isDirectory()).filter(file -> s.startsWith(".") || !file.startsWith(".")).toList();
Set<String> maps = new LinkedHashSet<>();
SteamwarWorld.getBuilderWorlds(version.getVersionSuffix()).stream()
.map(SteamwarWorld::getName)
.forEach(maps::add);
File legacyFolder = new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH));
String[] files = legacyFolder.list();
if(files != null)
Arrays.stream(files).filter(file -> new File(legacyFolder, file).isDirectory()).forEach(maps::add);
return maps.stream()
.filter(file -> s.startsWith(".") || !file.startsWith("."))
.toList();
}
};
}
@@ -133,7 +144,7 @@ public class BuilderCloudCommand extends SWCommand {
if(s.isEmpty())
return null;
File folder = getWorldFolder(previousArguments, 2);
File folder = getLegacyWorldFolder(previousArguments, 2);
if(folder == null)
throw new SecurityException();
@@ -147,7 +158,7 @@ public class BuilderCloudCommand extends SWCommand {
@Override
public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
File folder = getWorldFolder(previousArguments, 2);
File folder = getLegacyWorldFolder(previousArguments, 2);
String[] files;
if(folder == null || (files = folder.list()) == null)
@@ -158,14 +169,36 @@ public class BuilderCloudCommand extends SWCommand {
};
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.BUILDER_BASE_PATH), map);
private SteamwarWorld builderWorld(ServerVersion version, String map) {
SteamwarWorld world = SteamwarWorld.getBuilderWorld(map, version.getVersionSuffix());
if(world != null)
return world;
File legacyWorld = mapFile(version, map);
if(!legacyWorld.exists())
return null;
return SteamwarWorld.getOrCreateBuilderWorld(map, version.getVersionSuffix(), legacyWorld);
}
private File getWorldFolder(PreviousArguments previousArguments, int offset) {
ServerVersion v = ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
private boolean mapExists(ServerVersion version, String map) {
return SteamwarWorld.getBuilderWorld(map, version.getVersionSuffix()) != null || mapFile(version, map).exists();
}
private File mapFile(ServerVersion version, String map) {
return new File(version.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH), map);
}
private File getLegacyWorldFolder(PreviousArguments previousArguments, int offset) {
ServerVersion v = getVersion(previousArguments, offset);
if(v == null)
return null;
return new File(v.getWorldFolder(ServerStarter.BUILDER_BASE_PATH));
return new File(v.getWorldFolder(ServerStarter.LEGACY_BUILDER_BASE_PATH));
}
private ServerVersion getVersion(PreviousArguments previousArguments, int offset) {
if(previousArguments.userArgs.length < offset)
return null;
return ServerVersion.get(previousArguments.userArgs[previousArguments.userArgs.length - offset]);
}
}
@@ -24,11 +24,16 @@ import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.SteamwarWorld;
import de.steamwar.sql.UserPerm;
import de.steamwar.sql.internal.Statement;
import de.steamwar.velocitycore.ServerStarter;
import de.steamwar.velocitycore.ServerVersion;
import de.steamwar.velocitycore.VelocityCore;
import java.io.*;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -64,12 +69,21 @@ public class GDPRQuery extends SWCommand {
copy(getClass().getClassLoader().getResourceAsStream("GDPRQueryREADME.md"), out, "README.txt");
sender.system("GDPR_STATUS_WORLD");
copyBauwelt(user, out, "/home/minecraft/userworlds/" + user.getUUID().toString(), "BuildWorld12");
copyBauwelt(user, out, "/home/minecraft/userworlds15/" + user.getId(), "BuildWorld15");
Set<Integer> exportedVersions = new LinkedHashSet<>();
for(ServerVersion version : ServerVersion.values()) {
int versionSuffix = version.getVersionSuffix();
if(!exportedVersions.add(versionSuffix))
continue;
SteamwarWorld world = SteamwarWorld.getBauWorld(user, versionSuffix);
if(world != null)
copyBauwelt(user, out, world.setupAndGetStoragePath(), "BuildWorld" + versionSuffix);
else
copyLegacyBauwelt(user, out, version);
}
sender.system("GDPR_STATUS_INVENTORIES");
copyPlayerdata(user, out, "/home/minecraft/userworlds", "BuildInventories12");
copyPlayerdata(user, out, "/home/minecraft/userworlds15", "BuildInventories15");
copyPlayerdata(user, out, SteamwarWorld.WORLD_STORAGE, "BuildInventories");
sender.system("GDPR_STATUS_DATABASE");
sqlCSV(user, out, bannedIPs, "BannedIPs.csv");
@@ -230,6 +244,18 @@ public class GDPRQuery extends SWCommand {
copy(playerdata, out, outDir + "/playerdata/" + user.getUUID().toString() + ".dat");
}
private void copyLegacyBauwelt(SteamwarUser user, ZipOutputStream out, ServerVersion version) throws IOException {
File legacyIdWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), String.valueOf(user.getId()));
if(legacyIdWorld.exists()) {
copyBauwelt(user, out, legacyIdWorld.getPath(), "BuildWorld" + version.getVersionSuffix());
return;
}
File legacyUuidWorld = new File(version.getWorldFolder(ServerStarter.LEGACY_WORLDS_BASE_PATH), user.getUUID().toString());
if(legacyUuidWorld.exists())
copyBauwelt(user, out, legacyUuidWorld.getPath(), "BuildWorld" + version.getVersionSuffix());
}
private void copyPlayerdata(SteamwarUser user, ZipOutputStream out, String inDir, String outDir) throws IOException {
File worlds = new File(inDir);
String path = "playerdata/" + user.getUUID().toString() + ".dat";