diff --git a/build.gradle b/build.gradle index e23ce3c..b6a38df 100644 --- a/build.gradle +++ b/build.gradle @@ -5,12 +5,13 @@ plugins { id 'application' id 'maven-publish' id 'org.cadixdev.licenser' version '0.6.1' - id "com.github.gmazzo.buildconfig" version "5.2.0" + id 'eclipse' + id "com.github.gmazzo.buildconfig" version "5.2.0" } def legacyVersion = "1.7.6" group 'net.legacyfabric' -version '1.5.2' +version '1.5.3' version = legacyVersion + "+fabricmc." + project.version diff --git a/src/main/java/net/fabricmc/meta/FabricMeta.java b/src/main/java/net/fabricmc/meta/FabricMeta.java index 77cc264..c762695 100644 --- a/src/main/java/net/fabricmc/meta/FabricMeta.java +++ b/src/main/java/net/fabricmc/meta/FabricMeta.java @@ -16,21 +16,60 @@ package net.fabricmc.meta; -import net.fabricmc.meta.data.VersionDatabase; -import net.fabricmc.meta.web.WebServer; -import net.legacyfabric.meta.data.LegacyVersionDatabase; - -import javax.xml.stream.XMLStreamException; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -public class FabricMeta { +import com.google.gson.stream.JsonReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.meta.data.VersionDatabase; +import net.fabricmc.meta.utils.Reference; +import net.fabricmc.meta.web.WebServer; +public class FabricMeta { public static volatile VersionDatabase database; + private static final Logger LOGGER = LoggerFactory.getLogger(VersionDatabase.class); + private static final Map config = new HashMap<>(); + private static boolean configInitialized; + private static URL heartbeatUrl; // URL pinged with every successful update() + public static void main(String[] args) { + Path configFile = Paths.get("config.json"); + + if (Files.exists(configFile)) { + try (JsonReader reader = new JsonReader(Files.newBufferedReader(configFile))) { + reader.beginObject(); + + while (reader.hasNext()) { + config.put(reader.nextName(), reader.nextString()); + } + + reader.endObject(); + + String heartbeatUrlString = config.get("heartbeatUrl"); + + if (heartbeatUrlString != null) { + heartbeatUrl = new URL(heartbeatUrlString); + } + } catch (IOException | IllegalStateException e) { + throw new RuntimeException("malformed config in "+configFile, e); + } + } + + configInitialized = true; + + LOGGER.info("Starting with local maven {}", Reference.LOCAL_FABRIC_MAVEN_URL); update(); @@ -43,13 +82,38 @@ public static void main(String[] args) { private static void update(){ try { database = VersionDatabase.generate(new LegacyVersionDatabase()); - } catch (IOException | XMLStreamException e) { - if(database == null){ - throw new RuntimeException(e); + updateHeartbeat(); + } catch (Throwable t) { + if (database == null){ + throw new RuntimeException(t); } else { - e.printStackTrace(); + LOGGER.warn("update failed", t); + } + } + } + + private static void updateHeartbeat() { + if (heartbeatUrl == null) return; + + try { + HttpURLConnection conn = (HttpURLConnection) heartbeatUrl.openConnection(); + conn.setRequestMethod("HEAD"); + conn.setConnectTimeout(500); + conn.setReadTimeout(500); + + int status = conn.getResponseCode(); + + if (status != HttpURLConnection.HTTP_OK) { + LOGGER.warn("heartbeat request failed with status {}", status); } + } catch (IOException e) { + LOGGER.warn("heartbeat request failed: {}", e.toString()); } } + public static Map getConfig() { + if (!configInitialized) throw new IllegalStateException("accessing config before initialization"); // to catch any accidental early access through etc + + return config; + } } diff --git a/src/main/java/net/fabricmc/meta/data/VersionDatabase.java b/src/main/java/net/fabricmc/meta/data/VersionDatabase.java index 6c1ca77..ca61aea 100644 --- a/src/main/java/net/fabricmc/meta/data/VersionDatabase.java +++ b/src/main/java/net/fabricmc/meta/data/VersionDatabase.java @@ -17,14 +17,8 @@ package net.fabricmc.meta.data; -import net.fabricmc.meta.utils.MinecraftLauncherMeta; -import net.fabricmc.meta.utils.PomParser; -import net.fabricmc.meta.web.models.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.impl.SimpleLoggerFactory; +import static net.fabricmc.meta.utils.Reference.LOCAL_FABRIC_MAVEN_URL; -import javax.xml.stream.XMLStreamException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -33,13 +27,24 @@ import java.util.function.Function; import java.util.stream.Collectors; -public class VersionDatabase { - public static final String MAVEN_URL = "https://maven.fabricmc.net/"; +import javax.xml.stream.XMLStreamException; - public static final PomParser MAPPINGS_PARSER = new PomParser(MAVEN_URL + "net/fabricmc/yarn/maven-metadata.xml"); - public static final PomParser INTERMEDIARY_PARSER = new PomParser(MAVEN_URL + "net/fabricmc/intermediary/maven-metadata.xml"); - public static final PomParser LOADER_PARSER = new PomParser(MAVEN_URL + "net/fabricmc/fabric-loader/maven-metadata.xml"); - public static final PomParser INSTALLER_PARSER = new PomParser(MAVEN_URL + "net/fabricmc/fabric-installer/maven-metadata.xml"); +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.meta.utils.MinecraftLauncherMeta; +import net.fabricmc.meta.utils.PomParser; +import net.fabricmc.meta.web.models.BaseVersion; +import net.fabricmc.meta.web.models.MavenBuildGameVersion; +import net.fabricmc.meta.web.models.MavenBuildVersion; +import net.fabricmc.meta.web.models.MavenUrlVersion; +import net.fabricmc.meta.web.models.MavenVersion; + +public class VersionDatabase { + public static final PomParser MAPPINGS_PARSER = new PomParser(LOCAL_FABRIC_MAVEN_URL + "net/fabricmc/yarn/maven-metadata.xml"); + public static final PomParser INTERMEDIARY_PARSER = new PomParser(LOCAL_FABRIC_MAVEN_URL + "net/fabricmc/intermediary/maven-metadata.xml"); + public static final PomParser LOADER_PARSER = new PomParser(LOCAL_FABRIC_MAVEN_URL + "net/fabricmc/fabric-loader/maven-metadata.xml"); + public static final PomParser INSTALLER_PARSER = new PomParser(LOCAL_FABRIC_MAVEN_URL + "net/fabricmc/fabric-installer/maven-metadata.xml"); private static final ArrayList incorrectVersions = new ArrayList<>(); private static final Logger LOGGER = LoggerFactory.getLogger(VersionDatabase.class); diff --git a/src/main/java/net/fabricmc/meta/utils/LoaderMeta.java b/src/main/java/net/fabricmc/meta/utils/LoaderMeta.java index 8930625..b1f28fd 100644 --- a/src/main/java/net/fabricmc/meta/utils/LoaderMeta.java +++ b/src/main/java/net/fabricmc/meta/utils/LoaderMeta.java @@ -16,22 +16,20 @@ package net.fabricmc.meta.utils; -import com.google.gson.JsonObject; -import net.fabricmc.meta.data.VersionDatabase; -import net.fabricmc.meta.web.WebServer; -import net.fabricmc.meta.web.models.LoaderInfoBase; -import org.apache.commons.io.FileUtils; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.net.URL; -public class LoaderMeta { +import com.google.gson.JsonObject; +import org.apache.commons.io.FileUtils; + +import net.fabricmc.meta.web.WebServer; +import net.fabricmc.meta.web.models.LoaderInfoBase; +public class LoaderMeta { public static final File BASE_DIR = new File("metadata"); - public static final String MAVEN_URL = "https://maven.fabricmc.net/"; public static JsonObject getMeta(LoaderInfoBase loaderInfo){ String loaderMaven = loaderInfo.getLoader().getMaven(); @@ -42,7 +40,7 @@ public static JsonObject getMeta(LoaderInfoBase loaderInfo){ File launcherMetaFile = new File(BASE_DIR, path + "/" + filename); if(!launcherMetaFile.exists()){ try { - String url = String.format("%s%s/%s", MAVEN_URL, path, filename); + String url = String.format("%s%s/%s", Reference.LOCAL_FABRIC_MAVEN_URL, path, filename); System.out.println("Downloading " + url); FileUtils.copyURLToFile(new URL(url), launcherMetaFile); } catch (IOException e) { diff --git a/src/main/java/net/fabricmc/meta/utils/MinecraftLauncherMeta.java b/src/main/java/net/fabricmc/meta/utils/MinecraftLauncherMeta.java index 69e7d2a..316954e 100644 --- a/src/main/java/net/fabricmc/meta/utils/MinecraftLauncherMeta.java +++ b/src/main/java/net/fabricmc/meta/utils/MinecraftLauncherMeta.java @@ -17,17 +17,19 @@ package net.fabricmc.meta.utils; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import org.apache.commons.io.IOUtils; - import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; -public class MinecraftLauncherMeta { +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.apache.commons.io.IOUtils; +public class MinecraftLauncherMeta { public static final Gson GSON = new GsonBuilder().create(); List versions; @@ -46,7 +48,7 @@ public static MinecraftLauncherMeta getMeta() throws IOException { } public static MinecraftLauncherMeta getExperimentalMeta() throws IOException { - String url = "https://maven.fabricmc.net/net/minecraft/experimental_versions.json"; + String url = Reference.LOCAL_FABRIC_MAVEN_URL+"net/minecraft/experimental_versions.json"; String json = IOUtils.toString(new URL(url), StandardCharsets.UTF_8); return GSON.fromJson(json, MinecraftLauncherMeta.class); } diff --git a/src/main/java/net/fabricmc/meta/utils/PomParser.java b/src/main/java/net/fabricmc/meta/utils/PomParser.java index 560ac61..d59f118 100644 --- a/src/main/java/net/fabricmc/meta/utils/PomParser.java +++ b/src/main/java/net/fabricmc/meta/utils/PomParser.java @@ -16,14 +16,9 @@ package net.fabricmc.meta.utils; -import net.fabricmc.meta.web.models.BaseVersion; - -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamConstants; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; import java.io.IOException; import java.net.URL; +import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -34,6 +29,13 @@ import java.util.function.Function; import java.util.stream.Collectors; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import net.fabricmc.meta.web.models.BaseVersion; + public class PomParser { public String path; @@ -49,14 +51,24 @@ private void load() throws IOException, XMLStreamException { versions.clear(); URL url = new URL(path); - XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(url.openStream()); - while (reader.hasNext()) { - if (reader.next() == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals("version")) { - String text = reader.getElementText(); - versions.add(text); + URLConnection conn = url.openConnection(); + conn.setConnectTimeout(3000); + conn.setReadTimeout(3000); + XMLStreamReader reader = null; + + try { + reader = XMLInputFactory.newInstance().createXMLStreamReader(conn.getInputStream()); + + while (reader.hasNext()) { + if (reader.next() == XMLStreamConstants.START_ELEMENT && reader.getLocalName().equals("version")) { + String text = reader.getElementText(); + versions.add(text); + } } + } finally { + if (reader != null) reader.close(); } - reader.close(); + Collections.reverse(versions); latestVersion = versions.get(0); } @@ -89,9 +101,9 @@ public List getMeta(Function function, Str // Read a file containing a new line separated list of versions that should not be marked as stable. List unstableVersions = Files.readAllLines(unstableVersionsPath); list.stream() - .filter(v -> !unstableVersions.contains(v.getVersion())) - .findFirst() - .ifPresent(v -> v.setStable(true)); + .filter(v -> !unstableVersions.contains(v.getVersion())) + .findFirst() + .ifPresent(v -> v.setStable(true)); } else { stableIdentifier.process(list); } diff --git a/src/main/java/net/fabricmc/meta/utils/Reference.java b/src/main/java/net/fabricmc/meta/utils/Reference.java new file mode 100644 index 0000000..a3d145d --- /dev/null +++ b/src/main/java/net/fabricmc/meta/utils/Reference.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.meta.utils; + +import net.fabricmc.meta.FabricMeta; + +public final class Reference { + /** + * Fabric maven url to expose to the user. + * + *

This shouldn't be directly accessed by this meta server instance! + */ + public static final String FABRIC_MAVEN_URL = "https://maven.fabricmc.net/"; + + /** + * Fabric maven url to access from this meta server instance. + * + *

This is not to be included in any output data! + */ + public static final String LOCAL_FABRIC_MAVEN_URL = FabricMeta.getConfig().getOrDefault("localFabricMavenUrl", FABRIC_MAVEN_URL); +} diff --git a/src/main/java/net/fabricmc/meta/web/ProfileHandler.java b/src/main/java/net/fabricmc/meta/web/ProfileHandler.java index 6257017..80fbe24 100644 --- a/src/main/java/net/fabricmc/meta/web/ProfileHandler.java +++ b/src/main/java/net/fabricmc/meta/web/ProfileHandler.java @@ -38,7 +38,7 @@ import net.legacyfabric.meta.data.LegacyVersionDatabase; import org.apache.commons.io.IOUtils; -import net.fabricmc.meta.utils.LoaderMeta; +import net.fabricmc.meta.utils.Reference; import net.fabricmc.meta.web.models.LoaderInfoV2; public class ProfileHandler { @@ -116,8 +116,8 @@ private static JsonObject buildProfileJson(LoaderInfoV2 info, String side) { JsonObject librariesObject = launcherMeta.get("libraries").getAsJsonObject(); // Build the libraries array with the existing libs + loader and intermediary JsonArray libraries = (JsonArray) librariesObject.get("common"); - libraries.add(getLibrary(info.getIntermediary().getMaven(), LegacyVersionDatabase.MAVEN_URL)); - libraries.add(getLibrary(info.getLoader().getMaven(), VersionDatabase.MAVEN_URL)); + libraries.add(formatLibrary(info.getIntermediary().getMaven(), LegacyVersionDatabase.MAVEN_URL)); + libraries.add(formatLibrary(info.getLoader().getMaven(), Reference.FABRIC_MAVEN_URL)); if (librariesObject.has(side)) { libraries.addAll(librariesObject.get(side).getAsJsonArray()); @@ -163,7 +163,7 @@ private static JsonObject buildProfileJson(LoaderInfoV2 info, String side) { return profile; } - private static JsonObject getLibrary(String mavenPath, String url) { + private static JsonObject formatLibrary(String mavenPath, String url) { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("name", mavenPath); jsonObject.addProperty("url", url); diff --git a/src/main/java/net/fabricmc/meta/web/ServerBootstrap.java b/src/main/java/net/fabricmc/meta/web/ServerBootstrap.java index 901d3f0..4db1d41 100644 --- a/src/main/java/net/fabricmc/meta/web/ServerBootstrap.java +++ b/src/main/java/net/fabricmc/meta/web/ServerBootstrap.java @@ -17,21 +17,16 @@ package net.fabricmc.meta.web; -import io.javalin.core.util.Header; -import io.javalin.http.BadRequestResponse; -import io.javalin.http.Context; -import io.javalin.http.Handler; -import io.javalin.http.InternalServerErrorResponse; -import net.fabricmc.meta.FabricMeta; -import net.fabricmc.meta.web.models.BaseVersion; -import org.apache.commons.io.FileUtils; - import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.*; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,159 +34,171 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; -public class ServerBootstrap { - private static final Path CACHE_DIR = Paths.get("metadata", "installer"); - private static final Executor WORKER_EXECUTOR = Executors.newSingleThreadExecutor(); - - public static void setup() { - // http://localhost:5555/v2/versions/loader/1.17.1/0.12.0/0.8.0/server/jar - WebServer.javalin.get("/v2/versions/loader/:game_version/:loader_version/:installer_version/server/jar", boostrapHandler()); - } - - private static Handler boostrapHandler() { - return ctx -> { - if (!ctx.queryParamMap().isEmpty()) { - // Cannot really afford people to cache bust this. - throw new BadRequestResponse("Query params not allowed on this endpoint."); - } - - final String installerVersion = getAndValidateVersion(ctx, FabricMeta.database.installer, "installer_version"); - final String gameVersion = getAndValidateVersion(ctx, FabricMeta.database.game, "game_version"); - final String loaderVersion = getAndValidateVersion(ctx, FabricMeta.database.getAllLoader(), "loader_version"); - - validateLoaderVersion(loaderVersion); - validateInstallerVersion(installerVersion); - - // Set the filename and cache headers - final String filename = String.format("fabric-server-mc.%s-loader.%s-launcher.%s.jar", gameVersion, loaderVersion, installerVersion); - ctx.header(Header.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", filename)); - final String cacheControl = String.format("public, max-age=%d", getCacheDuration(ctx)); - ctx.header(Header.CACHE_CONTROL, cacheControl); - ctx.contentType("application/java-archive"); - - ctx.result(getResultStream(installerVersion, gameVersion, loaderVersion)); - }; - } - - private static String getAndValidateVersion(Context ctx, List versions, String name) { - String version = ctx.pathParam(name); - - for (V v : versions) { - if (version.equals("stable") && v.isStable()) { - return v.getVersion(); - } - - if (v.getVersion().equals(version)) { - return version; - } - } - - throw new BadRequestResponse("Unable to find valid version for " + name); - } - - private static void validateLoaderVersion(String loaderVersion) { - String[] versionSplit = loaderVersion.split("\\."); - - // future 1.x versions - if (Integer.parseInt(versionSplit[0]) > 0) { - return; - } - - // 0.12.x or newer - if (Integer.parseInt(versionSplit[1]) >= 12) { - return; - } - - throw new BadRequestResponse("Fabric loader 0.12 or higher is required for unattended server installs. Please use a newer fabric loader version, or the full installer."); - } - - private static void validateInstallerVersion(String installerVersion) { - String[] versionSplit = installerVersion.split("\\."); - - // future 1.x versions - if (Integer.parseInt(versionSplit[0]) > 0) { - return; - } - - // 0.8.x or newer - if (Integer.parseInt(versionSplit[1]) >= 8) { - return; - } - - throw new BadRequestResponse("Fabric Installer 0.8 or higher is required for unattended server installs."); - } - - private static CompletableFuture getResultStream(String installerVersion, String gameVersion, String loaderVersion) { - Path bundledJar = CACHE_DIR.resolve(String.format("fabric-server-mc.%s-loader.%s-launcher.%s.jar", gameVersion, loaderVersion, installerVersion)); - - if (!Files.exists(bundledJar)) { - return CompletableFuture.supplyAsync(() -> { - try { - if (!Files.exists(bundledJar)) { - Path installerJar = getInstallerJar(installerVersion); - writePropertiesToJar(installerJar, bundledJar, loaderVersion, gameVersion); - } - - return Files.newInputStream(bundledJar); - } catch (IOException e) { - e.printStackTrace(); - throw new InternalServerErrorResponse("Failed to generate bundled jar"); - } - }, WORKER_EXECUTOR); - } - - try { - return CompletableFuture.completedFuture(Files.newInputStream(bundledJar)); - } catch (IOException e) { - e.printStackTrace(); - throw new InternalServerErrorResponse("Failed to serve bundled jar"); - } - } - - private static Path getInstallerJar(String installerVersion) throws IOException { - Path installerJar = CACHE_DIR.resolve(String.format("fabric-installer-%s.jar", installerVersion)); - - if (Files.exists(installerJar)) { - return installerJar; - } - - return downloadInstallerJar(installerJar, installerVersion); - } - - private static Path downloadInstallerJar(Path jar, String installerVersion) throws IOException { - final String url = String.format(getInstallerURL(), installerVersion); - - System.out.println("Downloading: " + url); - FileUtils.copyURLToFile(new URL(url), jar.toFile()); - return jar; - } - - private static void writePropertiesToJar(Path inputJar, Path outputJar, String loaderVersion, String gameVersion) throws IOException { - String data = String.format("fabric-loader-version=%s\ngame-version=%s", loaderVersion, gameVersion); - - Path workingFile = Paths.get(outputJar.toAbsolutePath() + ".tmp"); - Files.copy(inputJar, workingFile); - - Map env = new HashMap<>(); - env.put("create", "true"); - URI uri = URI.create("jar:" + workingFile.toUri()); - - try (FileSystem zipFs = FileSystems.newFileSystem(uri, env)) { - Files.write(zipFs.getPath("install.properties"), data.getBytes(StandardCharsets.UTF_8)); - } +import io.javalin.core.util.Header; +import io.javalin.http.BadRequestResponse; +import io.javalin.http.Context; +import io.javalin.http.Handler; +import io.javalin.http.InternalServerErrorResponse; +import org.apache.commons.io.FileUtils; - Files.copy(workingFile, outputJar); - Files.delete(workingFile); - } +import net.fabricmc.meta.FabricMeta; +import net.fabricmc.meta.utils.Reference; +import net.fabricmc.meta.web.models.BaseVersion; - private static int getCacheDuration(Context ctx) { - if (ctx.pathParamMap().containsValue("stable")) { - return 120; - } - return 86400; - } +public class ServerBootstrap { + private static final Path CACHE_DIR = Paths.get("metadata", "installer"); + private static final Executor WORKER_EXECUTOR = Executors.newSingleThreadExecutor(); + + public static void setup() { + // http://localhost:5555/v2/versions/loader/1.17.1/0.12.0/0.8.0/server/jar + WebServer.javalin.get("/v2/versions/loader/:game_version/:loader_version/:installer_version/server/jar", boostrapHandler()); + } + + private static Handler boostrapHandler() { + return ctx -> { + if (!ctx.queryParamMap().isEmpty()) { + // Cannot really afford people to cache bust this. + throw new BadRequestResponse("Query params not allowed on this endpoint."); + } + + final String installerVersion = getAndValidateVersion(ctx, FabricMeta.database.installer, "installer_version"); + final String gameVersion = getAndValidateVersion(ctx, FabricMeta.database.game, "game_version"); + final String loaderVersion = getAndValidateVersion(ctx, FabricMeta.database.getAllLoader(), "loader_version"); + + validateLoaderVersion(loaderVersion); + validateInstallerVersion(installerVersion); + + // Set the filename and cache headers + final String filename = String.format("fabric-server-mc.%s-loader.%s-launcher.%s.jar", gameVersion, loaderVersion, installerVersion); + ctx.header(Header.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", filename)); + final String cacheControl = String.format("public, max-age=%d", getCacheDuration(ctx)); + ctx.header(Header.CACHE_CONTROL, cacheControl); + ctx.contentType("application/java-archive"); + + ctx.result(getResultStream(installerVersion, gameVersion, loaderVersion)); + }; + } + + private static String getAndValidateVersion(Context ctx, List versions, String name) { + String version = ctx.pathParam(name); + + for (V v : versions) { + if (version.equals("stable") && v.isStable()) { + return v.getVersion(); + } + + if (v.getVersion().equals(version)) { + return version; + } + } + + throw new BadRequestResponse("Unable to find valid version for " + name); + } + + private static void validateLoaderVersion(String loaderVersion) { + String[] versionSplit = loaderVersion.split("\\."); + + // future 1.x versions + if (Integer.parseInt(versionSplit[0]) > 0) { + return; + } + + // 0.12.x or newer + if (Integer.parseInt(versionSplit[1]) >= 12) { + return; + } + + throw new BadRequestResponse("Fabric loader 0.12 or higher is required for unattended server installs. Please use a newer fabric loader version, or the full installer."); + } + + private static void validateInstallerVersion(String installerVersion) { + String[] versionSplit = installerVersion.split("\\."); + + // future 1.x versions + if (Integer.parseInt(versionSplit[0]) > 0) { + return; + } + + // 0.8.x or newer + if (Integer.parseInt(versionSplit[1]) >= 8) { + return; + } + + throw new BadRequestResponse("Fabric Installer 0.8 or higher is required for unattended server installs."); + } + + private static CompletableFuture getResultStream(String installerVersion, String gameVersion, String loaderVersion) { + Path bundledJar = CACHE_DIR.resolve(String.format("fabric-server-mc.%s-loader.%s-launcher.%s.jar", gameVersion, loaderVersion, installerVersion)); + + if (!Files.exists(bundledJar)) { + return CompletableFuture.supplyAsync(() -> { + try { + if (!Files.exists(bundledJar)) { + Path installerJar = getInstallerJar(installerVersion); + writePropertiesToJar(installerJar, bundledJar, loaderVersion, gameVersion); + } + + return Files.newInputStream(bundledJar); + } catch (IOException e) { + e.printStackTrace(); + throw new InternalServerErrorResponse("Failed to generate bundled jar"); + } + }, WORKER_EXECUTOR); + } + + try { + return CompletableFuture.completedFuture(Files.newInputStream(bundledJar)); + } catch (IOException e) { + e.printStackTrace(); + throw new InternalServerErrorResponse("Failed to serve bundled jar"); + } + } + + private static Path getInstallerJar(String installerVersion) throws IOException { + Path installerJar = CACHE_DIR.resolve(String.format("fabric-installer-%s.jar", installerVersion)); + + if (Files.exists(installerJar)) { + return installerJar; + } + + return downloadInstallerJar(installerJar, installerVersion); + } + + private static Path downloadInstallerJar(Path jar, String installerVersion) throws IOException { + final String url = String.format(getInstallerURL(), installerVersion); + + System.out.println("Downloading: " + url); + FileUtils.copyURLToFile(new URL(url), jar.toFile()); + return jar; + } + + private static void writePropertiesToJar(Path inputJar, Path outputJar, String loaderVersion, String gameVersion) throws IOException { + String data = String.format("fabric-loader-version=%s\ngame-version=%s", loaderVersion, gameVersion); + + Path workingFile = Paths.get(outputJar.toAbsolutePath() + ".tmp"); + Files.copy(inputJar, workingFile); + + Map env = new HashMap<>(); + env.put("create", "true"); + URI uri = URI.create("jar:" + workingFile.toUri()); + + try (FileSystem zipFs = FileSystems.newFileSystem(uri, env)) { + Files.write(zipFs.getPath("install.properties"), data.getBytes(StandardCharsets.UTF_8)); + } + + Files.copy(workingFile, outputJar); + Files.delete(workingFile); + } + + private static int getCacheDuration(Context ctx) { + if (ctx.pathParamMap().containsValue("stable")) { + return 120; + } + return 86400; + } private static String getInstallerURL() { + // Reference.LOCAL_FABRIC_MAVEN_URL+"net/fabricmc/fabric-installer/%1$s/fabric-installer-%1$s-server.jar" return "https://maven.legacyfabric.net/net/legacyfabric/fabric-installer/%1$s/fabric-installer-%1$s-server.jar"; } } diff --git a/src/main/java/net/fabricmc/meta/web/models/MavenUrlVersion.java b/src/main/java/net/fabricmc/meta/web/models/MavenUrlVersion.java index 9f60d8f..1b49e8f 100644 --- a/src/main/java/net/fabricmc/meta/web/models/MavenUrlVersion.java +++ b/src/main/java/net/fabricmc/meta/web/models/MavenUrlVersion.java @@ -17,25 +17,24 @@ package net.fabricmc.meta.web.models; -import net.fabricmc.meta.data.VersionDatabase; +import net.fabricmc.meta.utils.Reference; public class MavenUrlVersion extends MavenVersion { + public final String url; - public final String url; - - public MavenUrlVersion(String maven) { - super(maven); - String[] split = maven.split(":"); - this.url = String.format("%s%s/%s/%s/%s-%s.jar", getMavenUrl(), - split[0].replaceAll("\\.", "/"), - split[1], - split[2], - split[1], - split[2] - ); - } + public MavenUrlVersion(String maven) { + super(maven); + String[] split = maven.split(":"); + this.url = String.format("%s%s/%s/%s/%s-%s.jar", getMavenUrl(), + split[0].replace('.', '/'), + split[1], + split[2], + split[1], + split[2] + ); + } public String getMavenUrl() { - return VersionDatabase.MAVEN_URL; + return Reference.FABRIC_MAVEN_URL; } }