diff --git a/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java b/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java
index 673f19a4..4a1c04e8 100644
--- a/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java
+++ b/src/main/java/me/itzg/helpers/curseforge/CurseForgeInstaller.java
@@ -429,6 +429,8 @@ private void finalizeExistingInstallation(CurseForgeManifest prevManifest) throw
if (prevManifest.getLevelName() != null) {
resultsFileWriter.write("LEVEL", prevManifest.getLevelName());
}
+ resultsFileWriter.write(ResultsFileWriter.MODPACK_NAME, prevManifest.getModpackName());
+ resultsFileWriter.write(ResultsFileWriter.MODPACK_VERSION, prevManifest.getModpackVersion());
}
}
}
@@ -462,6 +464,8 @@ private void finalizeResults(InstallContext context, ModPackResults results, int
try (ResultsFileWriter resultsFileWriter = new ResultsFileWriter(resultsFile, true)) {
if (results.getLevelName() != null) {
resultsFileWriter.write("LEVEL", results.getLevelName());
+ resultsFileWriter.write(ResultsFileWriter.MODPACK_NAME, results.getName());
+ resultsFileWriter.write(ResultsFileWriter.MODPACK_VERSION, results.getVersion());
}
}
}
diff --git a/src/main/java/me/itzg/helpers/env/SimplePlaceholders.java b/src/main/java/me/itzg/helpers/env/SimplePlaceholders.java
new file mode 100644
index 00000000..7f1c547c
--- /dev/null
+++ b/src/main/java/me/itzg/helpers/env/SimplePlaceholders.java
@@ -0,0 +1,79 @@
+package me.itzg.helpers.env;
+
+import java.time.Clock;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Performs simple placeholder replacement of %VAR%
s
+ */
+@Slf4j
+public class SimplePlaceholders {
+
+ /**
+ * Supports
+ *
+ * - %VERSION%
+ * - %env:VERSION%
+ * - %date:YYYY%
+ *
+ */
+ private static final Pattern PLACEHOLDERS_PATTERN = Pattern.compile("%((?\\w+):)?(?\\w+)%");
+
+ private final EnvironmentVariablesProvider environmentVariablesProvider;
+ private final Clock clock;
+
+ public SimplePlaceholders(EnvironmentVariablesProvider environmentVariablesProvider, Clock clock) {
+ this.environmentVariablesProvider = environmentVariablesProvider;
+ this.clock = clock;
+ }
+
+ public String processPlaceholders(String value) {
+ final Matcher m = PLACEHOLDERS_PATTERN.matcher(value);
+ if (m.find()) {
+ final StringBuffer sb = new StringBuffer();
+ do {
+ final String type = Optional.ofNullable(m.group("type"))
+ .orElse("env");
+ final String replacement = buildPlaceholderReplacement(type, m.group("var"), m.group());
+
+ m.appendReplacement(sb, replacement);
+ } while (m.find());
+
+ m.appendTail(sb);
+ return sb.toString();
+ }
+ return value;
+ }
+
+ private String buildPlaceholderReplacement(String type, String var, String fallback) {
+ switch (type) {
+ case "env":
+ final String result = environmentVariablesProvider.get(var);
+ if (result != null) {
+ return result;
+ }
+ else {
+ log.warn("Unable to resolve environment variable {}", var);
+ return fallback;
+ }
+
+ case "date":
+ case "time":
+ try {
+ final DateTimeFormatter f = DateTimeFormatter.ofPattern(var);
+ return ZonedDateTime.now(clock).format(f);
+ } catch (IllegalArgumentException e) {
+ log.error("Invalid date/time format in {}", var, e);
+ return fallback;
+ }
+ }
+
+ return fallback;
+ }
+
+}
diff --git a/src/main/java/me/itzg/helpers/files/ResultsFileWriter.java b/src/main/java/me/itzg/helpers/files/ResultsFileWriter.java
index b0ecf370..2480af44 100644
--- a/src/main/java/me/itzg/helpers/files/ResultsFileWriter.java
+++ b/src/main/java/me/itzg/helpers/files/ResultsFileWriter.java
@@ -18,6 +18,8 @@ public class ResultsFileWriter implements AutoCloseable {
public static final String OPTION_DESCRIPTION =
"A key=value file suitable for scripted environment variables. Currently includes"
+ "\n SERVER: the entry point jar or script";
+ public static final String MODPACK_NAME = "MODPACK_NAME";
+ public static final String MODPACK_VERSION = "MODPACK_VERSION";
private final BufferedWriter writer;
public ResultsFileWriter(Path resultsFile) throws IOException {
@@ -33,7 +35,7 @@ public ResultsFileWriter(Path resultsFile, boolean append) throws IOException {
}
public ResultsFileWriter write(String field, String value) throws IOException {
- log.debug("Writing {}={} to results file", field, value);
+ log.debug("Writing {}=\"{}\" to results file", field, value);
writer.write(String.format("%s=\"%s\"", field, value));
writer.newLine();
return this;
diff --git a/src/main/java/me/itzg/helpers/forge/ForgeInstaller.java b/src/main/java/me/itzg/helpers/forge/ForgeInstaller.java
index c23231d2..b56ce368 100644
--- a/src/main/java/me/itzg/helpers/forge/ForgeInstaller.java
+++ b/src/main/java/me/itzg/helpers/forge/ForgeInstaller.java
@@ -55,7 +55,7 @@ public void install(
needsInstall = true;
}
else if (prevManifest != null) {
- if (!Files.exists(Paths.get(prevManifest.getServerEntry()))) {
+ if (!serverEntryExists(outputDir, prevManifest.getServerEntry())) {
log.warn("Server entry for Minecraft {} Forge {} is missing. Re-installing.",
prevManifest.getMinecraftVersion(), prevManifest.getForgeVersion()
);
@@ -110,6 +110,11 @@ else if (
}
}
+ private boolean serverEntryExists(@NonNull Path outputDir, String serverEntry) {
+ return (serverEntry.startsWith("/") && Files.exists(Paths.get(serverEntry)))
+ || Files.exists(outputDir.resolve(serverEntry));
+ }
+
private ForgeManifest loadManifest(Path outputDir, String variant) throws IOException {
// First check for and retrofit legacy manifest format
final Path legacyFile = outputDir.resolve(LegacyManifest.FILENAME);
diff --git a/src/main/java/me/itzg/helpers/modrinth/FetchedPack.java b/src/main/java/me/itzg/helpers/modrinth/FetchedPack.java
index 6f78c520..4b2811c9 100644
--- a/src/main/java/me/itzg/helpers/modrinth/FetchedPack.java
+++ b/src/main/java/me/itzg/helpers/modrinth/FetchedPack.java
@@ -10,4 +10,9 @@ public class FetchedPack {
String projectSlug;
String versionId;
+
+ /**
+ * Human-readable version
+ */
+ String versionNumber;
}
diff --git a/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java b/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java
index 1e5839c6..bac993c1 100644
--- a/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java
+++ b/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java
@@ -15,6 +15,8 @@
import picocli.CommandLine;
import picocli.CommandLine.ExitCode;
import picocli.CommandLine.Option;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
@CommandLine.Command(name = "install-modrinth-modpack",
description = "Supports installation of Modrinth modpacks along with the associated mod loader",
@@ -103,6 +105,14 @@ public Integer call() throws IOException {
this.forceModloaderReinstall
)
.processModpack(sharedFetch)
+ .flatMap(installation -> {
+ if (resultsFile != null) {
+ return processResultsFile(fetchedPack, installation);
+ }
+ else {
+ return Mono.just(installation);
+ }
+ })
.map(installation ->
ModrinthModpackManifest.builder()
.files(Manifests.relativizeAll(this.outputDirectory, installation.files))
@@ -122,6 +132,17 @@ public Integer call() throws IOException {
return ExitCode.OK;
}
+ private Mono processResultsFile(FetchedPack fetchedPack, Installation installation) {
+ return Mono.fromCallable(() -> {
+ try (ResultsFileWriter results = new ResultsFileWriter(resultsFile, true)) {
+ results.write(ResultsFileWriter.MODPACK_NAME, installation.getIndex().getName());
+ results.write(ResultsFileWriter.MODPACK_VERSION, fetchedPack.getVersionNumber());
+ }
+ return installation;
+ })
+ .subscribeOn(Schedulers.boundedElastic());
+ }
+
private ModrinthPackFetcher buildModpackFetcher(
ModrinthApiClient apiClient, ProjectRef projectRef)
{
diff --git a/src/main/java/me/itzg/helpers/modrinth/ModrinthApiPackFetcher.java b/src/main/java/me/itzg/helpers/modrinth/ModrinthApiPackFetcher.java
index 905fe72e..4e5de9df 100644
--- a/src/main/java/me/itzg/helpers/modrinth/ModrinthApiPackFetcher.java
+++ b/src/main/java/me/itzg/helpers/modrinth/ModrinthApiPackFetcher.java
@@ -59,7 +59,9 @@ public Mono fetchModpack(ModrinthModpackManifest prevManifest) {
.filter(version -> needsInstall(prevManifest, project.getSlug(), version))
.flatMap(version ->
apiClient.downloadMrPack(ModrinthApiClient.pickVersionFile(version))
- .map(mrPackFile -> new FetchedPack(mrPackFile, project.getSlug(), version.getId()))
+ .map(mrPackFile -> new FetchedPack(
+ mrPackFile, project.getSlug(), version.getId(), version.getVersionNumber()
+ ))
)
);
}
diff --git a/src/main/java/me/itzg/helpers/modrinth/ModrinthHttpPackFetcher.java b/src/main/java/me/itzg/helpers/modrinth/ModrinthHttpPackFetcher.java
index 015da4c7..347b0376 100644
--- a/src/main/java/me/itzg/helpers/modrinth/ModrinthHttpPackFetcher.java
+++ b/src/main/java/me/itzg/helpers/modrinth/ModrinthHttpPackFetcher.java
@@ -26,7 +26,13 @@ public Mono fetchModpack(ModrinthModpackManifest prevManifest) {
(uri, file, contentSizeBytes) ->
log.info("Downloaded {}", destFilePath)
)
- .map(mrPackFile -> new FetchedPack(mrPackFile, "custom", deriveVersionId()));
+ .map(mrPackFile -> new FetchedPack(mrPackFile, "custom", deriveVersionId(), deriveVersionName()));
+ }
+
+ private String deriveVersionName() {
+ final int lastSlash = modpackUri.getPath().lastIndexOf('/');
+ return lastSlash > 0 ? modpackUri.getPath().substring(lastSlash + 1)
+ : "unknown";
}
private String deriveVersionId() {
diff --git a/src/main/java/me/itzg/helpers/properties/SetPropertiesCommand.java b/src/main/java/me/itzg/helpers/properties/SetPropertiesCommand.java
index 999555b9..3cfd70c2 100644
--- a/src/main/java/me/itzg/helpers/properties/SetPropertiesCommand.java
+++ b/src/main/java/me/itzg/helpers/properties/SetPropertiesCommand.java
@@ -9,6 +9,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.time.Clock;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
@@ -21,6 +22,7 @@
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.env.EnvironmentVariablesProvider;
+import me.itzg.helpers.env.SimplePlaceholders;
import me.itzg.helpers.env.StandardEnvironmentVariablesProvider;
import me.itzg.helpers.errors.InvalidParameterException;
import me.itzg.helpers.json.ObjectMappers;
@@ -39,6 +41,8 @@ public class SetPropertiesCommand implements Callable {
private static final TypeReference