diff --git a/caf-bootstrap/src/main/java/io/iec/edp/caf/CAFBootstrap.java b/caf-bootstrap/src/main/java/io/iec/edp/caf/CAFBootstrap.java index 6b9f7684486a65f292631428d00501927760b9b4..68552d14f4b31e11ed2f84279df7e58f4537de4e 100644 --- a/caf-bootstrap/src/main/java/io/iec/edp/caf/CAFBootstrap.java +++ b/caf-bootstrap/src/main/java/io/iec/edp/caf/CAFBootstrap.java @@ -26,6 +26,7 @@ import io.iec.edp.caf.event.StartupEventPublisher; import io.iec.edp.caf.msu.common.manager.AppManager; import io.iec.edp.caf.multicontext.classloader.PlatformClassloader; import io.iec.edp.caf.multicontext.context.PlatformApplicationContext; +import io.iec.edp.caf.upgrade.UpgradeScriptUtils; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import lombok.var; @@ -62,6 +63,7 @@ public class CAFBootstrap { private static Boolean parallel = false; @SneakyThrows public static void main(String[] args) { + UpgradeScriptUtils.upgradeScript(); parallel = System.getProperty("parallel.startup", "false").equals("true"); EnvironmentPreparedConfiguration.environmentPrepared(); diff --git a/caf-bootstrap/src/main/java/io/iec/edp/caf/upgrade/UpgradeScriptUtils.java b/caf-bootstrap/src/main/java/io/iec/edp/caf/upgrade/UpgradeScriptUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..4fa0421937739a79400d4ac68f15b6f2b4bbae50 --- /dev/null +++ b/caf-bootstrap/src/main/java/io/iec/edp/caf/upgrade/UpgradeScriptUtils.java @@ -0,0 +1,810 @@ +package io.iec.edp.caf.upgrade; + + +import io.iec.edp.caf.CAFBootstrap; +import io.iec.edp.caf.commons.runtime.CafEnvironment; +import lombok.extern.slf4j.Slf4j; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +import java.io.*; +import java.lang.management.ManagementFactory; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +public class UpgradeScriptUtils { + private static final String UPGRADE_TEMPLATE_PATH = "upgrade-script.txt"; + private static final String REPEAT_CONFIG_PATH = "repeat-config.txt"; + + private static final String DEFAULT_DEBUG_CONFIG = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"; + + + public static void upgradeScript() { + String basePath = CafEnvironment.getBasePath(); + String filePath = basePath + File.separator + "config.yaml"; + File configFile = new File(filePath); + + if (!configFile.exists()) { + upgrade(filePath, null); + return; + } + + Map data = readConfig(filePath); + //原本含有config.yaml,但里面没有内容则升级 + if (data == null) { + upgrade(filePath, null); + return; + } + + boolean hasServer = data.containsKey("server"); + boolean hasEnvironment = data.containsKey("environment"); + boolean hasJava = data.containsKey("java"); + + if (hasServer && hasEnvironment && hasJava) { + Map serverMap = (Map) data.get("server"); + if (serverMap.get("update-script") == null) { + printConflictNotice(filePath); + } else { + boolean upgradeFlag = Boolean.TRUE.equals(serverMap.get("update-script")); + if (upgradeFlag) { + upgrade(filePath, null); + } + } + } else if (hasServer || hasEnvironment || hasJava) { //含有其中一个配置节,则给出提示 + printConflictNotice(filePath); + } else { //未含有server、enviroment、java配置节,则升级 + upgrade(filePath, data); + } + } + + private static void upgrade(String filePath, Map existingData){ + //生成config配置文件 + generateConfigFile(filePath,existingData); + //备份旧脚本 + List removedFiles=backupOldScripts(); + //生成新启动脚本 + createNewScripts(); + //给出升级通知 + printUpgradeNotice(removedFiles); + } + + private static void generateConfigFile(String filePath, Map existingData) { + List> environment = new ArrayList<>(); + // // 1. 获取 JVM 启动参数 + List jvmInputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); + List jvmArgs=new ArrayList<>(); + Map updateMap=new HashMap<>(); + for(String arg:jvmInputArguments){ + if(arg.startsWith("-Dparallel.startup")){ + int index = arg.indexOf('='); + if (index != -1 && index < arg.length() - 1) { + String value = arg.substring(index + 1); + updateMap.put("parallel-init",value); + } + continue; + } + if(arg.startsWith("-Xrunjdwp:")){ + updateMap.put("debug-config",arg.replaceFirst("^-Xrunjdwp:", "-agentlib:jdwp=")); + continue; + }else if(arg.startsWith("-agentlib:jdwp")){ + updateMap.put("debug-config",arg); + continue; + } + + if(arg.startsWith("-Dspring.profiles.active")){ + continue; + } + if(arg.startsWith("-Xdebug")){ + continue; + } + + //将含有绝对路径的值,用占位符替换 + String source=normalizePath(arg); + String target=normalizePath(CafEnvironment.getBasePath()); + if(source.contains(target)){ + jvmArgs.add(replacePathPlaceholder(source,target)); + }else { + jvmArgs.add(arg); + } + } + String javaOpts = String.join(" ", jvmArgs); + // 收集环境变量 + for (Map.Entry entry : System.getenv().entrySet()) { + addConfigItem(environment, entry.getKey(), entry.getValue()); + } + + Map dataToSave = buildConfigStructure(javaOpts, environment, updateMap); + + if (existingData != null && !existingData.isEmpty()) { + mergeYamlData(existingData, dataToSave); + dataToSave = existingData; + } + + writeToYaml(filePath, dataToSave); + } + + + /** + * 统一路径分隔符为正斜杠 + */ + private static String normalizePath(String path) { + return path == null ? "" : path.replace("\\", "/"); + } + + /** + * 安全替换路径为占位符 + */ + private static String replacePathPlaceholder(String source, String target) { + return source.replace(target, "${CAF_HOME}"); + } + private static void addConfigItem(List> environment, String name, Object value) { + Map item = new LinkedHashMap<>(); + item.put("name", name); + item.put("value", value); + environment.add(item); + } + private static Map buildConfigStructure(String javaOpts, List> environment, Map updateMap) { + Map data = new LinkedHashMap<>(); + data.put("environment", environment); + data.put("java", buildJavaConfig(javaOpts)); + data.put("server", buildServerConfig(updateMap)); + return data; + } + + private static Map buildJavaConfig(String javaOpts) { + Map javaData = new LinkedHashMap<>(); + String target = normalizePath(CafEnvironment.getBasePath()); + String javaHome = System.getenv("JAVA_HOME"); + + if (javaHome != null) { + String normalizedJavaHome = normalizePath(javaHome); + javaData.put("home", normalizedJavaHome.contains(target) + ? replacePathPlaceholder(normalizedJavaHome, target) + : javaHome); + } + + javaData.put("jvm-options", javaOpts); + return javaData; + } + + private static Map buildServerConfig(Map updateMap) { + Map serverData = new LinkedHashMap<>(); + String target = normalizePath(CafEnvironment.getBasePath()); + + serverData.put("update-script", false); + serverData.put("stop-grace-period", System.getenv("CAF_STOP_GRACE_PERIOD")); + serverData.put("enable-console-logging", parseBooleanEnv("ENABLE_CONSOLE_LOGGING", false)); + serverData.put("temp-dir", replaceEnvPathWithPlaceholder("CAF_TMPDIR", target)); + serverData.put("std-out-path", replaceEnvPathWithPlaceholder("CAF_OUT", target)); + serverData.put("jpda-address", System.getenv("JPDA_ADDRESS")); + serverData.put("parallel-init", parseBooleanEnv(updateMap.get("parallel-init"), false)); + + // Debug 配置 + if (updateMap.containsKey("debug-config")) { + serverData.put("enable-debug", true); + serverData.put("debug-config", updateMap.get("debug-config")); + } else { + serverData.put("enable-debug", false); + serverData.put("debug-config", DEFAULT_DEBUG_CONFIG); + } + + return serverData; + } + + /** + * 安全解析布尔值环境变量 + */ + private static boolean parseBooleanEnv(String envValue, boolean defaultValue) { + if (envValue == null) { + return defaultValue; + } + return Boolean.parseBoolean(envValue); + } + + /** + * 替换环境变量中的绝对路径为占位符 + */ + private static String replaceEnvPathWithPlaceholder(String envKey, String target) { + String envValue = System.getenv(envKey); + if (envValue == null) { + return null; + } + String normalized = normalizePath(envValue); + return normalized.contains(target) ? replacePathPlaceholder(normalized, target) : envValue; + } + + private static void mergeYamlData(Map target, Map source) { + for (Map.Entry entry : source.entrySet()) { + if (!target.containsKey(entry.getKey())) { + target.put(entry.getKey(), entry.getValue()); + } + } + } + + private static void writeToYaml(String filePath, Map data) { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setIndent(2); + options.setIndicatorIndent(0); + options.setWidth(200); + + Yaml yaml = new Yaml(options); + try (FileWriter writer = new FileWriter(filePath)) { + yaml.dump(data, writer); + } catch (IOException e) { + throw new UncheckedIOException("Failed to write config file: " + filePath, e); + } + } + + private static Map readConfig(String filePath) { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + Yaml yaml = new Yaml(options); + try (InputStream inputStream = new FileInputStream(filePath); + InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + return yaml.load(reader); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read config file: " + filePath, e); + } + } + private static void printConflictNotice(String filePath) { + if (parseBooleanEnv(System.getenv("caf_upgrade"), true)) { + String template = loadTemplate(REPEAT_CONFIG_PATH); + String output = template + .replace("${rootDir}", CafEnvironment.getBasePath()) + .replace("${filePath}", filePath); + + System.out.println(output); + String txtFilePath=CafEnvironment.getBasePath()+File.separator+"upgradeConflictNotice.txt"; + writeNoticeToFile(txtFilePath,output); + waitForKeyPress(); + System.exit(0); + } + } + + private static void waitForKeyPress() { + try { + System.out.println("按任意键退出..."); + System.in.read(); + } catch (IOException e) { + Thread.currentThread().interrupt(); + } + } + private static String loadTemplate(String templateName) { + ClassLoader classLoader = CAFBootstrap.class.getClassLoader(); + try (InputStream is = classLoader.getResourceAsStream(templateName)) { + if (is == null) { + throw new IllegalArgumentException("Resource not found: " + templateName); + } + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(is, StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + + + private static List backupOldScripts(){ + //备份启动脚本 + String baseDirStr =CafEnvironment.getBasePath(); + String binDirStr = baseDirStr + File.separator+"bin"; + String backupDirStr = binDirStr +File.separator+ "caf-backup"; + String backupBinDirStr = backupDirStr +File.separator+ "bin"; + + //初始化所有必要的目录 + try { + Files.createDirectories(Paths.get(backupDirStr)); + Files.createDirectories(Paths.get(backupBinDirStr)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + List removedFiles=moveFiles(baseDirStr, backupDirStr); + //防止死循环,要确保不要把刚刚移动到 backupDirStr 的文件又移动走 + List removerBinFiles=moveFiles(binDirStr, backupBinDirStr); + removedFiles.addAll(removerBinFiles); + //隐藏caf-backup 目录 + hideDirectory(new File(backupDirStr)); + return removedFiles; + } + private static List moveFiles(String sourcePathStr, String targetPathStr) { + List scriptFiles=new ArrayList<>(); + File sourceDir = new File(sourcePathStr); + File targetDir = new File(targetPathStr); + + if (!sourceDir.exists() || !sourceDir.isDirectory()) { + return scriptFiles; + } + + File[] files = sourceDir.listFiles(); + if (files == null) return scriptFiles; + for (File file : files) { + // 如果是目录,且正好是目标目录(防止递归死循环或自己移动自己),则跳过 + if (file.isDirectory() && file.equals(targetDir)) { + continue; + } + + String fileName = file.getName(); + // 检查后缀 + if ((fileName.endsWith(".cmd") || fileName.endsWith(".sh") || fileName.endsWith(".bat"))&&!fileName.equals("restore.sh")&&!fileName.equals("restore.bat")) { + try { + Path source = file.toPath(); + Path target = targetDir.toPath().resolve(fileName); + // 移动文件,如果目标存在则覆盖 + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); + scriptFiles.add(file.getAbsolutePath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + return scriptFiles; + } + private static void hideDirectory(File dir) { + if (!dir.exists()) { + return; + } + String os = System.getProperty("os.name").toLowerCase(); + try { + if (os.contains("win")) { + // Windows: 使用 attrib +H 命令 + // 必须包含引号以防路径中有空格 + Process p = Runtime.getRuntime().exec("attrib +H \"" + dir.getAbsolutePath() + "\""); + p.waitFor(); + } else if (os.contains("nix") || os.contains("nux") || os.contains("mac")) { + // Linux/Mac: 通过重命名为以 "." 开头实现隐藏 + File hiddenDir = new File(dir.getParentFile(), "." + dir.getName()); + if (!hiddenDir.exists() && !dir.renameTo(hiddenDir)) { + log.error("Renaming directory failed, please check permissions"); + } + } else { + log.error("Unknown operating system, no hidden operation performed."); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Error executing hidden operation: " + e.getMessage()); + } + } + private static void createNewScripts() { + String dirPath = CafEnvironment.getBasePath(); + File dir = new File(dirPath); + + createWindowsStartupScript(dir); + createLinuxStartupScript(dir); + createWindowsIgixScript(dir); + createLinuxIgixScript(dir); + createWindowsShutdownScript(dir); + createLinuxShutdownScript(dir); + createWindowsRestoreScript(dir); + createLinuxRestoreScript(dir); + } + + private static void createWindowsStartupScript(File dir) { + File startupScript = new File(dir, "startup.bat"); + try (PrintWriter writer = createUtf8Writer(startupScript)) { + writer.println("@echo off"); + writer.println("setlocal enabledelayedexpansion"); + writer.println("title iGIX Server"); + writer.println(); + writer.println("set SERVER_PATH=%~dp0"); + writer.println(); + writer.println("if exist \"%SERVER_PATH%server\\runtime\\environment.jar\" ("); + writer.println(" \"%SERVER_PATH%server\\runtime\\java\\%PROCESSOR_ARCHITECTURE%-win\\bin\\java\" -cp \"%SERVER_PATH%server\\runtime\\environment.jar\" io.iec.edp.environment.Environ"); + writer.println(" del /f /q \"%SERVER_PATH%server\\runtime\\environment.jar\" 2>nul"); + writer.println(")"); + writer.println(); + writer.println("chcp 65001 > nul"); + writer.println(); + writer.println("call \"%SERVER_PATH%\\igix.bat\" start"); + writer.println("echo."); + writer.println("cmd /k"); + writer.println("endlocal"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create startup.bat", e); + } + } + + private static void createLinuxStartupScript(File dir) { + File linuxStartup = new File(dir, "startup.sh"); + try (PrintWriter writer = createUtf8Writer(linuxStartup)) { + writer.println("#!/bin/bash"); + writer.println("SERVER_PATH=\"$(cd \"$(dirname \"$0\")\" && pwd)\""); + writer.println(); + writer.println("CAF_BOOT_ARCH=`uname -m`"); + writer.println("CAF_BOOT_OS_KERNEL=`uname -s | tr '[:upper:]' '[:lower:]'`"); + writer.println(); + writer.println("if [ -f \"${SERVER_PATH}/server/runtime/environment.jar\" ]; then"); + writer.println(" \"${SERVER_PATH}/server/runtime/java/${CAF_BOOT_ARCH}-${CAF_BOOT_OS_KERNEL}/bin/java\" -cp \"${SERVER_PATH}/server/runtime/environment.jar\" io.iec.edp.environment.Environ"); + writer.println(" rm -f \"${SERVER_PATH}/server/runtime/environment.jar\""); + writer.println("fi"); + writer.println(); + writer.println("if [ -f \"${SERVER_PATH}/igix.sh\" ]; then"); + writer.println(" \"${SERVER_PATH}/igix.sh\" start"); + writer.println("fi"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create startup.sh", e); + } + } + + private static void createWindowsIgixScript(File dir) { + File igixScript = new File(dir, "igix.bat"); + try (PrintWriter writer = createUtf8Writer(igixScript)) { + writer.println("@echo off"); + writer.println("setlocal"); + writer.println("title iGIX Server"); + writer.println(); + writer.println("if \"%~1\"==\"\" ("); + writer.println(" echo this command need more parameters,like:start,stop...."); + writer.println(" exit /b 1"); + writer.println(")"); + writer.println(); + writer.println("set SERVER_PATH=%~dp0"); + writer.println("set BIN_DIR=%SERVER_PATH%bin\\windows\\%PROCESSOR_ARCHITECTURE%"); + writer.println(); + writer.println("if not exist \"%BIN_DIR%\" ("); + writer.println(" echo The directory \"%BIN_DIR%\" does not exist."); + writer.println(" exit /b 1"); + writer.println(")"); + writer.println(); + writer.println("set caf_command=%BIN_DIR%\\caf.exe -w %SERVER_PATH%"); + writer.println(); + writer.println(":loop"); + writer.println("if \"%~1\"==\"\" goto execute"); + writer.println("set caf_command=%caf_command% %~1"); + writer.println("shift"); + writer.println("goto loop"); + writer.println(); + writer.println(":execute"); + writer.println("%caf_command%"); + writer.println("echo."); + writer.println("cmd /k"); + writer.println("endlocal"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create igix.bat", e); + } + } + + private static void createLinuxIgixScript(File dir) { + File igixScriptLinuxFile = new File(dir, "igix.sh"); + try (PrintWriter writer = createUtf8Writer(igixScriptLinuxFile)) { + writer.println("#!/bin/bash"); + writer.println(); + writer.println("if [ $# -eq 0 ]; then"); + writer.println(" echo \"This command needs more parameters, like: start、stop, etc.\""); + writer.println(" exit 1"); + writer.println("fi"); + writer.println(); + writer.println("if [ \"$1\" = \"start\" ]; then"); + writer.println(" OPEN_FILE=`ulimit -n`"); + writer.println(" LESS_FILE=65535"); + writer.println(" if [ not $darwin ] && [ $OPEN_FILE -lt $LESS_FILE ]; then"); + writer.println(" echo Warning: The num of open-files is probably less than $LESS_FILE.;"); + writer.println(" echo You can check out with command [ulimit -n], and please restart after checking out and solving it.;"); + writer.println(" echo Now open-files : $OPEN_FILE;"); + writer.println(" exit 1;"); + writer.println(" fi"); + writer.println("fi"); + writer.println(); + writer.println("SERVER_PATH=\"$(dirname \"$0\")\""); + writer.println("ARCH=$(uname -m)"); + writer.println("OS_KERNEL=`uname -s | tr '[:upper:]' '[:lower:]'`"); + writer.println("BIN_DIR=$SERVER_PATH/bin/$OS_KERNEL/$ARCH"); + writer.println(); + writer.println("if [ ! -d \"$BIN_DIR\" ]; then"); + writer.println(" echo \"The directory '$BIN_DIR' does not exist.\""); + writer.println(" exit 1"); + writer.println("fi"); + writer.println(); + writer.println("CAF_COMMAND=\"$BIN_DIR/caf -w $SERVER_PATH\""); + writer.println(); + writer.println("for arg in \"$@\"; do"); + writer.println(" CAF_COMMAND=\"$CAF_COMMAND $arg\""); + writer.println("done"); + writer.println(); + writer.println("eval $CAF_COMMAND"); + writer.println("echo \"\""); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create igix.sh", e); + } + } + + private static void createWindowsShutdownScript(File dir) { + File shutdownScript = new File(dir, "shutdown.bat"); + try (PrintWriter writer = createUtf8Writer(shutdownScript)) { + writer.println("@echo off"); + writer.println("setlocal"); + writer.println("title iGIX Server"); + writer.println(); + writer.println("set SERVER_PATH=%~dp0"); + writer.println(); + writer.println("%SERVER_PATH%\\igix.bat stop"); + writer.println("echo."); + writer.println("cmd /k"); + writer.println("endlocal"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create shutdown.bat", e); + } + } + + private static void createLinuxShutdownScript(File dir) { + File shutdownScriptLinux = new File(dir, "shutdown.sh"); + try (PrintWriter writer = createUtf8Writer(shutdownScriptLinux)) { + writer.println("#!/bin/bash"); + writer.println(); + writer.println("SERVER_PATH=`dirname $0`"); + writer.println("\"$SERVER_PATH/igix.sh\" stop"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create shutdown.sh", e); + } + } + private static void createWindowsRestoreScript(File dir) { + File restoreScript = new File(dir, "restore.bat"); + try (PrintWriter writer = createUtf8Writer(restoreScript)) { + writer.println("@echo off"); + writer.println(":: 1. 切换编码为 UTF-8,解决中文乱码问题"); + writer.println("chcp 65001 >nul"); + writer.println("setlocal"); + writer.println(); + + writer.println(":: 设置源目录(备份位置)"); + writer.println("set \"backup_root=%~dp0bin\\caf-backup\""); + writer.println(":: 设置目标目录"); + writer.println("set \"target_root=%~dp0\""); + writer.println("set \"target_bin=%~dp0bin\""); + writer.println(); + + writer.println("cls"); + writer.println("echo =========================================="); + writer.println("echo 脚本还原工具"); + writer.println("echo =========================================="); + writer.println("echo."); + writer.println("echo 源目录: %backup_root%"); + writer.println("echo 目标目录: %target_root%"); + writer.println("echo."); + writer.println("echo 警告:此操作将覆盖 %target_root% 和 %target_bin% 下的同名文件!"); + writer.println("echo."); + writer.println(); + + writer.println(":: 校验用户输入"); + writer.println("set /p confirm=确认要还原吗?请输入 confirm 确认: "); + writer.println(":: 判断输入是否等于 \"confirm\""); + writer.println("if /i not \"%confirm%\"==\"confirm\" ("); + writer.println(" echo."); + writer.println(" echo 用户取消操作,脚本退出。"); + writer.println(" pause"); + writer.println(" exit /b"); + writer.println(")"); + writer.println(); + + writer.println("echo."); + writer.println("echo 正在准备还原..."); + writer.println(":: 检查备份目录是否存在"); + writer.println("if not exist \"%backup_root%\" ("); + writer.println(" echo 错误:未找到备份目录 %backup_root%"); + writer.println(" pause"); + writer.println(" exit /b"); + writer.println(")"); + writer.println(); + + writer.println(":: 取消目录隐藏"); + writer.println("attrib -h -s \"%backup_root%\" /s /d"); + writer.println(); + + writer.println("echo 正在还原主目录脚本..."); + writer.println(":: 还原主目录文件"); + // 使用循环简化重复的 move 命令 + writer.println("for %%e in (cmd bat sh) do ("); + writer.println(" if exist \"%backup_root%\\*.%%e\" move /Y \"%backup_root%\\*.%%e\" \"%target_root%\\\""); + writer.println(")"); + writer.println(); + + writer.println("echo 正在还原 bin 目录脚本..."); + writer.println(":: 还原 bin 目录文件"); + writer.println("if not exist \"%target_bin%\" mkdir \"%target_bin%\""); + writer.println("for %%e in (cmd bat sh) do ("); + writer.println(" if exist \"%backup_root%\\bin\\*.%%e\" move /Y \"%backup_root%\\bin\\*.%%e\" \"%target_bin%\\\""); + writer.println(")"); + writer.println(); + + writer.println("echo."); + writer.println("echo 正在清理新增脚本和配置文件..."); + writer.println(":: 删除指定的文件"); + // 使用循环简化重复的 del 命令 + writer.println("for %%f in (config.yaml startup.bat startup.sh igix.bat igix.sh shutdown.bat shutdown.sh upgradeNotice.txt upgradeConflictNotice.txt) do ("); + writer.println(" if exist \"%target_root%%%f\" del /F /Q \"%target_root%%%f\""); + writer.println(")"); + writer.println("echo 清理完成。"); + writer.println(); + + writer.println("echo =========================================="); + writer.println("echo 还原完成!"); + writer.println("echo =========================================="); + writer.println(); + + writer.println(":: 检查并删除空目录"); + writer.println("echo."); + writer.println("echo 正在检查备份目录..."); + writer.println(":: 先尝试删除空的子目录 bin"); + writer.println("rd \"%backup_root%\\bin\" >nul 2>&1"); + writer.println(":: 再尝试删除根目录 (如果文件都移走了,这里应该能删掉)"); + writer.println("rd \"%backup_root%\" >nul 2>&1"); + writer.println(":: 检查目录是否还存在"); + writer.println("if exist \"%backup_root%\" ("); + writer.println(" echo 提示:备份目录 %backup_root% 非空,已保留。"); + writer.println(" echo (可能包含非脚本文件,请手动检查)"); + writer.println(") else ("); + writer.println(" echo 提示:空的备份目录已自动清理。"); + writer.println(")"); + writer.println(); + writer.println("echo."); + writer.println("pause"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create restore.bat", e); + } + } + + private static void createLinuxRestoreScript(File dir) { + File restoreScript = new File(dir, "restore.sh"); + try (PrintWriter writer = createUtf8Writer(restoreScript)) { + writer.println("#!/bin/bash"); + writer.println("# 获取脚本所在目录的绝对路径(兼容软链接)"); + writer.println("SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\""); + writer.println(); + + writer.println("# 设置源目录(备份位置)"); + writer.println("backup_root=\"${SCRIPT_DIR}/bin/.caf-backup\""); + writer.println("# 设置目标目录"); + writer.println("target_root=\"${SCRIPT_DIR}\""); + writer.println("target_bin=\"${SCRIPT_DIR}/bin\""); + writer.println(); + + writer.println("# 清屏"); + writer.println("clear"); + writer.println("echo =========================================="); + writer.println("echo \" 脚本还原工具\""); + writer.println("echo =========================================="); + writer.println("echo \"\""); + writer.println("echo \"源目录: ${backup_root}\""); + writer.println("echo \"目标目录: ${target_root}\""); + writer.println("echo \"\""); + writer.println("echo \"警告:此操作将覆盖 ${target_root} 和 ${target_bin} 下的同名文件!\""); + writer.println("echo \"\""); + writer.println(); + + writer.println("# 校验用户输入"); + writer.println("read -p \"确认要还原吗?请输入 confirm 确认: \" confirm"); + writer.println("# 判断输入是否等于 \"confirm\" (忽略大小写)"); + writer.println("if [[ \"${confirm^^}\" != \"CONFIRM\" ]]; then"); + writer.println(" echo \"\""); + writer.println(" echo \"用户取消操作,脚本退出。\""); + writer.println(" read -p \"按任意键继续...\" -n 1"); + writer.println(" exit 1"); + writer.println("fi"); + writer.println(); + + writer.println("echo \"\""); + writer.println("echo \"正在准备还原...\""); + writer.println("# 检查备份目录是否存在"); + writer.println("if [ ! -d \"${backup_root}\" ]; then"); + writer.println(" echo \"错误:未找到备份目录 ${backup_root}\""); + writer.println(" read -p \"按任意键继续...\" -n 1"); + writer.println(" exit 1"); + writer.println("fi"); + writer.println(); + + writer.println("# Linux 下默认没有类似 Windows attrib 的隐藏属性,此步可忽略。如需取消隐藏属性(针对以.开头的文件),可以使用 chattr 或 chmod,通常不需要。"); + writer.println(); + + writer.println("echo \"正在还原主目录脚本...\""); + writer.println("# 还原主目录文件 (使用 find 更精确地匹配文件后缀,避免匹配到目录)"); + writer.println("find \"${backup_root}\" -maxdepth 1 -type f \\( -name \"*.cmd\" -o -name \"*.bat\" -o -name \"*.sh\" \\) -print -exec mv -f {} \"${target_root}/\" \\;"); + writer.println(); + + writer.println("echo \"正在还原 bin 目录脚本...\""); + writer.println("# 还原 bin 目录文件"); + writer.println("mkdir -p \"${target_bin}\""); + writer.println("if [ -d \"${backup_root}/bin\" ]; then"); + writer.println(" find \"${backup_root}/bin\" -maxdepth 1 -type f \\( -name \"*.cmd\" -o -name \"*.bat\" -o -name \"*.sh\" \\) -print -exec mv -f {} \"${target_bin}/\" \\;"); + writer.println("fi"); + writer.println(); + + writer.println("echo \"\""); + writer.println("echo \"正在清理新增脚本和配置文件...\""); + writer.println("# 删除指定的文件"); + // 使用循环简化重复的 rm -f 逻辑 + writer.println("for f in config.yaml startup.bat startup.sh igix.bat igix.sh shutdown.bat shutdown.sh upgradeNotice.txt upgradeConflictNotice.txt; do"); + writer.println(" [ -f \"${target_root}/$f\" ] && rm -f \"${target_root}/$f\""); + writer.println("done"); + writer.println("echo \"清理完成。\""); + writer.println(); + + writer.println("echo =========================================="); + writer.println("echo \" 还原完成!\""); + writer.println("echo =========================================="); + writer.println("echo \"\""); + writer.println(); + + writer.println("echo \"正在检查备份目录...\""); + writer.println("# 检查并删除空目录"); + writer.println("# Linux 的 rmdir 只能删除空目录,相当于批处理的 rd (不带 /s /q)"); + writer.println("rmdir \"${backup_root}/bin\" 2>/dev/null"); + writer.println("rmdir \"${backup_root}\" 2>/dev/null"); + writer.println("# 检查目录是否还存在"); + writer.println("if [ -d \"${backup_root}\" ]; then"); + writer.println(" echo \"提示:备份目录 ${backup_root} 非空,已保留。\""); + writer.println(" echo \"(可能包含非脚本文件,请手动检查)\""); + writer.println("else"); + writer.println(" echo \"提示:空的备份目录已自动清理。\""); + writer.println("fi"); + writer.println(); + writer.println("echo \"\""); + writer.println("read -p \"按任意键继续...\" -n 1"); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create restore.sh", e); + } + } + + private static PrintWriter createUtf8Writer(File file) throws IOException { + return new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8), true); + } + + private static void printUpgradeNotice(List removedScripts) { + String basePath = CafEnvironment.getBasePath(); + List addedScripts = Arrays.asList( + basePath + File.separator + "startup.bat", + basePath + File.separator + "igix.bat", + basePath + File.separator + "shutdown.bat", + basePath + File.separator + "startup.sh", + basePath + File.separator + "igix.sh", + basePath + File.separator + "shutdown.sh" + ); + String addedConfig = basePath + File.separator + "config.yaml:用来配置环境变量及JVM参数等信息"; + String newScriptPath = "startup.bat/sh"; + + String template = loadTemplate(UPGRADE_TEMPLATE_PATH); + String output = template + .replace("${removedScripts}", formatScriptList(removedScripts, true)) + .replace("${addedScripts}", formatScriptList(addedScripts, true)) + .replace("${addedConfig}", addedConfig) + .replace("${newScriptPath}", newScriptPath); + System.out.println(output); + String filePath = basePath + File.separator + "upgradeNotice.txt"; + writeNoticeToFile(filePath, output);//有用nouhup脚本启动时屏蔽日志输出的,故将信息重新输出一份 + waitForKeyPress(); + System.exit(0); + } + private static void writeNoticeToFile(String filePath, String content) { + try (PrintWriter writer = createUtf8Writer(new File(filePath))) { + writer.println(content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + private static String formatScriptList(List scripts, boolean numbered) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < scripts.size(); i++) { + sb.append(" "); + if (numbered) { + sb.append(i + 1).append("、"); + } + sb.append(scripts.get(i)); + if (i < scripts.size() - 1) { + sb.append(System.lineSeparator()); + } + } + return sb.toString(); + } + +} diff --git a/caf-bootstrap/src/main/resources/repeat-config.txt b/caf-bootstrap/src/main/resources/repeat-config.txt new file mode 100644 index 0000000000000000000000000000000000000000..6b8451e4455cdb609eaa2bef2d35ce27c4b5a881 --- /dev/null +++ b/caf-bootstrap/src/main/resources/repeat-config.txt @@ -0,0 +1,18 @@ +********************************************************************************************* + + 【 重要通知 】 检测到config.yaml文件! + +********************************************************************************************* + + + +[重要提示] 本次升级启动脚本将在${rootDir}根目录下生成config.yaml文件, + 现检测到环境已存在${filePath},存在冲突 + +[操作要求] 请移走原config.yaml文件,重新启动服务 + + + +********************************************************************************************* + 请确认您已知晓上述内容,重启服务 +********************************************************************************************* diff --git a/caf-bootstrap/src/main/resources/upgrade-script.txt b/caf-bootstrap/src/main/resources/upgrade-script.txt new file mode 100644 index 0000000000000000000000000000000000000000..60962f586f0472515e9b6fbcf335b0ee996ea72b --- /dev/null +++ b/caf-bootstrap/src/main/resources/upgrade-script.txt @@ -0,0 +1,31 @@ +****************************************************************************************** + + 【 重要通知 】 检测到启动脚本架构已升级! + +****************************************************************************************** + +[重要提示] 本次启动执行了脚本迁移升级,旧脚本已被废弃,但旧脚本中的环境配置会保留 +[操作要求] 请务必使用${newScriptPath}重新启动服务。 + +------------------------------------------------------------------------ + 变更明细 +------------------------------------------------------------------------ + 已移除脚本,包含如下: +${removedScripts} + + 新增脚本,包含如下: +${addedScripts} + + 新增配置如下: +${addedConfig} +------------------------------------------------------------------------ + 使用示例 (新脚本) +------------------------------------------------------------------------ + 启动服务: 手动运行startup.bat/sh + 停止服务: 手动运行shutdown.bat/sh + 查看日志: 控制台输入igix.bat/sh logs命令 + 查看服务状态: 控制台输入igix.bat/sh list命令 + +****************************************************************************************** + 请确认您已知晓上述变更,请手动启动服务 +******************************************************************************************