commit e58680b78b4cced39ac9313e99378edabddd4e97 Author: KartoffelChipss Date: Sat May 23 23:13:12 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6314db0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Created by https://www.toptal.com/developers/gitignore/api/java,maven +# Edit at https://www.toptal.com/developers/gitignore?templates=java,maven + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# End of https://www.toptal.com/developers/gitignore/api/java,maven diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..2b7b41e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..3c77c62 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..858f48c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..4d2090f --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + { + "customColor": "", + "associatedIndex": 1 +} + + + + { + "keyToString": { + "ModuleVcsDetector.initialDetectionPerformed": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "RunOnceActivity.typescript.service.memoryLimit.init": "true", + "codeWithMe.voiceChat.enabledByDefault": "false", + "git-widget-placeholder": "main", + "kotlin-language-version-configured": "true", + "vue.rearranger.settings.migration": "true" + } +} + + + + 1779565206518 + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e8ac751 --- /dev/null +++ b/pom.xml @@ -0,0 +1,180 @@ + + + + + 4.0.0 + + org.strassburger + mc-run + 0.0.7 + maven-plugin + + MCRun Maven Plugin + + + ${maven.version} + + + + UTF-8 + 21 + 21 + 3.3.9 + 3.13.1 + + + + + org.apache.maven + maven-plugin-api + ${maven.version} + provided + + + org.apache.maven + maven-core + ${maven.version} + provided + + + org.apache.maven + maven-artifact + ${maven.version} + provided + + + org.apache.maven + maven-compat + ${maven.version} + test + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.0 + provided + + + junit + junit + 4.12 + test + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + 3.3.0 + test + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-plugin-plugin + ${maven-plugin-plugin.version} + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + maven-invoker-plugin + 3.1.0 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin-plugin.version} + + mcrun + true + + + + mojo-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + + + + + run-its + + + + + org.apache.maven.plugins + maven-invoker-plugin + 3.1.0 + + true + ${project.build.directory}/it + + */pom.xml + + verify + ${project.build.directory}/local-repo + src/it/settings.xml + + clean + test-compile + + + + + integration-test + + install + integration-test + verify + + + + + + + + + diff --git a/src/main/java/org/strassburger/mcrun/RunServerMojo.java b/src/main/java/org/strassburger/mcrun/RunServerMojo.java new file mode 100644 index 0000000..5f8c843 --- /dev/null +++ b/src/main/java/org/strassburger/mcrun/RunServerMojo.java @@ -0,0 +1,95 @@ +package org.strassburger.mcrun; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.strassburger.mcrun.downloader.PaperServerJarDownloader; +import org.strassburger.mcrun.downloader.ServerJarDownloadException; +import org.strassburger.mcrun.downloader.ServerJarDownloader; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +@Mojo(name = "run", defaultPhase = LifecyclePhase.PACKAGE) +public class RunServerMojo extends AbstractMojo { + @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.jar") + private File pluginJar; + + @Parameter(defaultValue = "${project.basedir}/server") + private File serverDirectory; + + @Parameter(defaultValue = "server.jar") + private String serverJarName; + + @Parameter(defaultValue = "paper") + private String serverSoftware; + + @Parameter(required = true) + private String mcVersion; + + @Parameter(defaultValue = "2G") + private String memory; + + @Parameter(defaultValue = "true") + private boolean acceptEula; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + getLog().info("Server directory: " + serverDirectory.getAbsolutePath()); + + File pluginsDir = new File(serverDirectory, "plugins"); + pluginsDir.mkdirs(); + try { + Files.copy(pluginJar.toPath(), + new File(pluginsDir, pluginJar.getName()).toPath(), + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new MojoExecutionException("Failed to copy plugin jar file: " + pluginJar.getAbsolutePath(), e); + } + + try { + ServerJarDownloader jarDownloader = ServerJarDownloader.forServerSoftware(serverSoftware) + .withLog(getLog()).withServerDirectory(serverDirectory) + .withServerJarName(serverJarName).withMcVersion(mcVersion) + .build(); + jarDownloader.run(); + } catch (ServerJarDownloadException e) { + throw new MojoExecutionException("Failed to download server jar", e); + } + if (acceptEula) acceptEula(); + + ProcessBuilder pb = new ProcessBuilder( + "java", "-Xmx" + memory, "-Xms" + memory, + "-jar", serverJarName, "--nogui" + ); + pb.directory(serverDirectory); + pb.inheritIO(); + Process process; + try { + process = pb.start(); + } catch (IOException e) { + throw new MojoExecutionException("Failed to start server: " + e.getMessage(), e); + } + try { + process.waitFor(); + } catch (InterruptedException e) { + throw new MojoExecutionException("Failed to start server: " + e.getMessage(), e); + } + } + + private void acceptEula() throws MojoExecutionException { + File eula = new File(serverDirectory, "eula.txt"); + if (eula.exists()) return; + try { + Files.writeString(eula.toPath(), "eula=true\n"); + getLog().info("eula.txt created and accepted."); + } catch (IOException e) { + throw new MojoExecutionException("Failed to write eula.txt: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/org/strassburger/mcrun/downloader/PaperServerJarDownloader.java b/src/main/java/org/strassburger/mcrun/downloader/PaperServerJarDownloader.java new file mode 100644 index 0000000..02cf25e --- /dev/null +++ b/src/main/java/org/strassburger/mcrun/downloader/PaperServerJarDownloader.java @@ -0,0 +1,59 @@ +package org.strassburger.mcrun.downloader; + +import org.apache.maven.plugin.logging.Log; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public class PaperServerJarDownloader extends ServerJarDownloader { + + public PaperServerJarDownloader(Log log, File serverDirectory, String serverJarName, String mcVersion) { + super(log, serverDirectory, serverJarName, mcVersion); + } + + @Override + protected void downloadServerJar() throws ServerJarDownloadException { + File jarFile = getServerJar(); + + try { + getLog().info("Fetching latest Paper build info for " + getMcVersion() + "..."); + String apiUrl = "https://fill.papermc.io/v3/projects/paper/versions/" + getMcVersion() + "/builds/latest"; + String json = fetchUrl(apiUrl); + + String downloadUrl = parseDownloadUrl(json); + getLog().info("Downloading Paper from: " + downloadUrl); + + try (var in = new java.net.URI(downloadUrl).toURL().openStream()) { + Files.copy(in, jarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + getLog().info("Paper downloaded to: " + jarFile.getAbsolutePath()); + } catch (Exception e) { + throw new ServerJarDownloadException("Failed to download Paper: " + e.getMessage(), e); + } + } + + private String fetchUrl(String url) throws Exception { + var connection = new java.net.URI(url).toURL().openConnection(); + connection.setRequestProperty("Accept", "application/json"); + try (var in = connection.getInputStream()) { + return new String(in.readAllBytes()); + } + } + + private String parseDownloadUrl(String json) { + // Parse: .downloads["server:default"].url + int downloadsIdx = json.indexOf("\"downloads\""); + int serverDefaultIdx = json.indexOf("\"server:default\"", downloadsIdx); + int urlIdx = json.indexOf("\"url\"", serverDefaultIdx); + int urlStart = json.indexOf("\"", urlIdx + 5) + 1; + int urlEnd = json.indexOf("\"", urlStart); + + if (urlStart <= 0 || urlEnd <= 0) { + throw new ServerJarDownloadException("Could not parse download URL from Paper API response"); + } + + return json.substring(urlStart, urlEnd); + } +} diff --git a/src/main/java/org/strassburger/mcrun/downloader/ServerJarDownloadException.java b/src/main/java/org/strassburger/mcrun/downloader/ServerJarDownloadException.java new file mode 100644 index 0000000..7734810 --- /dev/null +++ b/src/main/java/org/strassburger/mcrun/downloader/ServerJarDownloadException.java @@ -0,0 +1,11 @@ +package org.strassburger.mcrun.downloader; + +public class ServerJarDownloadException extends RuntimeException { + public ServerJarDownloadException(String message) { + super(message); + } + + public ServerJarDownloadException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/strassburger/mcrun/downloader/ServerJarDownloader.java b/src/main/java/org/strassburger/mcrun/downloader/ServerJarDownloader.java new file mode 100644 index 0000000..a6d04bd --- /dev/null +++ b/src/main/java/org/strassburger/mcrun/downloader/ServerJarDownloader.java @@ -0,0 +1,89 @@ +package org.strassburger.mcrun.downloader; + +import org.apache.maven.plugin.logging.Log; + +import java.io.File; +import java.util.function.Function; + +public abstract class ServerJarDownloader { + private final Log log; + private final File serverDirectory; + private final String serverJarName; + private final String mcVersion; + + public ServerJarDownloader(Log log, File serverDirectory, String serverJarName, String mcVersion) { + this.log = log; + this.serverDirectory = serverDirectory; + this.serverJarName = serverJarName; + this.mcVersion = mcVersion; + } + + public void run() throws ServerJarDownloadException { + if (getServerJar().exists()) { + log.info("Paper jar already exists, skipping download."); + return; + } + + serverDirectory.mkdirs(); + + downloadServerJar(); + } + + protected Log getLog() { + return log; + } + + protected String getMcVersion() { + return mcVersion; + } + + protected File getServerJar() { + return new File(serverDirectory, serverJarName); + } + + protected abstract void downloadServerJar() throws ServerJarDownloadException; + + public static Builder forServerSoftware(String serverSoftware) throws ServerJarDownloadException { + return switch (serverSoftware) { + case "paper" -> new Builder(b -> + new PaperServerJarDownloader(b.log, b.serverDirectory, b.serverJarName, b.mcVersion)); + default -> throw new ServerJarDownloadException("Invalid server software: " + serverSoftware); + }; + } + + public static class Builder { + private final Function factory; + private Log log; + private File serverDirectory; + private String serverJarName; + private String mcVersion; + + public Builder(Function factory) { + this.factory = factory; + } + + public Builder withLog(Log log) { + this.log = log; + return this; + } + + public Builder withServerDirectory(File serverDirectory) { + this.serverDirectory = serverDirectory; + return this; + } + + public Builder withServerJarName(String serverJarName) { + this.serverJarName = serverJarName; + return this; + } + + public Builder withMcVersion(String mcVersion) { + this.mcVersion = mcVersion; + return this; + } + + public ServerJarDownloader build() { + return factory.apply(this); + } + } +}