Rewrite update system

This commit is contained in:
2026-05-19 16:47:00 -04:00
parent bb5bef7166
commit a7a21c89b6
9 changed files with 571 additions and 181 deletions
+5 -2
View File
@@ -2,6 +2,8 @@ import net.minecrell.pluginyml.paper.PaperPluginDescription
import java.text.SimpleDateFormat
import java.util.*
val paperApiVersion = "26.1.2"
plugins {
java
`maven-publish`
@@ -29,7 +31,7 @@ dependencies {
library("com.zaxxer:HikariCP:7.0.2")
library("com.j256.ormlite:ormlite-jdbc:6.1")
library("org.jetbrains:annotations:26.1.0")
compileOnly("io.papermc.paper:paper-api:26.1.2.build.+")
compileOnly("io.papermc.paper:paper-api:${paperApiVersion}.build.+")
compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") {
exclude("org.bukkit", "bukkit")
}
@@ -53,7 +55,7 @@ paper {
loader = "dev.plex.PlexLibraryManager"
website = "https://plex.us.org"
authors = listOf("Telesphoreo", "taahanis", "supernt")
apiVersion = "26.1.2"
apiVersion = paperApiVersion
foliaSupported = true
generateLibrariesJson = true
// Load BukkitTelnet and LibsDisguises before Plex so the modules register properly
@@ -131,6 +133,7 @@ tasks {
property("buildNumber", if (System.getenv("BUILD_NUMBER") != null) System.getenv("BUILD_NUMBER") else getBuildNumber())
property("date", SimpleDateFormat("MM/dd/yyyy '<light_purple>at<gold>' hh:mm:ss a z").format(Date()))
property("gitCommit", indraGit.commit().get().name.take(7))
property("minecraftVersion", paperApiVersion)
}
}
}
@@ -27,7 +27,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@CommandPermissions(source = RequiredCommandSource.ANY)
@CommandParameters(name = "plex", usage = "/<command> [reload | redis | modules [reload]]", description = "Show information about Plex or reload it")
@CommandParameters(name = "plex", usage = "/<command> [reload | redis | update | modules [reload | update]]", description = "Show information about Plex or reload it")
public class PlexCMD extends ServerCommand
{
// Don't modify this command
@@ -132,11 +132,11 @@ public class PlexCMD extends ServerCommand
{
if (args.length == 1)
{
return Arrays.asList("reload", "redis", "modules");
return Arrays.asList("reload", "redis", "modules", "update");
}
else if (args[0].equalsIgnoreCase("modules"))
{
return List.of("reload");
return Arrays.asList("reload", "update");
}
return Collections.emptyList();
}
@@ -0,0 +1,197 @@
package dev.plex.updater;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
public final class ArtifactMetadata
{
private static final int CURRENT_SCHEMA_VERSION = 1;
private static final Pattern SHA256_PATTERN = Pattern.compile("^[a-fA-F0-9]{64}$");
private int schemaVersion;
private String name;
private String version;
private String buildNumber;
private String commit;
private String channel;
private List<String> minecraftVersions;
private Integer apiCompatibility;
private Integer requiredApiCompatibility;
private String downloadUrl;
private String sha256;
private Long size;
private String publishedAt;
public String name()
{
return name;
}
public String version()
{
return version;
}
public String buildNumber()
{
return buildNumber;
}
public String commit()
{
return commit;
}
public String channel()
{
return channel;
}
public String downloadUrl()
{
return downloadUrl;
}
public String sha256()
{
return sha256;
}
public Long size()
{
return size;
}
public String publishedAt()
{
return publishedAt;
}
public Optional<String> validatePlex(UpdateChannel requestedChannel, String minecraftVersion)
{
Optional<String> commonError = validateCommon(requestedChannel);
if (commonError.isPresent())
{
return commonError;
}
if (!"Plex".equalsIgnoreCase(name))
{
return Optional.of("Plex metadata has unexpected artifact name " + name);
}
if (minecraftVersions == null || !minecraftVersions.contains(minecraftVersion))
{
return Optional.of("metadata does not include Minecraft version " + minecraftVersion);
}
if (apiCompatibility == null)
{
return Optional.of("Plex metadata is missing apiCompatibility");
}
return Optional.empty();
}
public Optional<String> validateModule(UpdateChannel requestedChannel, String moduleName, int apiCompatibility)
{
Optional<String> commonError = validateCommon(requestedChannel);
if (commonError.isPresent())
{
return commonError;
}
if (!moduleName.equalsIgnoreCase(name))
{
return Optional.of("module metadata has unexpected artifact name " + name);
}
if (requiredApiCompatibility == null)
{
return Optional.of("module metadata is missing requiredApiCompatibility");
}
if (requiredApiCompatibility != apiCompatibility)
{
return Optional.of("module metadata requires API compatibility " + requiredApiCompatibility + ", but Plex provides " + apiCompatibility);
}
return Optional.empty();
}
public boolean matchesCurrentBuild(String currentVersion, String currentBuildNumber, String currentCommit)
{
if (!Objects.equals(version, currentVersion))
{
return false;
}
if (isKnown(commit) && isKnown(currentCommit))
{
return commit.equalsIgnoreCase(currentCommit);
}
return Objects.equals(buildNumber, currentBuildNumber);
}
public String fileName()
{
try
{
String path = URI.create(downloadUrl).getPath();
int separatorIndex = path.lastIndexOf('/');
String fileName = separatorIndex >= 0 ? path.substring(separatorIndex + 1) : path;
if (!fileName.isBlank())
{
return URLDecoder.decode(fileName, StandardCharsets.UTF_8);
}
}
catch (IllegalArgumentException ignored)
{
}
return name + "-" + version + ".jar";
}
private Optional<String> validateCommon(UpdateChannel requestedChannel)
{
if (schemaVersion != CURRENT_SCHEMA_VERSION)
{
return Optional.of("metadata schemaVersion " + schemaVersion + " is not supported");
}
if (isBlank(name))
{
return Optional.of("metadata is missing name");
}
if (isBlank(version))
{
return Optional.of("metadata is missing version");
}
if (isBlank(channel))
{
return Optional.of("metadata is missing channel");
}
if (!requestedChannel.id().equalsIgnoreCase(channel))
{
return Optional.of("metadata channel " + channel + " does not match requested channel " + requestedChannel.id());
}
if (requestedChannel == UpdateChannel.STABLE && version.toUpperCase(Locale.ROOT).contains("SNAPSHOT"))
{
return Optional.of("stable metadata must not point to a SNAPSHOT version");
}
if (isBlank(downloadUrl))
{
return Optional.of("metadata is missing downloadUrl");
}
if (isBlank(sha256) || !SHA256_PATTERN.matcher(sha256).matches())
{
return Optional.of("metadata is missing a valid sha256");
}
return Optional.empty();
}
private static boolean isBlank(String value)
{
return value == null || value.isBlank();
}
private static boolean isKnown(String value)
{
return !isBlank(value) && !"unknown".equalsIgnoreCase(value);
}
}
@@ -0,0 +1,36 @@
package dev.plex.updater;
import java.util.Locale;
public enum UpdateChannel
{
STABLE("stable"),
DEV("dev");
private final String id;
UpdateChannel(String id)
{
this.id = id;
}
public String id()
{
return id;
}
public static UpdateChannel fromConfig(String value)
{
if (value == null)
{
return STABLE;
}
return switch (value.trim().toLowerCase(Locale.ROOT))
{
case "dev" -> DEV;
case "stable" -> STABLE;
default -> STABLE;
};
}
}
@@ -0,0 +1,157 @@
package dev.plex.updater;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
public final class UpdateMetadataClient
{
private static final List<String> BASE_URLS = List.of("https://updater.plex.us.org", "https://plex-updater.com");
private final Gson gson = new Gson();
private final UpdateChannel channel;
public UpdateMetadataClient(UpdateChannel channel)
{
this.channel = channel;
}
public ArtifactMetadata fetchPlexLatest(String minecraftVersion) throws MetadataException
{
String path = "/api/v1/projects/Plex/channels/" + channel.id() + "/latest/minecraft/" + encodePathSegment(minecraftVersion) + ".json";
ArtifactMetadata metadata = fetch(path);
Optional<String> validationError = metadata.validatePlex(channel, minecraftVersion);
if (validationError.isPresent())
{
throw new MetadataException(validationError.get(), false);
}
return metadata;
}
public ArtifactMetadata fetchModuleLatest(String moduleName, int apiCompatibility) throws MetadataException
{
String path = "/api/v1/projects/" + encodePathSegment(moduleName) + "/channels/" + channel.id() + "/latest/api/" + apiCompatibility + ".json";
ArtifactMetadata metadata = fetch(path);
Optional<String> validationError = metadata.validateModule(channel, moduleName, apiCompatibility);
if (validationError.isPresent())
{
throw new MetadataException(validationError.get(), false);
}
return metadata;
}
private ArtifactMetadata fetch(String path) throws MetadataException
{
MetadataException notFound = null;
MetadataException failure = null;
for (String baseUrl : BASE_URLS)
{
try
{
return fetch(baseUrl, path);
}
catch (MetadataException e)
{
if (e.notFound())
{
if (notFound == null)
{
notFound = e;
}
continue;
}
failure = e;
}
}
if (failure != null)
{
throw new MetadataException("all metadata endpoints failed for " + path + "; last error: " + failure.getMessage(), false, failure);
}
if (notFound != null)
{
throw notFound;
}
throw new MetadataException("no updater metadata endpoints are available", false);
}
private ArtifactMetadata fetch(String baseUrl, String path) throws MetadataException
{
String url = baseUrl + path;
try
{
HttpURLConnection connection = (HttpURLConnection) URI.create(url).toURL().openConnection();
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.setRequestProperty("Accept", "application/json");
int statusCode = connection.getResponseCode();
if (statusCode == HttpURLConnection.HTTP_NOT_FOUND)
{
throw new MetadataException("no compatible update metadata exists at " + path + " on " + baseUrl, true);
}
if (statusCode != HttpURLConnection.HTTP_OK)
{
throw new MetadataException("metadata request returned HTTP " + statusCode + " for " + path + " on " + baseUrl, false);
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)))
{
ArtifactMetadata metadata = gson.fromJson(reader, ArtifactMetadata.class);
if (metadata == null)
{
throw new MetadataException("metadata response was empty for " + path + " on " + baseUrl, false);
}
return metadata;
}
catch (JsonSyntaxException e)
{
throw new MetadataException("metadata response was not valid JSON for " + path + " on " + baseUrl, false, e);
}
}
catch (IllegalArgumentException e)
{
throw new MetadataException("metadata URL is invalid: " + url, false, e);
}
catch (IOException e)
{
throw new MetadataException("metadata request failed for " + path + " on " + baseUrl, false, e);
}
}
private static String encodePathSegment(String value)
{
return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20");
}
public static final class MetadataException extends Exception
{
private final boolean notFound;
private MetadataException(String message, boolean notFound)
{
super(message);
this.notFound = notFound;
}
private MetadataException(String message, boolean notFound, Throwable cause)
{
super(message, cause);
this.notFound = notFound;
}
public boolean notFound()
{
return notFound;
}
}
}
@@ -17,6 +17,8 @@ public class BuildInfo
public static String date;
@Getter
public static String number;
@Getter
public static String minecraftVersion;
public void load(Plex plugin)
{
@@ -33,6 +35,7 @@ public class BuildInfo
commit = props.getProperty("gitCommit", "unknown");
date = props.getProperty("date", "unknown");
number = props.getProperty("buildNumber", "unknown");
minecraftVersion = props.getProperty("minecraftVersion", "unknown");
}
catch (Exception ignored)
{
@@ -1,88 +1,41 @@
package dev.plex.util;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import dev.plex.Plex;
import dev.plex.updater.ArtifactMetadata;
import dev.plex.updater.UpdateChannel;
import dev.plex.updater.UpdateMetadataClient;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import lombok.NonNull;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.commons.io.FileUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.json.JSONException;
public class UpdateChecker
{
/*
* -4 = Never checked for updates
* -3 = Likely rate limited
* -2 = Unknown commit
* -1 = Error occurred
* 0 = Up to date
* > 0 = Number of commits behind
*/
private final Plex plugin;
private final String DOWNLOAD_PAGE = "https://ci.plex.us.org/job/";
private final String REPO;
private String BRANCH;
private int distance = -4;
private final UpdateChannel channel;
private final UpdateMetadataClient metadataClient;
private ArtifactMetadata latestPlexMetadata;
public UpdateChecker(Plex plugin)
{
this.plugin = plugin;
this.REPO = plugin.config.getString("update_repo");
this.BRANCH = plugin.config.getString("update_branch");
}
// Adapted from Paper
private int fetchDistanceFromGitHub(@NonNull String repo, @NonNull String branch, @NonNull String hash)
{
try
{
HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).toURL().openConnection();
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)
{
return -2; // Unknown commit
}
if (connection.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN)
{
return -3; // Rate limited likely
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)))
{
JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
String status = obj.get("status").getAsString();
return switch (status)
{
case "identical" -> 0;
case "behind" -> obj.get("behind_by").getAsInt();
default -> -1;
};
}
catch (JsonSyntaxException | NumberFormatException e)
{
e.printStackTrace();
return -1;
}
}
catch (IOException e)
{
e.printStackTrace();
return -1;
}
this.channel = UpdateChannel.fromConfig(plugin.config.getString("updater.channel"));
this.metadataClient = new UpdateMetadataClient(channel);
}
// If verbose is 0, it will display nothing
@@ -90,141 +43,181 @@ public class UpdateChecker
// If verbose is 2, it will display all messages
public boolean getUpdateStatusMessage(CommandSender sender, boolean cached, int verbosity)
{
if (BRANCH == null)
try
{
PlexLog.error("You did not specify a branch to use for update checking. Defaulting to master.");
BRANCH = "master";
}
// If it's -4, it hasn't checked for updates yet
if (distance == -4)
{
distance = fetchDistanceFromGitHub(REPO, BRANCH, BuildInfo.getCommit());
PlexLog.debug("Never checked for updates, checking now...");
}
else
{
// If the request isn't asked to be cached, fetch it
if (!cached)
{
distance = fetchDistanceFromGitHub(REPO, BRANCH, BuildInfo.getCommit());
PlexLog.debug("We have checked for updates before, but this request was not asked to be cached.");
}
else
{
PlexLog.debug("We have checked for updates before, using cache.");
ArtifactMetadata metadata = fetchLatestPlexMetadata(cached);
if (metadata.matchesCurrentBuild(plugin.getPluginMeta().getVersion(), BuildInfo.getNumber(), BuildInfo.getCommit()))
{
if (verbosity == 2)
{
sendMessage(sender, Component.text("Plex is up to date on the " + channel.id() + " channel.").color(NamedTextColor.GREEN));
}
return false;
}
switch (distance)
{
case -1 ->
{
if (verbosity == 2)
{
sendMessage(sender, Component.text("There was an error checking for updates.").color(NamedTextColor.RED));
}
return false;
}
case 0 ->
{
if (verbosity == 2)
{
sendMessage(sender, Component.text("Plex is up to date!").color(NamedTextColor.GREEN));
}
return false;
}
case -2 ->
{
if (verbosity == 2)
{
sendMessage(sender, Component.text("Unknown version, unable to check for updates.").color(NamedTextColor.RED));
}
return false;
}
default ->
{
if (verbosity >= 1)
{
sendMessage(sender, Component.text("Plex is not up to date!", NamedTextColor.RED));
sendMessage(sender, Component.text("Download a new version at: " + DOWNLOAD_PAGE + "Plex").color(NamedTextColor.RED));
sendMessage(sender, Component.text("Or run: /plex update").color(NamedTextColor.RED));
sendMessage(sender, Component.text("Plex " + metadata.version() + " is available on the " + channel.id() + " channel.", NamedTextColor.RED));
sendMessage(sender, Component.text("Run: /plex update").color(NamedTextColor.RED));
}
return true;
}
catch (UpdateMetadataClient.MetadataException e)
{
if (verbosity == 2 || (verbosity >= 1 && !e.notFound()))
{
sendMessage(sender, Component.text(updateMetadataErrorMessage(e)).color(NamedTextColor.RED));
}
if (!e.notFound())
{
PlexLog.error("Unable to check for updates: {0}", e.getMessage());
if (e.getCause() != null)
{
e.getCause().printStackTrace();
}
}
return false;
}
}
public void updateJar(CommandSender sender, String name, boolean module)
{
AtomicReference<String> url = new AtomicReference<>(DOWNLOAD_PAGE + name);
if (!module)
{
url.set(url.get() + "/job/" + BRANCH);
}
PlexLog.debug(url.toString());
try
{
HttpURLConnection connection = (HttpURLConnection) URI.create(url + "/lastSuccessfulBuild/api/json").toURL().openConnection();
int statusCode = connection.getResponseCode();
ArtifactMetadata metadata = module
? metadataClient.fetchModuleLatest(name, plugin.getApi().compatibility().version())
: fetchLatestPlexMetadata(false);
if (statusCode == HttpURLConnection.HTTP_OK)
if (!module && metadata.matchesCurrentBuild(plugin.getPluginMeta().getVersion(), BuildInfo.getNumber(), BuildInfo.getCommit()))
{
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)))
{
JsonObject object = new Gson().fromJson(reader, JsonObject.class);
JsonObject artifact = object.getAsJsonArray("artifacts").get(module ? 0 : 1).getAsJsonObject();
String jarFile = artifact.get("fileName").getAsString();
sendMessage(sender, PlexUtils.mmDeserialize("<green>Downloading latest JAR file: " + jarFile));
File copyTo;
if (!module)
{
copyTo = new File(Bukkit.getUpdateFolderFile(), jarFile);
sendMessage(sender, PlexUtils.mmDeserialize("<red>Plex is already up to date on the " + channel.id() + " channel."));
return;
}
else
{
copyTo = new File(plugin.getModulesFolder().getPath(), jarFile);
File copyTo = module
? new File(plugin.getModulesFolder(), metadata.fileName())
: new File(Bukkit.getUpdateFolderFile(), metadata.fileName());
sendMessage(sender, PlexUtils.mmDeserialize("<green>Downloading latest JAR file: " + metadata.fileName()));
plugin.getApi().scheduler().runAsync(() -> downloadAndInstall(sender, metadata, copyTo));
}
plugin.getApi().scheduler().runAsync(() ->
catch (UpdateMetadataClient.MetadataException e)
{
sendMessage(sender, PlexUtils.mmDeserialize("<red>" + updateMetadataErrorMessage(e)));
if (!e.notFound())
{
PlexLog.error("Unable to update {0}: {1}", name, e.getMessage());
if (e.getCause() != null)
{
e.getCause().printStackTrace();
}
}
}
}
private ArtifactMetadata fetchLatestPlexMetadata(boolean cached) throws UpdateMetadataClient.MetadataException
{
if (!cached || latestPlexMetadata == null)
{
latestPlexMetadata = metadataClient.fetchPlexLatest(BuildInfo.getMinecraftVersion());
}
return latestPlexMetadata;
}
private void downloadAndInstall(CommandSender sender, ArtifactMetadata metadata, File copyTo)
{
File parent = copyTo.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs())
{
sendMessage(sender, PlexUtils.mmDeserialize("<red>Unable to create update directory: " + parent.getAbsolutePath()));
return;
}
File temporaryFile = new File(parent, copyTo.getName() + ".download");
try
{
download(metadata.downloadUrl(), temporaryFile);
validateDownloadedFile(metadata, temporaryFile);
Files.move(temporaryFile.toPath(), copyTo.toPath(), StandardCopyOption.REPLACE_EXISTING);
sendMessage(sender, PlexUtils.mmDeserialize("<green>New JAR file downloaded and verified successfully."));
}
catch (IOException e)
{
sendMessage(sender, PlexUtils.mmDeserialize("<red>Something went wrong while downloading " + metadata.name() + ". Please check the log for more information."));
PlexLog.error("Unable to download update {0}: {1}", metadata.name(), e.getMessage());
e.printStackTrace();
}
finally
{
try
{
FileUtils.copyURLToFile(
URI.create(url + "/lastSuccessfulBuild/artifact/build/libs/" + jarFile).toURL(),
copyTo
);
sendMessage(sender, PlexUtils.mmDeserialize("<green>New JAR file downloaded successfully."));
Files.deleteIfExists(temporaryFile.toPath());
}
catch (IOException e)
catch (IOException ignored)
{
e.printStackTrace();
}
});
}
catch (JsonSyntaxException | NumberFormatException e)
}
private void download(String downloadUrl, File destination) throws IOException
{
e.printStackTrace();
}
}
else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND)
HttpURLConnection connection = (HttpURLConnection) URI.create(downloadUrl).toURL().openConnection();
connection.setConnectTimeout(15000);
connection.setReadTimeout(30000);
int statusCode = connection.getResponseCode();
if (statusCode != HttpURLConnection.HTTP_OK)
{
sendMessage(sender, PlexUtils.mmDeserialize("<red>Could not update " + name + " as it can't be found on Jenkins."));
throw new IOException("download request returned HTTP " + statusCode);
}
else
try (InputStream inputStream = connection.getInputStream())
{
sendMessage(sender, PlexUtils.mmDeserialize("<red>Something went wrong while trying to update " + name + ". Please check the log for more information."));
PlexLog.error("Unable to update module {0} due to unexpected status code returned from Jenkins - Status Code: {1}", name, statusCode);
Files.copy(inputStream, destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
catch (IOException e)
private void validateDownloadedFile(ArtifactMetadata metadata, File file) throws IOException
{
e.printStackTrace();
}
catch (JSONException e)
if (metadata.size() != null && metadata.size() >= 0 && Files.size(file.toPath()) != metadata.size())
{
sendMessage(sender, PlexUtils.mmDeserialize("<red>Something went wrong while trying to gather information from Jenkins for " + name + ". Please check the log for more information"));
PlexLog.error("Unable to parse JSON information received from Jenkins - see below for more information...");
e.printStackTrace();
throw new IOException("downloaded file size did not match metadata size");
}
String actualSha256 = sha256(file);
if (!metadata.sha256().equalsIgnoreCase(actualSha256))
{
throw new IOException("downloaded file SHA-256 did not match metadata SHA-256");
}
}
private String sha256(File file) throws IOException
{
MessageDigest digest;
try
{
digest = MessageDigest.getInstance("SHA-256");
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException("SHA-256 is not available", e);
}
try (InputStream inputStream = Files.newInputStream(file.toPath());
DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest))
{
digestInputStream.transferTo(OutputStream.nullOutputStream());
}
return HexFormat.of().formatHex(digest.digest());
}
private String updateMetadataErrorMessage(UpdateMetadataClient.MetadataException e)
{
if (e.notFound())
{
return "No compatible update is available on the " + channel.id() + " channel.";
}
return "There was an error checking update metadata: " + e.getMessage();
}
private void sendMessage(CommandSender sender, Component message)
@@ -5,3 +5,5 @@ buildNumber={{ buildNumber | default("unknown") }}
date={{ date }}
gitCommit={{ gitCommit | default("unknown") }}
minecraftVersion={{ minecraftVersion | default("unknown") }}
+4 -5
View File
@@ -258,11 +258,10 @@ worlds:
stone: 16
bedrock: 1
# If you are running a custom fork of Plex, you may wish to check for updates from a different repository.
update_repo: "plexusorg/Plex"
# What branch should Plex fetch updates from?
update_branch: "master"
# Static updater metadata. The metadata can be hosted as plain JSON on Cloudflare Pages.
updater:
# Update channel to use. stable only serves non-SNAPSHOT releases; dev serves development builds.
channel: "stable"
# Additional logging for debugging
debug: false