/* * This file is a part of the SteamWar software. * * Copyright (C) 2025 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import java.security.MessageDigest import java.util.stream.Collectors plugins { } class DevServer extends DefaultTask { @Input @Optional String worldName = null @Input String template = null @Input @Optional String plugins = null @Input @Optional Integer port = null @Input @Optional String jar = null @Input @Optional Map dParams = new HashMap<>() @Input @Optional String checkpointFolder = null @Input @Optional Boolean profile = null @Input @Optional Boolean forceUpgrade = null DevServer() { super() doFirst { if (checkpointFolder != null) dParams.put("checkpoint", checkpointFolder) List projects = [] projects.add(project) while (projects.first.parent != null) { projects.add(0, projects.first.parent) } def properties = new Properties() projects.forEach { def file = new File(it.projectDir, "steamwar.properties") if (file.exists()) { properties.load(new FileInputStream(file)) } } if (worldName == null) worldName = properties.get("worldName") host = properties.get("host") if (host == null) { throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!") } } doLast { setupTemplate(template) uploadDependencies() startDevServer() } finalizedBy(new Finalizer()) } @Internal BufferedWriter processInput @Internal String host @Internal Boolean running = true class Finalizer extends DefaultTask { Finalizer() { super() doLast { running = false if (processInput != null) { processInput.write(template.endsWith("Velocity") ? "end\n" : "stop\n") processInput.flush() } } } } private Process run(String... args) { List arguments = new ArrayList<>(); arguments.add("ssh") arguments.add(host) arguments.add("-T") arguments.addAll(Arrays.asList(args)) def process = new ProcessBuilder(arguments).start() process.waitFor() return process } private boolean checkFileOnRemote(String path) { def process = run("[ -e \"$path\" ] && echo \"true\"") process.errorStream.close() process.outputStream.close() try (def reader = new BufferedReader(new InputStreamReader(process.inputStream))) { return reader.lines().count() > 0 } } private static void closeProcess(Process process) { process.outputStream.close() process.inputStream.close() process.errorStream.close() } void setupTemplate(String template) { if (checkFileOnRemote("$template")) return if (checkFileOnRemote("/configs/GameModes/${template}.yml")) { println("GameMode Config exists") def process = run("cat /configs/GameModes/${template}.yml | grep \"Folder: \"") String serverTemplateName = new BufferedReader(new InputStreamReader(process.inputStream)).lines().collect(Collectors.joining("\n")) .trim() .substring("Folder: ".length()) DevServer.closeProcess(process) setupTemplate(serverTemplateName) run("ln -s $serverTemplateName $template") return } if (!checkFileOnRemote("/servers/$template")) { throw new GradleException("Used template ($template) is not in /servers/ directory of the given host $host") } DevServer.closeProcess(run("cp -r /servers/$template $template")) DevServer.closeProcess(run("chmod u+w $template")) DevServer.closeProcess(run("rm -r $template/plugins/*WorldEdit/")) DevServer.closeProcess(run("rm $template/log4j2.xml")) } void uploadDependencies() { def base = plugins == null ? "$template/plugins" : plugins println("Uploading to ~/$base") this.dependsOn.forEach { Project resolved AbstractArchiveTask archiveTask if (it instanceof String) { resolved = project.findProject(it.substring(0, it.lastIndexOf(':'))) archiveTask = (AbstractArchiveTask) resolved.tasks.findByName(it.substring(it.lastIndexOf(':') + 1)) } else { throw new GradleException("Illegal argument for uploading dependencies") } def archive = archiveTask.archiveFile.get().asFile Process process = new ProcessBuilder("ssh", host, "-T", "sha1sum $base/${archive.name.replace("-all", "")}").start() byte[] bytes = MessageDigest.getInstance("sha1").digest(archive.bytes) StringBuilder sb = new StringBuilder() for (byte b : bytes) { sb.append(String.format("%02X", b)) } boolean same = false process.inputStream.readLines().forEach { same |= it.startsWith(sb.toString().toLowerCase()) } DevServer.closeProcess(process) if (same) { println("Skipping $archive") return } println("Uploading $archive") process = new ProcessBuilder("ssh", host, "-T", "rm $base/${archive.name.replace("-all", "")}").start() process.waitFor() DevServer.closeProcess(process) process = new ProcessBuilder("scp", archive.absolutePath, "$host:~/$base/${archive.name.replace("-all", "")}").start(); process.waitFor() DevServer.closeProcess(process) println("Uploaded $archive") } } void startDevServer() { def devPy = new StringBuilder().append("dev.py") if (port != null) devPy.append(" --port $port") if (worldName != null) devPy.append(" -w $template/$worldName") if (plugins != null) devPy.append(" -p $plugins") if (profile != null) devPy.append(" --profile") if (forceUpgrade != null) devPy.append(" --forceUpgrade") if (jar != null) devPy.append(" --jar $jar") for (Map.Entry dParam : dParams.entrySet()) { devPy.append(" -D${dParam.key}=${dParam.value}") } devPy.append(" $template") println("Starting $template with command ${devPy.toString()}") def process = new ProcessBuilder("ssh", host, "-T", devPy.toString()).start() def processOutput = new BufferedReader(new InputStreamReader(process.inputStream)) new Thread({ while (running) { if (processOutput.ready()) { println(processOutput.readLine()) } } processOutput.close() process.errorStream.close() }).start() processInput = new BufferedWriter(new OutputStreamWriter(process.outputStream)) def input = new BufferedReader(new InputStreamReader(System.in)) new Thread({ while (running) { def text = input.readLine() if (text == null) break processInput.write(text) processInput.newLine() processInput.flush() } processInput.close() }).start() process.waitFor() processInput = null running = false } } class FightServer extends DevServer { @Input @Optional Integer checkSchemID = 0 @Input @Optional Integer prepareSchemID = 0 @Input @Optional Integer replay = 0 @Input @Optional String config = null @Input @Optional // Property: fightID Integer eventKampfID = 0 @Input @Optional UUID blueLeader = null @Input @Optional UUID redLeader = null FightServer() { super() doFirst { if (checkSchemID != 0) dParams.put("checkSchemID", "$checkSchemID") if (prepareSchemID != 0) dParams.put("prepareSchemID", "$prepareSchemID") if (replay != 0) dParams.put("replay", "$replay") if (eventKampfID != 0) dParams.put("fightID", "$eventKampfID") if (blueLeader != null) dParams.put("blueLeader", blueLeader.toString()) if (redLeader != null) dParams.put("redLeader", redLeader.toString()) if (config != null) dParams.put("config", config) } } }