You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HuskSync/test/spin_network.py

312 lines
13 KiB
Python

# This script creates a network of Minecraft servers for testing HuskSync on Windows
# Based on Carcass (https://github.com/WiIIiam278/carcass/)
# To spin up a network of servers, run this script with Python 3.8 or higher
# Run pip install -r requirements.txt to install the required packages
# Then use python ./spin_network.py to spin up the network
import os
import requests
import shutil
from tqdm import tqdm
# Parameters for starting a network of Minecraft servers
class Parameters:
root_dir = './servers/'
proxy_version = "1.21"
minecraft_version = '1.21.1'
eula_agreement = 'true'
backend_names = ['alpha', 'beta']
backend_ports = [25567, 25568]
backend_type = 'paper'
backend_ram = 2048
backend_plugins = ['../target/HuskSync-Paper-*.jar']
backend_plugin_folders = ['./HuskSync']
operator_names = ['William278']
operator_uuids = ['5dfb0558-e306-44f4-bb9a-f9218d4eb787']
proxy_name = "proxy"
proxy_host = "0.0.0.0"
proxy_port = 25565
proxy_type = "waterfall"
proxy_ram = 512
proxy_plugins = []
proxy_plugin_folders = []
just_update_plugins = False
def main(update=False):
# Create parameters object
parameters = Parameters()
parameters.just_update_plugins = update
# Generate config
if os.path.exists("./config.yml"):
copy_husksync_config()
# Update plugins if necessary
if parameters.just_update_plugins:
for name in parameters.backend_names:
plugin_dir = f"{parameters.root_dir}{name}/plugins"
# Clear contents of the plugin_dir directory if it exists
if os.path.exists(plugin_dir):
shutil.rmtree(plugin_dir)
# Create the plugin_dir directory if it doesn't exist
if not os.path.exists(plugin_dir):
os.makedirs(plugin_dir)
# Copy plugins in
copy_plugins(parameters.backend_plugins, parameters.backend_plugin_folders, plugin_dir)
# Start servers
start_servers(parameters)
return
# Create all backend servers
for name, port in zip(parameters.backend_names, parameters.backend_ports):
create_backend_server(name, port, parameters)
# Create the proxy server
create_proxy_server(parameters)
# Start servers
start_servers(parameters)
def copy_husksync_config():
# Copy ./config.yml to ./HuskSync/config.yml
if not os.path.exists("./HuskSync"):
os.makedirs("./HuskSync")
shutil.copy("./config.yml", "./HuskSync/config.yml")
# Creates a backend server
def create_backend_server(name, port, parameters):
print(f"Creating {parameters.backend_type} backend server, {name}")
# Create a folder for the server. If it exists, delete it.
server_dir = parameters.root_dir + name
if os.path.exists(f"{server_dir}"):
os.system(f"rm -rf {server_dir}")
if not os.path.exists(server_dir):
os.makedirs(server_dir)
if parameters.backend_type == "paper":
# Create necessary subdirectories
create_subdirectories(["config", "plugins"], server_dir)
# Download the latest paper for the version and place it in the server folder
server_jar = "paper.jar"
download_paper_build("paper", parameters.minecraft_version,
get_latest_paper_build_number("paper", parameters.minecraft_version),
f"{server_dir}/{server_jar}")
# Create eula.text and set eula=true
with open(server_dir + "/eula.txt", "w") as file:
file.write(f"# Auto-generated eula.txt for server {name}\n")
file.write(f"eula={parameters.eula_agreement}")
# Create the spigot.yml and enable BungeeCord
with open(server_dir + "/spigot.yml", "w") as file:
file.write(f"# Auto-generated spigot.yml for server {name}\n")
file.write(f"settings:\n")
file.write(f" bungeecord: true\n")
# Create the paper-global.yml and enable BungeeCord
with open(server_dir + "/config/paper-global.yml", "w") as file:
file.write(f"# Auto-generated paper-global.yml for server {name}\n")
file.write(f"proxies:\n")
file.write(f" bungee-cord:\n")
file.write(f" online-mode: true\n")
# Create the server.properties file
server_properties = server_dir + "/server.properties"
with open(server_properties, "w") as file:
file.write(f"# Auto-generated server.properties for server {name}\n")
file.write(f"# {parameters.backend_type} server\n")
file.write(f"server-port={port}\n")
file.write(f"motd=Server {name}\n")
file.write(f"enable-query=false\n")
file.write(f"enable-rcon=false\n")
file.write(f"spawn-protection=0\n")
file.write(f"online-mode=false\n")
# Create operators file if needed
if len(parameters.operator_names) > 0 and len(parameters.operator_uuids) > 0:
with open(server_dir + "/ops.json", "w") as file:
file.write("[\n")
operator_count = min(len(parameters.operator_names), len(parameters.operator_uuids))
for i in range(0, operator_count):
file.write(" {\n")
file.write(f" \"uuid\": \"{parameters.operator_uuids[i]}\",\n")
file.write(f" \"name\": \"{parameters.operator_names[i]}\",\n")
file.write(" \"level\": 4,\n")
file.write(" \"bypassesPlayerLimit\": false\n")
if i < operator_count - 1:
file.write(" },\n")
else:
file.write(" }\n")
file.write("]")
# Copy plugins
copy_plugins(parameters.backend_plugins, parameters.backend_plugin_folders, f"{server_dir}/plugins")
# Create start scripts
create_start_scripts(server_dir,
f"-Xms{parameters.backend_ram}M -Xmx{parameters.backend_ram}M -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true -jar ./{server_jar} --nogui")
def create_proxy_server(parameters):
print(f"Creating {parameters.proxy_type} proxy server, {parameters.proxy_name}")
# Create folder for the proxy
server_dir = parameters.root_dir + parameters.proxy_name
if os.path.exists(f"{server_dir}"):
os.system(f"rm -rf {server_dir}")
if not os.path.exists(server_dir):
os.makedirs(server_dir)
if parameters.proxy_type == "waterfall":
# Create necessary subdirectories
create_subdirectories(["plugins"], server_dir)
# Download the latest paper for the version and place it in the server folder
proxy_jar = "waterfall.jar"
download_paper_build("waterfall", parameters.proxy_version,
get_latest_paper_build_number("waterfall", parameters.proxy_version),
f"{server_dir}/{proxy_jar}")
# Create the config.yml
with open(server_dir + "/config.yml", "w") as file:
file.write(f"# Auto-generated config.yml for proxy server {parameters.proxy_name}\n")
# Write proxy settings
file.write(f"listeners:\n")
file.write(f"- query_port: {parameters.proxy_port}\n")
file.write(f" motd: '{parameters.proxy_version} Proxy Server'\n")
file.write(f" query_enabled: false\n")
file.write(f" proxy_protocol: false\n")
file.write(f" priorities:\n")
file.write(f" - {parameters.backend_names[0]}\n")
file.write(f" bind_local_address: true\n")
file.write(f" host: {parameters.proxy_host}:{parameters.proxy_port}\n")
file.write(f"ip_forward: true\n")
file.write(f"online_mode: true\n")
# Write servers
file.write(f"servers:\n")
for i in range(len(parameters.backend_names)):
file.write(f" {parameters.backend_names[i]}:\n")
file.write(
f" motd: '&eBackend {parameters.backend_type} {parameters.backend_names[i]} (port {parameters.backend_ports[i]})'\n")
file.write(f" address: localhost:{parameters.backend_ports[i]}\n")
file.write(f" restricted: false\n")
# Copy plugins
copy_plugins(parameters.proxy_plugins, parameters.proxy_plugin_folders, f"{server_dir}/plugins")
# Create startup scripts
create_start_scripts(server_dir,
f"-Xms{parameters.proxy_ram}M -Xmx{parameters.proxy_ram}M -XX:+UseG1GC -XX:G1HeapRegionSize=4M -XX:+UnlockExperimentalVMOptions -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -jar ./{proxy_jar}")
# Fetches the latest build number for a paper project
def get_latest_paper_build_number(project, version_set):
url = f"https://api.papermc.io/v2/projects/{project}/versions/{version_set}/builds"
return requests.get(url).json()["builds"][-1]["build"]
# Downloads a paper build for a project to the target file
def download_paper_build(project, version_set, build_number, target_file):
url = f"https://api.papermc.io/v2/projects/{project}/versions/{version_set}/builds/{build_number}/downloads/{project}-{version_set}-{build_number}.jar"
requests.get(url, stream=True)
with open(target_file, "wb") as file:
for chunk in tqdm(requests.get(url, stream=True).iter_content(chunk_size=1024)):
if chunk:
file.write(chunk)
file.flush()
# Creates subdirectories for a server
def create_subdirectories(sub_directories, parent_directory):
for subdirectory in sub_directories:
if not os.path.exists(parent_directory + "/" + subdirectory):
os.makedirs(parent_directory + "/" + subdirectory)
# Create batch and powershell start scripts
def create_start_scripts(server_directory, start_arguments):
with open(server_directory + "/start.bat", "w") as file:
file.write("@echo off\n")
file.write(
f"java {start_arguments}\n")
file.write("pause")
with open(server_directory + "/start.ps1", "w") as file:
file.write(f"Set-Location -Path {server_directory}\n")
file.write(f"Start-Process java -ArgumentList '{start_arguments}'")
# Copies plugins and plugin folders from the source to the target
def copy_plugins(plugins, plugin_folders, plugins_folder):
# Copy each file from the plugin list to the server/plugins folder
for plugin in plugins:
# Skip if the plugin does not exist
if not os.path.exists(plugin):
# If the plugin name contains a *, find the plugin with the same name before the *
if "*" in plugin:
# Get the parent directory of the plugin by getting everything before the last /
parent_directory = plugin.rsplit("/", 1)[0]
plugin_name = plugin.split("*")[0].split("/")[-1]
if os.path.exists(parent_directory):
hit = False
for file in os.listdir(parent_directory):
if file.startswith(plugin_name):
shutil.copy(parent_directory + "/" + file, plugins_folder + "/" + file)
print(f"Copied plugin {file} to {plugins_folder}")
hit = True
break
if not hit:
print(f"Plugin jar file {plugin} could not be found, skipping copy")
continue
else:
print(f"Plugin jar file {plugin} is in an invalid directory, skipping copy")
continue
else:
print(f"Plugin jar file {plugin} does not exist, skipping copy")
continue
shutil.copy(plugin, plugins_folder)
print(f"Copied plugin {plugin} to {plugins_folder}")
# Copy each plugin data folder from the plugin list to the server/plugins folder
for folder in plugin_folders:
# Skip if the plugin does not exist
if not os.path.exists(folder):
print(f"Plugin data folder {folder} does not exist, skipping copy")
continue
# Copy the contents of the folder to a subdirectory of the server/plugins folder
shutil.copytree(folder, plugins_folder + "/" + folder.split("/")[-1])
print(f"Copied plugin data folder {folder} to {plugins_folder}")
def start_servers(parameters):
start_server(f"{parameters.root_dir}{parameters.proxy_name}")
for name in parameters.backend_names:
start_server(f"{parameters.root_dir}{name}")
def start_server(server):
os.chdir(server)
os.system("start start.bat")
os.chdir("../../")
# Execute the main function
if __name__ == "__main__":
main(os.path.exists("./servers"))