forked from Qortal/qortal
Merge branch 'master' into q-apps
This commit is contained in:
commit
2370a67b8a
1
pom.xml
1
pom.xml
@ -148,6 +148,7 @@
|
|||||||
tagsSorter: "alpha",
|
tagsSorter: "alpha",
|
||||||
operationsSorter:
|
operationsSorter:
|
||||||
"alpha",
|
"alpha",
|
||||||
|
validatorUrl: false,
|
||||||
</value>
|
</value>
|
||||||
</replacement>
|
</replacement>
|
||||||
</replacements>
|
</replacements>
|
||||||
|
@ -20,6 +20,7 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
@ -31,10 +32,13 @@ import javax.ws.rs.*;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.core.LoggerContext;
|
import org.apache.logging.log4j.core.LoggerContext;
|
||||||
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.api.*;
|
import org.qortal.api.*;
|
||||||
@ -42,6 +46,7 @@ import org.qortal.api.model.ActivitySummary;
|
|||||||
import org.qortal.api.model.NodeInfo;
|
import org.qortal.api.model.NodeInfo;
|
||||||
import org.qortal.api.model.NodeStatus;
|
import org.qortal.api.model.NodeStatus;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
|
import org.qortal.controller.AutoUpdate;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.controller.Synchronizer;
|
import org.qortal.controller.Synchronizer;
|
||||||
import org.qortal.controller.Synchronizer.SynchronizationResult;
|
import org.qortal.controller.Synchronizer.SynchronizationResult;
|
||||||
@ -169,6 +174,37 @@ public class AdminResource {
|
|||||||
return nodeSettings;
|
return nodeSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/settings/{setting}")
|
||||||
|
@Operation(
|
||||||
|
summary = "Fetch a single node setting",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public Object setting(@PathParam("setting") String setting) {
|
||||||
|
try {
|
||||||
|
Object settingValue = FieldUtils.readField(Settings.getInstance(), setting, true);
|
||||||
|
if (settingValue == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
else if (settingValue instanceof String[]) {
|
||||||
|
JSONArray array = new JSONArray(settingValue);
|
||||||
|
return array.toString(4);
|
||||||
|
}
|
||||||
|
else if (settingValue instanceof List) {
|
||||||
|
JSONArray array = new JSONArray((List<Object>) settingValue);
|
||||||
|
return array.toString(4);
|
||||||
|
}
|
||||||
|
return settingValue;
|
||||||
|
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/stop")
|
@Path("/stop")
|
||||||
@Operation(
|
@Operation(
|
||||||
@ -199,6 +235,37 @@ public class AdminResource {
|
|||||||
return "true";
|
return "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/restart")
|
||||||
|
@Operation(
|
||||||
|
summary = "Restart",
|
||||||
|
description = "Restart",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "\"true\"",
|
||||||
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String restart(@HeaderParam(Security.API_KEY_HEADER) String apiKey) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
// Short sleep to allow HTTP response body to be emitted
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Not important
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoUpdate.attemptRestart();
|
||||||
|
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/summary")
|
@Path("/summary")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -293,4 +293,77 @@ public class AutoUpdate extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean attemptRestart() {
|
||||||
|
LOGGER.info(String.format("Restarting node..."));
|
||||||
|
|
||||||
|
// Give repository a chance to backup in case things go badly wrong (if enabled)
|
||||||
|
if (Settings.getInstance().getRepositoryBackupInterval() > 0) {
|
||||||
|
try {
|
||||||
|
// Timeout if the database isn't ready for backing up after 60 seconds
|
||||||
|
long timeout = 60 * 1000L;
|
||||||
|
RepositoryManager.backup(true, "backup", timeout);
|
||||||
|
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
LOGGER.info("Attempt to backup repository failed due to timeout: {}", e.getMessage());
|
||||||
|
// Continue with the node restart anyway...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call ApplyUpdate to end this process (unlocking current JAR so it can be replaced)
|
||||||
|
String javaHome = System.getProperty("java.home");
|
||||||
|
LOGGER.debug(String.format("Java home: %s", javaHome));
|
||||||
|
|
||||||
|
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||||
|
LOGGER.debug(String.format("Java binary: %s", javaBinary));
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<String> javaCmd = new ArrayList<>();
|
||||||
|
// Java runtime binary itself
|
||||||
|
javaCmd.add(javaBinary.toString());
|
||||||
|
|
||||||
|
// JVM arguments
|
||||||
|
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||||
|
|
||||||
|
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||||
|
javaCmd = javaCmd.stream()
|
||||||
|
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// Remove JNI options as they won't be supported by command-line 'java'
|
||||||
|
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||||
|
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||||
|
|
||||||
|
// Call ApplyUpdate using JAR
|
||||||
|
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyUpdate.class.getCanonicalName()));
|
||||||
|
|
||||||
|
// Add command-line args saved from start-up
|
||||||
|
String[] savedArgs = Controller.getInstance().getSavedArgs();
|
||||||
|
if (savedArgs != null)
|
||||||
|
javaCmd.addAll(Arrays.asList(savedArgs));
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||||
|
|
||||||
|
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "AUTO_UPDATE"), //TODO
|
||||||
|
Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"), //TODO
|
||||||
|
MessageType.INFO);
|
||||||
|
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||||
|
|
||||||
|
// New process will inherit our stdout and stderr
|
||||||
|
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||||
|
|
||||||
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
|
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||||
|
process.getOutputStream().close();
|
||||||
|
|
||||||
|
return true; // restarting node OK
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error(String.format("Failed to restart node: %s", e.getMessage()));
|
||||||
|
|
||||||
|
return true; // repo was okay, even if applying update failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user