Merge pull request #160 from AlphaX-Projects/master

Rework restart and bootstrap node
This commit is contained in:
AlphaX-Projects 2024-01-02 15:04:01 +01:00 committed by GitHub
commit 995ed6ab2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 777 additions and 182 deletions

View File

@ -37,7 +37,7 @@
<jsoup.version>1.17.1</jsoup.version>
<junit-jupiter-engine.version>5.10.0</junit-jupiter-engine.version>
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
<log4j.version>2.21.1</log4j.version>
<log4j.version>2.22.0</log4j.version>
<mail.version>1.5.0-b01</mail.version>
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
@ -46,7 +46,7 @@
<maven-surefire-plugin.version>3.2.3</maven-surefire-plugin.version>
<package-info-maven-plugin.version>1.1.0</package-info-maven-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<protobuf.version>3.25.0</protobuf.version>
<protobuf.version>3.25.1</protobuf.version>
<replacer.version>1.5.3</replacer.version>
<reproducible-build-maven-plugin.version>0.16</reproducible-build-maven-plugin.version>
<simplemagic.version>1.17</simplemagic.version>

View File

@ -0,0 +1,227 @@
package org.qortal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.api.ApiKey;
import org.qortal.api.ApiRequest;
import org.qortal.controller.BootstrapNode;
import org.qortal.settings.Settings;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.Security;
import java.util.*;
import java.util.stream.Collectors;
import static org.qortal.controller.BootstrapNode.AGENTLIB_JVM_HOLDER_ARG;
public class ApplyBootstrap {
static {
// This static block will be called before others if using ApplyBootstrap.main()
// Log into different files for bootstrap - this has to be before LogManger.getLogger() calls
System.setProperty("log4j2.filenameTemplate", "log-apply-bootstrap.txt");
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static final Logger LOGGER = LogManager.getLogger(ApplyBootstrap.class);
private static final String JAR_FILENAME = BootstrapNode.JAR_FILENAME;
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
private static final String JAVA_TOOL_OPTIONS_VALUE = "";
private static final long CHECK_INTERVAL = 15 * 1000L; // ms
private static final int MAX_ATTEMPTS = 20;
public static void main(String[] args) {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
// Load/check settings, which potentially sets up blockchain config, etc.
if (args.length > 0)
Settings.fileInstance(args[0]);
else
Settings.getInstance();
LOGGER.info("Applying bootstrap...");
// Shutdown node using API
if (!shutdownNode())
return;
// Delete db
deleteDB();
// Restart node
restartNode(args);
LOGGER.info("Bootstrapping...");
}
private static boolean shutdownNode() {
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
// The /admin/stop endpoint requires an API key, which may or may not be already generated
boolean apiKeyNewlyGenerated = false;
ApiKey apiKey = null;
try {
apiKey = new ApiKey();
if (!apiKey.generated()) {
apiKey.generate();
apiKeyNewlyGenerated = true;
LOGGER.info("Generated API key");
}
} catch (IOException e) {
LOGGER.info("Error loading API key: {}", e.getMessage());
}
// Create GET params
Map<String, String> params = new HashMap<>();
if (apiKey != null) {
params.put("apiKey", apiKey.toString());
}
// Attempt to stop the node
int attempt;
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
final int attemptForLogging = attempt;
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
String response = ApiRequest.perform(baseUri + "admin/stop", params);
if (response == null) {
// No response - consider node shut down
if (apiKeyNewlyGenerated) {
// API key was newly generated for bootstrapping node, so we need to remove it
ApplyBootstrap.removeGeneratedApiKey();
}
return true;
}
LOGGER.info(() -> String.format("Response from API: %s", response));
try {
Thread.sleep(CHECK_INTERVAL);
} catch (InterruptedException e) {
// We still need to check...
break;
}
}
if (apiKeyNewlyGenerated) {
// API key was newly generated for bootstrapping node, so we need to remove it
ApplyBootstrap.removeGeneratedApiKey();
}
if (attempt == MAX_ATTEMPTS) {
LOGGER.error("Failed to shutdown node - giving up");
return false;
}
return true;
}
private static void removeGeneratedApiKey() {
try {
LOGGER.info("Removing newly generated API key...");
// Delete the API key since it was only generated for bootstrapping node
ApiKey apiKey = new ApiKey();
apiKey.delete();
} catch (IOException e) {
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
}
}
private static void deleteDB() {
// Get the repository path from settings
String repositoryPath = Settings.getInstance().getRepositoryPath();
LOGGER.debug(String.format("Repository path: %s", repositoryPath));
try {
Path directory = Paths.get(repositoryPath);
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
LOGGER.error("Error deleting DB: {}", e.getMessage());
}
}
private static void restartNode(String[] args) {
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));
Path exeLauncher = Paths.get(WINDOWS_EXE_LAUNCHER);
LOGGER.debug(() -> String.format("Windows EXE launcher: %s", exeLauncher));
List<String> javaCmd;
if (Files.exists(exeLauncher)) {
javaCmd = Arrays.asList(exeLauncher.toString());
} else {
javaCmd = new ArrayList<>();
// Java runtime binary itself
javaCmd.add(javaBinary.toString());
// JVM arguments
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
// Reapply any retained, but disabled, -agentlib JVM arg
javaCmd = javaCmd.stream()
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
.collect(Collectors.toList());
// Call mainClass in JAR
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
// Add saved command-line args
javaCmd.addAll(Arrays.asList(args));
}
try {
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
if (Files.exists(exeLauncher)) {
LOGGER.debug(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
}
// 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();
} catch (Exception e) {
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
}
}
}

View File

@ -0,0 +1,196 @@
package org.qortal;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.api.ApiKey;
import org.qortal.api.ApiRequest;
import org.qortal.controller.RestartNode;
import org.qortal.settings.Settings;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
import java.util.*;
import java.util.stream.Collectors;
import static org.qortal.controller.RestartNode.AGENTLIB_JVM_HOLDER_ARG;
public class ApplyRestart {
static {
// This static block will be called before others if using ApplyRestart.main()
// Log into different files for restart node - this has to be before LogManger.getLogger() calls
System.setProperty("log4j2.filenameTemplate", "log-apply-restart.txt");
// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}
private static final Logger LOGGER = LogManager.getLogger(ApplyRestart.class);
private static final String JAR_FILENAME = RestartNode.JAR_FILENAME;
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
private static final String JAVA_TOOL_OPTIONS_VALUE = "";
private static final long CHECK_INTERVAL = 10 * 1000L; // ms
private static final int MAX_ATTEMPTS = 12;
public static void main(String[] args) {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
// Load/check settings, which potentially sets up blockchain config, etc.
if (args.length > 0)
Settings.fileInstance(args[0]);
else
Settings.getInstance();
LOGGER.info("Applying restart...");
// Shutdown node using API
if (!shutdownNode())
return;
// Restart node
restartNode(args);
LOGGER.info("Restarting...");
}
private static boolean shutdownNode() {
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
// The /admin/stop endpoint requires an API key, which may or may not be already generated
boolean apiKeyNewlyGenerated = false;
ApiKey apiKey = null;
try {
apiKey = new ApiKey();
if (!apiKey.generated()) {
apiKey.generate();
apiKeyNewlyGenerated = true;
LOGGER.info("Generated API key");
}
} catch (IOException e) {
LOGGER.info("Error loading API key: {}", e.getMessage());
}
// Create GET params
Map<String, String> params = new HashMap<>();
if (apiKey != null) {
params.put("apiKey", apiKey.toString());
}
// Attempt to stop the node
int attempt;
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
final int attemptForLogging = attempt;
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
String response = ApiRequest.perform(baseUri + "admin/stop", params);
if (response == null) {
// No response - consider node shut down
if (apiKeyNewlyGenerated) {
// API key was newly generated for restarting node, so we need to remove it
ApplyRestart.removeGeneratedApiKey();
}
return true;
}
LOGGER.info(() -> String.format("Response from API: %s", response));
try {
Thread.sleep(CHECK_INTERVAL);
} catch (InterruptedException e) {
// We still need to check...
break;
}
}
if (apiKeyNewlyGenerated) {
// API key was newly generated for restarting node, so we need to remove it
ApplyRestart.removeGeneratedApiKey();
}
if (attempt == MAX_ATTEMPTS) {
LOGGER.error("Failed to shutdown node - giving up");
return false;
}
return true;
}
private static void removeGeneratedApiKey() {
try {
LOGGER.info("Removing newly generated API key...");
// Delete the API key since it was only generated for restarting node
ApiKey apiKey = new ApiKey();
apiKey.delete();
} catch (IOException e) {
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
}
}
private static void restartNode(String[] args) {
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));
Path exeLauncher = Paths.get(WINDOWS_EXE_LAUNCHER);
LOGGER.debug(() -> String.format("Windows EXE launcher: %s", exeLauncher));
List<String> javaCmd;
if (Files.exists(exeLauncher)) {
javaCmd = Arrays.asList(exeLauncher.toString());
} else {
javaCmd = new ArrayList<>();
// Java runtime binary itself
javaCmd.add(javaBinary.toString());
// JVM arguments
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
// Reapply any retained, but disabled, -agentlib JVM arg
javaCmd = javaCmd.stream()
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
.collect(Collectors.toList());
// Call mainClass in JAR
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
// Add saved command-line args
javaCmd.addAll(Arrays.asList(args));
}
try {
LOGGER.debug(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
if (Files.exists(exeLauncher)) {
LOGGER.debug(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
}
// 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();
} catch (Exception e) {
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
}
}
}

View File

@ -38,7 +38,7 @@ public class ApplyUpdate {
private static final String NEW_JAR_FILENAME = AutoUpdate.NEW_JAR_FILENAME;
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
private static final String JAVA_TOOL_OPTIONS_VALUE = "-XX:MaxRAMFraction=4";
private static final String JAVA_TOOL_OPTIONS_VALUE = "";
private static final long CHECK_INTERVAL = 30 * 1000L; // ms
private static final int MAX_ATTEMPTS = 12;
@ -139,7 +139,7 @@ public class ApplyUpdate {
apiKey.delete();
} catch (IOException e) {
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
LOGGER.error("Error loading or deleting API key: {}", e.getMessage());
}
}
@ -181,13 +181,13 @@ public class ApplyUpdate {
private static void restartNode(String[] args) {
String javaHome = System.getProperty("java.home");
LOGGER.info(() -> String.format("Java home: %s", javaHome));
LOGGER.debug(() -> String.format("Java home: %s", javaHome));
Path javaBinary = Paths.get(javaHome, "bin", "java");
LOGGER.info(() -> String.format("Java binary: %s", javaBinary));
LOGGER.debug(() -> String.format("Java binary: %s", javaBinary));
Path exeLauncher = Paths.get(WINDOWS_EXE_LAUNCHER);
LOGGER.info(() -> String.format("Windows EXE launcher: %s", exeLauncher));
LOGGER.debug(() -> String.format("Windows EXE launcher: %s", exeLauncher));
List<String> javaCmd;
if (Files.exists(exeLauncher)) {
@ -213,12 +213,12 @@ public class ApplyUpdate {
}
try {
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
LOGGER.debug(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
if (Files.exists(exeLauncher)) {
LOGGER.info(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
LOGGER.debug(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
}

View File

@ -24,8 +24,9 @@ import org.qortal.api.model.ActivitySummary;
import org.qortal.api.model.NodeInfo;
import org.qortal.api.model.NodeStatus;
import org.qortal.block.BlockChain;
import org.qortal.controller.AutoUpdate;
import org.qortal.controller.BootstrapNode;
import org.qortal.controller.Controller;
import org.qortal.controller.RestartNode;
import org.qortal.controller.Synchronizer;
import org.qortal.controller.Synchronizer.SynchronizationResult;
import org.qortal.controller.repository.BlockArchiveRebuilder;
@ -250,7 +251,7 @@ public class AdminResource {
// Not important
}
AutoUpdate.attemptRestart();
RestartNode.attemptToRestart();
}).start();
@ -281,7 +282,7 @@ public class AdminResource {
// Not important
}
AutoUpdate.attemptBootstrap();
BootstrapNode.attemptToBootstrap();
}).start();

View File

@ -23,12 +23,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@ -294,171 +291,4 @@ public class AutoUpdate extends Thread {
return true; // repo was okay, even if applying update failed
}
}
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
}
}
public static boolean attemptBootstrap() {
LOGGER.info(String.format("Bootstrapping 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 bootstrap anyway...
}
}
// Get the repository path from settings
String repositoryPath = Settings.getInstance().getRepositoryPath();
LOGGER.debug(String.format("Repository path: %s", repositoryPath));
// 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 {
Path directory = Paths.get(repositoryPath);
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
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
}
}
}

View File

@ -0,0 +1,103 @@
package org.qortal.controller;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.ApplyBootstrap;
import org.qortal.globalization.Translator;
import org.qortal.gui.SysTray;
import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings;
import java.awt.TrayIcon.MessageType;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/* NOTE: It is CRITICAL that we use OpenJDK and not Java SE because our uber jar repacks BouncyCastle which, in turn, unsigns BC causing it to be rejected as a security provider by Java SE. */
public class BootstrapNode {
public static final String JAR_FILENAME = "qortal.jar";
public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib=";
private static final Logger LOGGER = LogManager.getLogger(BootstrapNode.class);
public static boolean attemptToBootstrap() {
LOGGER.info(String.format("Bootstrapping 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 bootstrap anyway...
}
}
// Call ApplyBootstrap to end this process
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 ApplyBootstrap using JAR
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyBootstrap.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", "BOOTSTRAP_NODE"),
Translator.INSTANCE.translate("SysTray", "APPLYING_BOOTSTRAP_AND_RESTARTING"),
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 bootstrap failed
}
}
}

View File

@ -0,0 +1,102 @@
package org.qortal.controller;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.ApplyRestart;;
import org.qortal.globalization.Translator;
import org.qortal.gui.SysTray;
import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings;
import java.awt.TrayIcon.MessageType;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/* NOTE: It is CRITICAL that we use OpenJDK and not Java SE because our uber jar repacks BouncyCastle which, in turn, unsigns BC causing it to be rejected as a security provider by Java SE. */
public class RestartNode {
public static final String JAR_FILENAME = "qortal.jar";
public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib=";
private static final Logger LOGGER = LogManager.getLogger(RestartNode.class);
public static boolean attemptToRestart() {
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 ApplyRestart to end this process
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 ApplyRestart using JAR
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyRestart.class.getCanonicalName()));
// Add command-line args saved from start-up
String[] savedArgs = Controller.getInstance().getSavedArgs();
if (savedArgs != null)
javaCmd.addAll(Arrays.asList(savedArgs));
LOGGER.debug(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "RESTARTING_NODE"),
Translator.INSTANCE.translate("SysTray", "APPLYING_RESTARTING_NODE"),
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 restart failed
}
}
}

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Synchronisiere Uhrzeit
SYNCHRONIZING_BLOCKCHAIN = Synchronisiere
SYNCHRONIZING_CLOCK = Uhrzeit wird synchronisiert
RESTARTING_NODE = Knoten wird neu gestartet
APPLYING_RESTARTING_NODE = Neustart des knotens wird angewendet. Bitte haben Sie Geduld...
BOOTSTRAP_NODE = Bootstrapping-Knoten
APPLYING_BOOTSTRAP_AND_RESTARTING = Bootstrap anwenden und Knoten neu starten. Bitte haben Sie Geduld...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Synchronize clock
SYNCHRONIZING_BLOCKCHAIN = Synchronizing
SYNCHRONIZING_CLOCK = Synchronizing clock
RESTARTING_NODE = Restarting Node
APPLYING_RESTARTING_NODE = Applying restarting node. Please be patient...
BOOTSTRAP_NODE = Bootstrapping Node
APPLYING_BOOTSTRAP_AND_RESTARTING = Applying bootstrap and restarting node. Please be patient...

View File

@ -44,3 +44,11 @@ SYNCHRONIZE_CLOCK = Sincronizar reloj
SYNCHRONIZING_BLOCKCHAIN = Sincronizando
SYNCHRONIZING_CLOCK = Sincronizando reloj
RESTARTING_NODE = Reiniciando el nodo
APPLYING_RESTARTING_NODE = Aplicando el nodo de reinicio. Por favor sea paciente...
BOOTSTRAP_NODE = Nodo de arranque
APPLYING_BOOTSTRAP_AND_RESTARTING = Aplicando bootstrap y reiniciando el nodo. Por favor sea paciente...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Synkronisoi kello
SYNCHRONIZING_BLOCKCHAIN = Synkronisoi
SYNCHRONIZING_CLOCK = Synkronisoi kelloa
RESTARTING_NODE = Käynnistetään uudelleen solmu
APPLYING_RESTARTING_NODE = Käytetään uudelleenkäynnistyssolmua. Olkaa kärsivällisiä...
BOOTSTRAP_NODE = Käynnistyssolmu
APPLYING_BOOTSTRAP_AND_RESTARTING = Käynnistetään ja käynnistetään solmu uudelleen. Olkaa kärsivällisiä...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Mettre l'heure à jour
SYNCHRONIZING_BLOCKCHAIN = Synchronisation
SYNCHRONIZING_CLOCK = Synchronisation de l'heure
RESTARTING_NODE = Redémarrage du nœud
APPLYING_RESTARTING_NODE = Application du redémarrage du nœud. S'il vous plaît, soyez patient...
BOOTSTRAP_NODE = Nœud d'amorçage
APPLYING_BOOTSTRAP_AND_RESTARTING = Application du bootstrap et redémarrage du nœud. S'il vous plaît, soyez patient...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = סנכרן שעון
SYNCHRONIZING_BLOCKCHAIN = מסנכרן
SYNCHRONIZING_CLOCK = מסנכרן שעון
RESTARTING_NODE = הפעלה מחדש של צומת
APPLYING_RESTARTING_NODE = החלת צומת הפעלה מחדש. אנא התאזר בסבלנות...
BOOTSTRAP_NODE = צומת אתחול
APPLYING_BOOTSTRAP_AND_RESTARTING = החלת אתחול והפעלת צומת

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Óra-szinkronizálás megkezdése
SYNCHRONIZING_BLOCKCHAIN = Szinkronizálás
SYNCHRONIZING_CLOCK = Óraszinkronizálás folyamatban
RESTARTING_NODE = Csomópont újraindítása
APPLYING_RESTARTING_NODE = Újraindító csomópont alkalmazása. Kérjük várjon...
BOOTSTRAP_NODE = Rendszerindítási csomópont
APPLYING_BOOTSTRAP_AND_RESTARTING = Bootstrap alkalmazása és csomópont újraindítása. Kérjük várjon...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Sincronizzare l'orologio
SYNCHRONIZING_BLOCKCHAIN = Sincronizzazione della blockchain
SYNCHRONIZING_CLOCK = Sincronizzazione orologio
RESTARTING_NODE = Riavvio del nodo
APPLYING_RESTARTING_NODE = Applicazione del nodo di riavvio. Per favore sii paziente...
BOOTSTRAP_NODE = Nodo di bootstrap
APPLYING_BOOTSTRAP_AND_RESTARTING = Applicazione del bootstrap e riavvio del nodo. Per favore sii paziente...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = 時刻を同期
SYNCHRONIZING_BLOCKCHAIN = ブロックチェーンを同期中
SYNCHRONIZING_CLOCK = 時刻を同期中
RESTARTING_NODE = ノードを再起動しています
APPLYING_RESTARTING_NODE = 再起動ノードを適用しています。 しばらくお待ちください...
BOOTSTRAP_NODE = ブートストラップ ノード
APPLYING_BOOTSTRAP_AND_RESTARTING = ブートストラップを適用し、ノードを再起動します。 しばらくお待ちください...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = 시간 동기화
SYNCHRONIZING_BLOCKCHAIN = 동기화중
SYNCHRONIZING_CLOCK = 시간 동기화
RESTARTING_NODE = 노드 다시 시작 중
APPLYING_RESTARTING_NODE = 노드 재시작을 적용합니다. 기다려주십시오...
BOOTSTRAP_NODE = 부트스트래핑 노드
APPLYING_BOOTSTRAP_AND_RESTARTING = 부트스트랩을 적용하고 노드를 다시 시작합니다. 기다려주십시오...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Synchronizeer klok
SYNCHRONIZING_BLOCKCHAIN = Bezig met synchronizeren
SYNCHRONIZING_CLOCK = Klok wordt gesynchronizeerd
RESTARTING_NODE = Knooppunt opnieuw starten
APPLYING_RESTARTING_NODE = Herstartknooppunt toepassen. Wees alstublieft geduldig...
BOOTSTRAP_NODE = Opstartknooppunt
APPLYING_BOOTSTRAP_AND_RESTARTING = Bootstrap toepassen en knooppunt opnieuw starten. Wees alstublieft geduldig...

View File

@ -44,3 +44,11 @@ SYNCHRONIZE_CLOCK = Synchronizuj zegar
SYNCHRONIZING_BLOCKCHAIN = Synchronizacja
SYNCHRONIZING_CLOCK = Synchronizacja zegara
RESTARTING_NODE = Ponowne uruchamianie węzła
APPLYING_RESTARTING_NODE = Stosuję ponowne uruchomienie węzła. Proszę być cierpliwym...
BOOTSTRAP_NODE = Węzeł ładowania początkowego
APPLYING_BOOTSTRAP_AND_RESTARTING = Stosowanie ładowania początkowego i ponowne uruchamianie węzła. Proszę być cierpliwym...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Sincronizare ceas
SYNCHRONIZING_BLOCKCHAIN = Sincronizare
SYNCHRONIZING_CLOCK = Se sincronizeaza ceasul
RESTARTING_NODE = Repornirea nodului
APPLYING_RESTARTING_NODE = Se aplica nodul de repornire. Te rog fii rabdator...
BOOTSTRAP_NODE = Nod de bootstrap
APPLYING_BOOTSTRAP_AND_RESTARTING = Se aplica bootstrap si se reporneste nodul. Te rog fii rabdator...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = Синхронизировать время
SYNCHRONIZING_BLOCKCHAIN = Синхронизация цепи
SYNCHRONIZING_CLOCK = Проверка времени
RESTARTING_NODE = Перезапуск узла
APPLYING_RESTARTING_NODE = Применение перезапуска узла. Пожалуйста, будьте терпеливы...
BOOTSTRAP_NODE = Узел начальной загрузки
APPLYING_BOOTSTRAP_AND_RESTARTING = Применение начальной загрузки и перезапуск узла. Пожалуйста, будьте терпеливы...

View File

@ -44,3 +44,11 @@ SYNCHRONIZE_CLOCK = Synkronisera klockan
SYNCHRONIZING_BLOCKCHAIN = Synkroniserar
SYNCHRONIZING_CLOCK = Synkroniserar klockan
RESTARTING_NODE = Mimitian deui Node
APPLYING_RESTARTING_NODE = Nerapkeun titik ngamimitian deui. Mangga sing sabar...
BOOTSTRAP_NODE = Bootstrapping Node
APPLYING_BOOTSTRAP_AND_RESTARTING = Nerapkeun bootstrap sareng hurungkeun deui titik. Mangga sing sabar...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = 同步时钟
SYNCHRONIZING_BLOCKCHAIN = 正在同步区块链
SYNCHRONIZING_CLOCK = 正在同步时钟
RESTARTING_NODE = 重新启动节点
APPLYING_RESTARTING_NODE = 应用重新启动节点。 请耐心等待...
BOOTSTRAP_NODE = 引导节点
APPLYING_BOOTSTRAP_AND_RESTARTING = 应用引导程序并重新启动节点。 请耐心等待...

View File

@ -46,3 +46,11 @@ SYNCHRONIZE_CLOCK = 同步時鐘
SYNCHRONIZING_BLOCKCHAIN = 正在同步區塊鏈
SYNCHRONIZING_CLOCK = 正在同步時鐘
RESTARTING_NODE = 重新啟動節點
APPLYING_RESTARTING_NODE = 應用重新啟動節點。 請耐心等待...
BOOTSTRAP_NODE = 引導節點
APPLYING_BOOTSTRAP_AND_RESTARTING = 應用引導程式並重新啟動節點。 請耐心等待...