Initial commit

This commit is contained in:
2026-05-23 23:13:12 +02:00
commit e58680b78b
12 changed files with 621 additions and 0 deletions
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<Builder, ServerJarDownloader> factory;
private Log log;
private File serverDirectory;
private String serverJarName;
private String mcVersion;
public Builder(Function<Builder, ServerJarDownloader> 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);
}
}
}