Browse Source

以 devops1 仓库 master 分支的 d5c931b920 版本为起点

reghao 2 years ago
commit
8a6ac46729
100 changed files with 4573 additions and 0 deletions
  1. 9 0
      .gitignore
  2. 2 0
      README.md
  3. 158 0
      agent/pom.xml
  4. 111 0
      agent/src/main/java/cn/reghao/devops/agent/AgentApp.java
  5. 53 0
      agent/src/main/java/cn/reghao/devops/agent/config/ConfigFile.java
  6. 15 0
      agent/src/main/java/cn/reghao/devops/agent/config/DagentConfig.java
  7. 15 0
      agent/src/main/java/cn/reghao/devops/agent/config/ManagerConfig.java
  8. 51 0
      agent/src/main/java/cn/reghao/devops/agent/event/EventCenter.java
  9. 52 0
      agent/src/main/java/cn/reghao/devops/agent/event/handler/EvtAppDeployHandler.java
  10. 73 0
      agent/src/main/java/cn/reghao/devops/agent/event/handler/EvtAppStatHandler.java
  11. 99 0
      agent/src/main/java/cn/reghao/devops/agent/event/ws/WebSocketListenerImpl.java
  12. 99 0
      agent/src/main/java/cn/reghao/devops/agent/event/ws/WsClient.java
  13. 18 0
      agent/src/main/resources/logback.xml
  14. 293 0
      agent/src/test/java/AgentTest.java
  15. 91 0
      agent/src/test/java/WebSocketClient.java
  16. 88 0
      agent/src/test/java/WebSocketClientHandler.java
  17. 73 0
      common/pom.xml
  18. 18 0
      common/src/main/java/cn/reghao/devops/common/agent/app/dto/AppInfo.java
  19. 12 0
      common/src/main/java/cn/reghao/devops/common/agent/app/iface/AppDeploy.java
  20. 17 0
      common/src/main/java/cn/reghao/devops/common/agent/app/iface/AppStat.java
  21. 28 0
      common/src/main/java/cn/reghao/devops/common/agent/app/iface/impl/AppDeployService.java
  22. 66 0
      common/src/main/java/cn/reghao/devops/common/agent/app/iface/impl/AppStatService.java
  23. 84 0
      common/src/main/java/cn/reghao/devops/common/agent/app/iface/impl/DockerApp.java
  24. 81 0
      common/src/main/java/cn/reghao/devops/common/agent/machine/MachineEvent.java
  25. 66 0
      common/src/main/java/cn/reghao/devops/common/build/chain/Bootstrap.java
  26. 20 0
      common/src/main/java/cn/reghao/devops/common/build/chain/BuildHandlers.java
  27. 120 0
      common/src/main/java/cn/reghao/devops/common/build/chain/BuildTools.java
  28. 16 0
      common/src/main/java/cn/reghao/devops/common/build/chain/Handler.java
  29. 11 0
      common/src/main/java/cn/reghao/devops/common/build/chain/HandlerParam.java
  30. 11 0
      common/src/main/java/cn/reghao/devops/common/build/chain/HandlerResult.java
  31. 17 0
      common/src/main/java/cn/reghao/devops/common/build/chain/impl/BuildChainParam.java
  32. 38 0
      common/src/main/java/cn/reghao/devops/common/build/chain/impl/BuildChainResult.java
  33. 46 0
      common/src/main/java/cn/reghao/devops/common/build/chain/impl/CompileHandler.java
  34. 45 0
      common/src/main/java/cn/reghao/devops/common/build/chain/impl/PackHandler.java
  35. 54 0
      common/src/main/java/cn/reghao/devops/common/build/chain/impl/UpdateHandler.java
  36. 32 0
      common/src/main/java/cn/reghao/devops/common/build/model/AppDto.java
  37. 16 0
      common/src/main/java/cn/reghao/devops/common/build/model/BuildConfigDto.java
  38. 22 0
      common/src/main/java/cn/reghao/devops/common/build/model/BuildConsumedDto.java
  39. 21 0
      common/src/main/java/cn/reghao/devops/common/build/model/CompilerDto.java
  40. 20 0
      common/src/main/java/cn/reghao/devops/common/build/model/LocalBuildDir.java
  41. 21 0
      common/src/main/java/cn/reghao/devops/common/build/model/PackerDto.java
  42. 23 0
      common/src/main/java/cn/reghao/devops/common/build/model/RepoAuth.java
  43. 15 0
      common/src/main/java/cn/reghao/devops/common/build/model/constant/CompileType.java
  44. 19 0
      common/src/main/java/cn/reghao/devops/common/build/model/constant/EnvType.java
  45. 13 0
      common/src/main/java/cn/reghao/devops/common/build/model/constant/RepoAuthType.java
  46. 13 0
      common/src/main/java/cn/reghao/devops/common/build/model/constant/RepoType.java
  47. 99 0
      common/src/main/java/cn/reghao/devops/common/build/tool/BuilderUtil.java
  48. 18 0
      common/src/main/java/cn/reghao/devops/common/build/tool/compiler/CodeCompiler.java
  49. 13 0
      common/src/main/java/cn/reghao/devops/common/build/tool/compiler/EmptyCompiler.java
  50. 119 0
      common/src/main/java/cn/reghao/devops/common/build/tool/compiler/MavenCompiler.java
  51. 33 0
      common/src/main/java/cn/reghao/devops/common/build/tool/compiler/ShellCompiler.java
  52. 24 0
      common/src/main/java/cn/reghao/devops/common/build/tool/packer/CodePacker.java
  53. 37 0
      common/src/main/java/cn/reghao/devops/common/build/tool/packer/DockerPack.java
  54. 180 0
      common/src/main/java/cn/reghao/devops/common/build/tool/packer/ZipFiles.java
  55. 51 0
      common/src/main/java/cn/reghao/devops/common/build/tool/packer/ZipPack.java
  56. 18 0
      common/src/main/java/cn/reghao/devops/common/build/tool/repo/ChangedFile.java
  57. 38 0
      common/src/main/java/cn/reghao/devops/common/build/tool/repo/CodeUpdater.java
  58. 33 0
      common/src/main/java/cn/reghao/devops/common/build/tool/repo/CommitInfo.java
  59. 63 0
      common/src/main/java/cn/reghao/devops/common/build/tool/repo/CustomCredentialProvider.java
  60. 53 0
      common/src/main/java/cn/reghao/devops/common/build/tool/repo/CustomSshSessionFactory.java
  61. 295 0
      common/src/main/java/cn/reghao/devops/common/build/tool/repo/GitImpl.java
  62. 30 0
      common/src/main/java/cn/reghao/devops/common/docker/Docker.java
  63. 269 0
      common/src/main/java/cn/reghao/devops/common/docker/DockerImpl.java
  64. 46 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Config.java
  65. 18 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Data.java
  66. 11 0
      common/src/main/java/cn/reghao/devops/common/docker/po/ExposedPorts.java
  67. 16 0
      common/src/main/java/cn/reghao/devops/common/docker/po/GraphDriver.java
  68. 17 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Health.java
  69. 15 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Healthcheck.java
  70. 23 0
      common/src/main/java/cn/reghao/devops/common/docker/po/HostConfig.java
  71. 14 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Labels.java
  72. 16 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Log.java
  73. 11 0
      common/src/main/java/cn/reghao/devops/common/docker/po/NetworkingConfig.java
  74. 21 0
      common/src/main/java/cn/reghao/devops/common/docker/po/RestartPolicy.java
  75. 24 0
      common/src/main/java/cn/reghao/devops/common/docker/po/State.java
  76. 14 0
      common/src/main/java/cn/reghao/devops/common/docker/po/Volumes.java
  77. 21 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/Bridge.java
  78. 33 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/Container.java
  79. 47 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/ContainerInfo.java
  80. 19 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/Mount.java
  81. 13 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/NetworkSettings.java
  82. 13 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/Networks.java
  83. 15 0
      common/src/main/java/cn/reghao/devops/common/docker/po/result/Port.java
  84. 33 0
      common/src/main/java/cn/reghao/devops/common/machine/Cpu.java
  85. 86 0
      common/src/main/java/cn/reghao/devops/common/machine/Disk.java
  86. 69 0
      common/src/main/java/cn/reghao/devops/common/machine/Machine.java
  87. 53 0
      common/src/main/java/cn/reghao/devops/common/machine/Memory.java
  88. 73 0
      common/src/main/java/cn/reghao/devops/common/machine/Network.java
  89. 30 0
      common/src/main/java/cn/reghao/devops/common/machine/Os.java
  90. 17 0
      common/src/main/java/cn/reghao/devops/common/msg/MessageSender.java
  91. 14 0
      common/src/main/java/cn/reghao/devops/common/msg/MsgQueue.java
  92. 9 0
      common/src/main/java/cn/reghao/devops/common/msg/constant/AppId.java
  93. 9 0
      common/src/main/java/cn/reghao/devops/common/msg/constant/AppStatOps.java
  94. 9 0
      common/src/main/java/cn/reghao/devops/common/msg/constant/NodeStatus.java
  95. 15 0
      common/src/main/java/cn/reghao/devops/common/msg/constant/PackType.java
  96. 11 0
      common/src/main/java/cn/reghao/devops/common/msg/constant/Protocol.java
  97. 26 0
      common/src/main/java/cn/reghao/devops/common/msg/event/EvtAgentHeartbeat.java
  98. 33 0
      common/src/main/java/cn/reghao/devops/common/msg/event/EvtAgentStart.java
  99. 19 0
      common/src/main/java/cn/reghao/devops/common/msg/event/EvtAppDeploy.java
  100. 22 0
      common/src/main/java/cn/reghao/devops/common/msg/event/EvtAppStat.java

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+
+.idea/
+*.iml
+*target*/
+*logs/
+*.jar
+*git.properties*
+*bin*/
+devops_data/

+ 2 - 0
README.md

@@ -0,0 +1,2 @@
+# devops
+一个 DevOps 自动化系统,分为 manager 和 agent 两个应用。

+ 158 - 0
agent/pom.xml

@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>devops</artifactId>
+        <groupId>cn.reghao.devops</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>agent</artifactId>
+    <version>1.0.0</version>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.outputDir>${project.basedir}/bin</project.build.outputDir>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.reghao.devops</groupId>
+            <artifactId>common</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.10.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>devops-${project.artifactId}</finalName>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>git.properties</include>
+                    <include>logback.xml</include>
+                </includes>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.2.0</version>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>cn.reghao.devops.agent.AgentApp</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <!-- 不设置此属性则生成的 jar 包名字会带有 jar-with-dependencies -->
+                    <appendAssemblyId>false</appendAssemblyId>
+                    <outputDirectory>${project.build.outputDir}</outputDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!--git-commit-id-plugin 插件,用于实现打包带git版本信息-->
+            <plugin>
+                <groupId>pl.project13.maven</groupId>
+                <artifactId>git-commit-id-plugin</artifactId>
+                <version>2.1.5</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>revision</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!--日期格式;默认值:dd.MM.yyyy '@' HH:mm:ss z;-->
+                    <dateFormat>yyyy-MM-dd HH:mm:ss</dateFormat>
+                    <!--,构建过程中,是否打印详细信息;默认值:false;-->
+                    <verbose>true</verbose>
+                    <!-- ".git"文件路径;默认值:${project.basedir}/.git;
+                      注意: 如果是多模块(多模块)项目,则需要修改到.git文件夹的目录-->
+                    <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
+                    <!--若项目打包类型为pom,是否取消构建;默认值:true;-->
+                    <skipPoms>false</skipPoms>
+                    <!--是否生成"git.properties"文件;默认值:false;-->
+                    <generateGitPropertiesFile>true</generateGitPropertiesFile>
+                    <!--指定"git.properties"文件的存放路径(相对于${project.basedir}的一个路径);
+                    注意:该地址决定接口代码是否可以读取到git版本信息,请自行修改-->
+                    <generateGitPropertiesFilename>src/main/resources/git.properties</generateGitPropertiesFilename>
+                    <!--".git"文件夹未找到时,构建是否失败;若设置true,则构建失败;若设置false,则跳过执行该目标;默认值:true;-->
+                    <failOnNoGitDirectory>true</failOnNoGitDirectory>
+                    <!--git描述配置,可选;由JGit提供实现;-->
+                    <gitDescribe>
+                        <!--是否生成描述属性-->
+                        <skip>false</skip>
+                        <!--提交操作未发现tag时,仅打印提交操作ID,-->
+                        <always>false</always>
+                        <!--提交操作ID显式字符长度,最大值为:40;默认值:7;
+                            0代表特殊意义;后面有解释;
+                        -->
+                        <abbrev>8</abbrev>
+                        <!--构建触发时,代码有修改时(即"dirty state"),添加指定后缀;默认值:"";-->
+                        <dirty>-dirty</dirty>
+                        <!--always print using the "tag-commits_from_tag-g_commit_id-maybe_dirty" format, even if "on" a tag.
+                            The distance will always be 0 if you're "on" the tag.
+                        -->
+                        <forceLongFormat>false</forceLongFormat>
+                    </gitDescribe>
+                </configuration>
+            </plugin>
+            <!--apache maven插件,用于执行ant脚本函数-->
+            <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+                <version>1.8</version>
+                <executions>
+                    <execution>
+                        <!--maven 插件生命周期,我试过用compile 发现打包以后没有git.properties,因为打包的时候,还没有生成
+                        注意,这里改成 initialize 的话, 上面 git-commit-id-plugin 插件对应也该添加 phase 配置,并设置为 initialize
+                         否则你生成的文件,写入的就不会对应的值,而是变量名-->
+                        <phase>initialize</phase>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                        <configuration>
+                            <tasks>
+                                <echo message="写入 git 版本信息"/>
+                                <!-- concat 常用于把一些信息写入文件,我这里用于把 git-commit-id-plugin 生成的git环境变量写入文件 -->
+                                <concat destfile="src/main/resources/git.properties">
+                                    repo=${git.remote.origin.url}
+                                    branch=${git.branch}
+                                    commitId=${git.commit.id.abbrev}
+                                    commitTime=${git.commit.time}
+                                    buildTime=${git.build.time}
+                                </concat>
+                            </tasks>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 111 - 0
agent/src/main/java/cn/reghao/devops/agent/AgentApp.java

@@ -0,0 +1,111 @@
+package cn.reghao.devops.agent;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import cn.reghao.devops.agent.config.ConfigFile;
+import cn.reghao.devops.agent.config.DagentConfig;
+import cn.reghao.devops.agent.config.ManagerConfig;
+import cn.reghao.devops.agent.event.ws.WsClient;
+import cn.reghao.devops.common.agent.app.iface.AppDeploy;
+import cn.reghao.devops.common.agent.app.iface.AppStat;
+import cn.reghao.devops.common.agent.app.iface.impl.AppDeployService;
+import cn.reghao.devops.common.agent.app.iface.impl.AppStatService;
+import cn.reghao.devops.common.agent.app.iface.impl.DockerApp;
+import cn.reghao.devops.common.msg.MessageSender;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.jdk.string.StringRegexp;
+import cn.reghao.jutil.jdk.util.SingleInstance;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+public class AgentApp {
+	static MessageSender messageSender;
+
+	static void setLogLevel() {
+		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+		Logger rootLogger = loggerContext.getLogger("ROOT");
+		rootLogger.setLevel(Level.INFO);
+		//LoggerConfig.initLogger(Appenders.fileAppender());
+	}
+	
+	static boolean tryConnect(String host, int port) {
+		try {
+			boolean isAddress = StringRegexp.matchIPv4Address(host);
+			InetAddress inetAddress;
+			if (isAddress) {
+				inetAddress = InetAddress.getByAddress(host.getBytes(StandardCharsets.UTF_8));
+			} else {
+				inetAddress = InetAddress.getByName(host);
+			}
+
+			InetSocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
+			Socket socket = new Socket();
+			socket.connect(socketAddress);
+			return true;
+		} catch (IOException e) {
+			log.error("尝试建立到 {}:{} 的 Socket 连接失败", host, port);
+		}
+		return false;
+	}
+
+	static MessageSender getMessageSender(DagentConfig dagentConfig) {
+		DockerApp dockerApp = new DockerApp();
+		AppDeploy appDeploy = new AppDeployService(dockerApp);
+		AppStat appStat = new AppStatService(dockerApp);
+
+		ManagerConfig managerConfig = dagentConfig.getManager();
+		if (managerConfig == null) {
+			log.error("配置文件中必须包含 manager 配置");
+			return null;
+		}
+
+		if (tryConnect(managerConfig.getHost(), managerConfig.getPort())) {
+			messageSender = new WsClient(dagentConfig, appDeploy, appStat);
+			return messageSender;
+		}
+
+		return null;
+	}
+
+	static void shutdownGracefully() {
+		Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHook(), "main-shutdown-hook"));
+	}
+
+	static class ShutdownHook implements Runnable {
+		@Override
+		public void run() {
+			messageSender.close();
+			log.info("资源清理完成,结束 devops-agent...");
+		}
+	}
+
+	public static void main(String[] args) {
+		setLogLevel();
+		if (args.length != 1) {
+			log.error("必须指定配置文件...");
+			return;
+		}
+
+		String configFilePath = ConfigFile.configFilePath(args[0], AgentApp.class);
+		DagentConfig dagentConfig = JsonConverter.jsonFileToObject(new File(configFilePath), DagentConfig.class);
+		messageSender = getMessageSender(dagentConfig);
+		if (messageSender == null) {
+			log.error("没有可用的 MessageSender, Agent 结束运行, 具体信息请查看日志");
+			System.exit(-1);
+		} else {
+			messageSender.connect();
+		}
+
+		shutdownGracefully();
+		SingleInstance.onlyOne(60001);
+	}
+}

+ 53 - 0
agent/src/main/java/cn/reghao/devops/agent/config/ConfigFile.java

@@ -0,0 +1,53 @@
+package cn.reghao.devops.agent.config;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author reghao
+ * @date 2021-03-03 18:47:01
+ */
+@Slf4j
+public class ConfigFile {
+    public static String configFilePath(String arg, Class<?> clazz) {
+        String configFilePath = null;
+        File configFile = new File(arg);
+        if (!configFile.exists()) {
+            if (arg.startsWith("./")) {
+                String filename = arg.replace(".", "")
+                        .replace("/", "");
+                configFilePath = runningHome(clazz) + "/" + filename;
+            } else if (!arg.contains("/")) {
+                configFilePath = runningHome(clazz) + "/" + arg;
+            } else {
+                log.error("相对路径的配置文件必须以 ./configFile 或 configFile 形式指定...");
+            }
+        } else {
+            // 绝对路径
+            configFilePath = arg;
+        }
+        return configFilePath;
+    }
+    
+    /**
+     * jar 文件运行目录
+     *
+     * @param
+     * @return
+     * @date 2021-03-03 下午6:33
+     */
+    public static String runningHome(Class<?> clazz) {
+        URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
+        String jarFilePath = URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8);
+        if (jarFilePath.endsWith(".jar")) {
+            jarFilePath = jarFilePath.substring(0, jarFilePath.lastIndexOf("/") + 1);
+        }
+
+        File file = new File(jarFilePath);
+        return file.getAbsolutePath();
+    }
+}

+ 15 - 0
agent/src/main/java/cn/reghao/devops/agent/config/DagentConfig.java

@@ -0,0 +1,15 @@
+package cn.reghao.devops.agent.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-02-23 11:37:32
+ */
+@AllArgsConstructor
+@Getter
+public class DagentConfig {
+    private ManagerConfig manager;
+    private Integer heartbeatInterval;
+}

+ 15 - 0
agent/src/main/java/cn/reghao/devops/agent/config/ManagerConfig.java

@@ -0,0 +1,15 @@
+package cn.reghao.devops.agent.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-02-23 11:42:58
+ */
+@AllArgsConstructor
+@Getter
+public class ManagerConfig {
+    private String host;
+    private int port;
+}

+ 51 - 0
agent/src/main/java/cn/reghao/devops/agent/event/EventCenter.java

@@ -0,0 +1,51 @@
+package cn.reghao.devops.agent.event;
+
+import cn.reghao.devops.agent.event.handler.EvtAppDeployHandler;
+import cn.reghao.devops.agent.event.handler.EvtAppStatHandler;
+import cn.reghao.devops.common.agent.app.iface.AppDeploy;
+import cn.reghao.devops.common.agent.app.iface.AppStat;
+import cn.reghao.devops.common.msg.MessageSender;
+import cn.reghao.devops.common.msg.event.EvtAppDeploy;
+import cn.reghao.devops.common.msg.event.EvtAppStat;
+import cn.reghao.jutil.jdk.event.message.Event;
+import cn.reghao.jutil.jdk.event.router.EventDispatcher;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.management.ManagementFactory;
+
+/**
+ * @author reghao
+ * @date 2023-02-23 09:18:11
+ */
+@Slf4j
+public class EventCenter {
+    private final long startTime;
+    private final EventDispatcher dispatcher;
+
+    public EventCenter(MessageSender messageSender, AppDeploy appDeploy, AppStat appStat) {
+        this.startTime = ManagementFactory.getRuntimeMXBean().getStartTime();
+        this.dispatcher = new EventDispatcher();
+        initDispatcher(messageSender, appDeploy, appStat);
+    }
+
+    private void initDispatcher(MessageSender messageSender, AppDeploy appDeploy, AppStat appStat) {
+        dispatcher.register(EvtAppDeploy.class, new EvtAppDeployHandler(messageSender, appDeploy));
+        dispatcher.register(EvtAppStat.class, new EvtAppStatHandler(messageSender, appStat));
+    }
+
+    public void dispatch(EventMessage eventMessage) {
+        try {
+            long sendTime = eventMessage.getSendTime();
+            if (sendTime < startTime) {
+                log.info("忽略 agent 启动前 manager 发送的事件...");
+                return;
+            }
+
+            Event event = eventMessage.getEvent();
+            dispatcher.dispatch(event);
+        } catch (Exception e) {
+            log.error("处理消息发生异常: {}", e.getMessage());
+        }
+    }
+}

+ 52 - 0
agent/src/main/java/cn/reghao/devops/agent/event/handler/EvtAppDeployHandler.java

@@ -0,0 +1,52 @@
+package cn.reghao.devops.agent.event.handler;
+
+import cn.reghao.devops.common.agent.app.iface.AppDeploy;
+import cn.reghao.devops.common.machine.Machine;
+import cn.reghao.devops.common.msg.MessageSender;
+import cn.reghao.devops.common.msg.MsgQueue;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+import cn.reghao.jutil.jdk.event.message.Event;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+import cn.reghao.jutil.jdk.event.handler.Handler;
+import cn.reghao.devops.common.msg.event.EvtAppDeploy;
+import cn.reghao.jutil.jdk.exception.ExceptionUtil;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.ResultStatus;
+
+import java.io.IOException;
+
+/**
+ * @author reghao
+ * @date 2023-03-01 10:52:04
+ */
+public class EvtAppDeployHandler extends Handler {
+    private final MessageSender messageSender;
+    private final AppDeploy appDeploy;
+
+    public EvtAppDeployHandler(MessageSender messageSender, AppDeploy appDeploy) {
+        this.messageSender = messageSender;
+        this.appDeploy = appDeploy;
+    }
+
+    @Override
+    public void handle(Event evt) {
+        EvtAppDeploy deployParam = (EvtAppDeploy) evt;
+        String appId = deployParam.getAppId();
+        EvtAppStatResult statResult;
+        try {
+            statResult = appDeploy.deploy(deployParam);
+            statResult.setResult(Result.result(ResultStatus.SUCCESS));
+        } catch (Exception e) {
+            statResult = new EvtAppStatResult(appId, Machine.ID);
+            statResult.setResult(Result.result(ResultStatus.FAIL, ExceptionUtil.errorMsg(e)));
+        }
+
+        statResult.setDeploy(true);
+        EventMessage evtMsg = EventMessage.evt(statResult);
+        try {
+            messageSender.send(MsgQueue.managerTopic(), evtMsg);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 73 - 0
agent/src/main/java/cn/reghao/devops/agent/event/handler/EvtAppStatHandler.java

@@ -0,0 +1,73 @@
+package cn.reghao.devops.agent.event.handler;
+
+import cn.reghao.devops.common.agent.app.iface.AppStat;
+import cn.reghao.devops.common.machine.Machine;
+import cn.reghao.devops.common.msg.MessageSender;
+import cn.reghao.devops.common.msg.constant.AppStatOps;
+import cn.reghao.devops.common.msg.event.EvtAppStat;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+import cn.reghao.jutil.jdk.event.handler.Handler;
+import cn.reghao.jutil.jdk.event.message.Event;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+import cn.reghao.jutil.jdk.exception.ExceptionUtil;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.ResultStatus;
+
+import java.io.IOException;
+
+/**
+ * @author reghao
+ * @date 2023-03-01 10:21:40
+ */
+public class EvtAppStatHandler extends Handler {
+    private final MessageSender messageSender;
+    private final AppStat appStat;
+
+    public EvtAppStatHandler(MessageSender messageSender, AppStat appStat) {
+        this.messageSender = messageSender;
+        this.appStat = appStat;
+    }
+
+    @Override
+    public void handle(Event evt) {
+        EvtAppStat evtAppStat = (EvtAppStat) evt;
+        String appId = evtAppStat.getAppId();
+        String ops = evtAppStat.getOps();
+
+        EvtAppStatResult statResult;
+        try {
+            switch (AppStatOps.valueOf(ops)) {
+                case start:
+                    statResult = appStat.start(evtAppStat);
+                    statResult.setResult(Result.result(ResultStatus.SUCCESS));
+                    break;
+                case stop:
+                    statResult = appStat.stop(evtAppStat);
+                    statResult.setResult(Result.result(ResultStatus.SUCCESS));
+                    break;
+                case restart:
+                    statResult = appStat.restart(evtAppStat);
+                    statResult.setResult(Result.result(ResultStatus.SUCCESS));
+                    break;
+                case stat:
+                    statResult = appStat.stat(evtAppStat);
+                    statResult.setResult(Result.result(ResultStatus.SUCCESS));
+                    break;
+                default:
+                    statResult = new EvtAppStatResult(appId, Machine.ID);
+                    String msg = String.format("应用状态操作类型 %s 不存在", ops);
+                    statResult.setResult(Result.result(ResultStatus.ERROR, msg));
+            }
+        } catch (Exception e) {
+            statResult = new EvtAppStatResult(appId, Machine.ID);
+            statResult.setResult(Result.result(ResultStatus.FAIL, ExceptionUtil.errorMsg(e)));
+        }
+
+        EventMessage evtMsg = EventMessage.evt(statResult);
+        try {
+            messageSender.send(Machine.ID, evtMsg);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 99 - 0
agent/src/main/java/cn/reghao/devops/agent/event/ws/WebSocketListenerImpl.java

@@ -0,0 +1,99 @@
+package cn.reghao.devops.agent.event.ws;
+
+import cn.reghao.devops.agent.event.EventCenter;
+import cn.reghao.devops.common.agent.machine.MachineEvent;
+import cn.reghao.devops.common.machine.Machine;
+import cn.reghao.jutil.jdk.serializer.JdkSerializer;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
+
+import java.io.EOFException;
+import java.net.ConnectException;
+import java.net.ProtocolException;
+
+/**
+ * @author reghao
+ * @date 2023-02-23 09:26:50
+ */
+@Slf4j
+public class WebSocketListenerImpl extends WebSocketListener {
+    private final EventCenter eventCenter;
+    private final WsClient wsClient;
+    private final MachineEvent machineEvent;
+
+    public WebSocketListenerImpl(WsClient wsClient, EventCenter eventCenter, MachineEvent machineEvent) {
+        this.eventCenter = eventCenter;
+        this.wsClient = wsClient;
+        this.machineEvent = machineEvent;
+    }
+
+    @Override
+    public void onOpen(WebSocket webSocket, Response response) {
+        log.info("WebSocket 连接成功");
+        wsClient.setConnected(true);
+        wsClient.resetRetryCount();
+
+        machineEvent.agentStart();
+    }
+
+    @Override
+    public void onClosing(WebSocket webSocket, int code, String reason) {
+        log.error("WebSocket 连接被动断开 -> {} - {}", code, reason);
+        wsClient.setConnected(false);
+
+        machineEvent.pauseHeartbeat();
+        if (wsClient.isRetry()) {
+            reconnect();
+        }
+    }
+
+    @Override
+    public void onClosed(WebSocket webSocket, int code, String reason) {
+        log.error("WebSocket 连接主动断开 -> {} - {}", code, reason);
+        wsClient.setConnected(false);
+    }
+
+    @Override
+    public void onFailure(WebSocket webSocket, Throwable throwable, Response response) {
+        log.info("WebSocket 异常事件: {}", throwable.getMessage());
+        wsClient.setConnected(false);
+        if (wsClient.isRetry()) {
+            reconnect();
+        }
+    }
+
+    private void reconnect() {
+        log.info("WebSocket 重连");
+        try {
+            if (wsClient.getRetryCount() > 10) {
+                log.info("WebSocket 重连超过 10 次, 休眠 1 分钟后再尝试");
+                Thread.sleep(60_000);
+                wsClient.resetRetryCount();
+            } else {
+                log.info("休眠 10s 后再尝试重连");
+                Thread.sleep(10_000);
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        wsClient.retryCountIncr();
+        wsClient.connect();
+    }
+
+    @Override
+    public void onMessage(WebSocket webSocket, String text) {
+    }
+
+    @Override
+    public void onMessage(WebSocket webSocket, ByteString bytes) {
+        Object object = JdkSerializer.deserialize(bytes.toByteArray());
+        if (object instanceof EventMessage) {
+            EventMessage eventMessage = (EventMessage) object;
+            eventCenter.dispatch(eventMessage);
+        }
+    }
+}

+ 99 - 0
agent/src/main/java/cn/reghao/devops/agent/event/ws/WsClient.java

@@ -0,0 +1,99 @@
+package cn.reghao.devops.agent.event.ws;
+
+import cn.reghao.devops.agent.config.DagentConfig;
+import cn.reghao.devops.agent.config.ManagerConfig;
+import cn.reghao.devops.agent.event.EventCenter;
+import cn.reghao.devops.common.agent.app.iface.AppDeploy;
+import cn.reghao.devops.common.agent.app.iface.AppStat;
+import cn.reghao.devops.common.agent.machine.MachineEvent;
+import cn.reghao.devops.common.machine.Machine;
+import cn.reghao.devops.common.msg.MessageSender;
+import cn.reghao.jutil.jdk.serializer.JdkSerializer;
+import okhttp3.*;
+import okio.ByteString;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author reghao
+ * @date 2023-02-23 09:26:50
+ */
+public class WsClient implements MessageSender {
+    private final String url;
+    private WebSocket webSocket;
+    private boolean connected;
+    private final WebSocketListener webSocketListener;
+    private boolean retry;
+    private int retryCount;
+
+    public WsClient(DagentConfig dagentConfig, AppDeploy appDeploy, AppStat appStat) {
+        String host = dagentConfig.getManager().getHost();
+        int port = dagentConfig.getManager().getPort();
+        this.url = String.format("ws://%s:%s/ws/agent?token=%s", host, port, Machine.ID);
+
+        EventCenter eventCenter = new EventCenter(this, appDeploy, appStat);
+        int heartbeatInterval = dagentConfig.getHeartbeatInterval();
+        MachineEvent machineEvent = new MachineEvent(this, new Machine(), heartbeatInterval);
+        this.webSocketListener = new WebSocketListenerImpl(this, eventCenter, machineEvent);
+        this.retry = true;
+        this.retryCount = 0;
+    }
+
+    public void setRetry(boolean retry) {
+        this.retry = retry;
+    }
+
+    public boolean isRetry() {
+        return retry;
+    }
+
+    public void retryCountIncr() {
+        this.retryCount += 1;
+    }
+
+    public void resetRetryCount() {
+        this.retryCount = 0;
+    }
+
+    public int getRetryCount() {
+        return retryCount;
+    }
+
+    @Override
+    public void connect() {
+        Request request = new Request.Builder()
+                .url(url)
+                .header("Authorization", "Bearer " + Machine.ID)
+                .build();
+
+        OkHttpClient okHttpClient = new OkHttpClient.Builder()
+                .connectTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(30, TimeUnit.SECONDS)
+                .writeTimeout(30, TimeUnit.SECONDS)
+                .build();
+        this.webSocket = okHttpClient.newWebSocket(request, webSocketListener);
+    }
+
+    @Override
+    public void setConnected(boolean status) {
+        this.connected = status;
+    }
+
+    @Override
+    public boolean isConnected() {
+        return connected;
+    }
+
+    @Override
+    public void send(String dest, Object message) {
+        if (isConnected()) {
+            byte[] bytes = JdkSerializer.serialize(message);
+            webSocket.send(ByteString.of(bytes));
+        }
+    }
+
+    public void close() {
+        setRetry(false);
+        webSocket.close(1000, "Client Close Connection");
+    }
+}

+ 18 - 0
agent/src/main/resources/logback.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>logs/log.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>
+                %d{HH:mm:ss.SSS} [%thread] %-5level %c %M %L - %msg%n
+            </pattern>
+        </encoder>
+    </appender>
+
+    <root level="DEBUG">
+        <appender-ref ref="FILE" />
+    </root>
+</configuration>

+ 293 - 0
agent/src/test/java/AgentTest.java

@@ -0,0 +1,293 @@
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import org.slf4j.LoggerFactory;
+import oshi.SystemInfo;
+import oshi.hardware.*;
+import oshi.software.os.*;
+import oshi.util.FormatUtil;
+import oshi.util.Util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * @author reghao
+ * @date 2022-05-21 13:58:00
+ */
+public class AgentTest {
+    private static void printComputerSystem(final ComputerSystem computerSystem) {
+
+        System.out.println("manufacturer: " + computerSystem.getManufacturer());
+        System.out.println("model: " + computerSystem.getModel());
+        System.out.println("serialnumber: " + computerSystem.getSerialNumber());
+        final Firmware firmware = computerSystem.getFirmware();
+        System.out.println("firmware:");
+        System.out.println("  manufacturer: " + firmware.getManufacturer());
+        System.out.println("  name: " + firmware.getName());
+        System.out.println("  description: " + firmware.getDescription());
+        System.out.println("  version: " + firmware.getVersion());
+//      System.out.println("  release date: " + (firmware.getReleaseDate() == null ? "unknown": firmware.getReleaseDate() == null ? "unknown" : FormatUtil.formatDate(firmware.getReleaseDate())));
+        final Baseboard baseboard = computerSystem.getBaseboard();
+        System.out.println("baseboard:");
+        System.out.println("  manufacturer: " + baseboard.getManufacturer());
+        System.out.println("  model: " + baseboard.getModel());
+        System.out.println("  version: " + baseboard.getVersion());
+        System.out.println("  serialnumber: " + baseboard.getSerialNumber());
+    }
+
+    private static void printProcessor(CentralProcessor processor) {
+        System.out.println(processor);
+        System.out.println(" " + processor.getPhysicalPackageCount() + " physical CPU package(s)");
+        System.out.println(" " + processor.getPhysicalProcessorCount() + " physical CPU core(s)");
+        System.out.println(" " + processor.getLogicalProcessorCount() + " logical CPU(s)");
+
+        System.out.println("Identifier: " + processor.getProcessorIdentifier());
+        System.out.println("ProcessorID: " + processor.getProcessorIdentifier().getProcessorID());
+    }
+
+    private static void printMemory(GlobalMemory memory) {
+        System.out.println("Memory: " + FormatUtil.formatBytes(memory.getAvailable()) + "/"
+                + FormatUtil.formatBytes(memory.getTotal()));
+        System.out.println("Swap used: " + FormatUtil.formatBytes(memory.getVirtualMemory().getSwapUsed()) + "/"
+                + FormatUtil.formatBytes(memory.getVirtualMemory().getSwapTotal()));
+    }
+
+    private static void printCpu(CentralProcessor processor) {
+        System.out.println(
+                "Context Switches/Interrupts: " + processor.getContextSwitches() + " / " + processor.getInterrupts());
+
+        long[] prevTicks = processor.getSystemCpuLoadTicks();
+        System.out.println("CPU, IOWait, and IRQ ticks @ 0 sec:" + Arrays.toString(prevTicks));
+        // Wait a second...
+        Util.sleep(1000);
+        long[] ticks = processor.getSystemCpuLoadTicks();
+        System.out.println("CPU, IOWait, and IRQ ticks @ 1 sec:" + Arrays.toString(ticks));
+        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
+        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
+        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
+        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
+        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
+        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
+        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
+        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
+        long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;
+
+        System.out.format(
+                "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%%n",
+                100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu,
+                100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu);
+        System.out.format("CPU load: %.1f%% (counting ticks)%n", processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100);
+        //System.out.format("CPU load: %.1f%% (OS MXBean)%n", processor.getSystemCpuLoad() * 100);
+        double[] loadAverage = processor.getSystemLoadAverage(3);
+        System.out.println("CPU load averages:" + (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0]))
+                + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1]))
+                + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2])));
+        // per core CPU
+//      StringBuilder procCpu = new StringBuilder("CPU load per processor:");
+//      double[] load = processor.getProcessorCpuLoadBetweenTicks();
+//      for (double avg : load) {
+//          procCpu.append(String.format(" %.1f%%", avg * 100));
+//      }
+//      System.out.println(procCpu.toString());
+    }
+
+    private static void printProcesses(OperatingSystem os, GlobalMemory memory) {
+        System.out.println("Processes: " + os.getProcessCount() + ", Threads: " + os.getThreadCount());
+        // Sort by highest CPU
+        Predicate<OSProcess> chromeProcessFilter = p -> p.getName().equalsIgnoreCase("Chrome");
+        List<OSProcess> procs = os.getProcesses(chromeProcessFilter, OperatingSystem.ProcessSorting.CPU_DESC, 0);
+
+        System.out.println("   PID  %CPU %MEM       VSZ       RSS Name");
+        for (int i = 0; i < procs.size() && i < 5; i++) {
+            OSProcess p = procs.get(i);
+            System.out.format(" %5d %5.1f %4.1f %9s %9s %s%n", p.getProcessID(),
+                    100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime(),
+                    100d * p.getResidentSetSize() / memory.getTotal(), FormatUtil.formatBytes(p.getVirtualSize()),
+                    FormatUtil.formatBytes(p.getResidentSetSize()), p.getName());
+        }
+    }
+
+    private static void printSensors(Sensors sensors) {
+        System.out.println("Sensors:");
+        System.out.format(" CPU Temperature: %.1f°C%n", sensors.getCpuTemperature());
+        System.out.println(" Fan Speeds: " + Arrays.toString(sensors.getFanSpeeds()));
+        System.out.format(" CPU Voltage: %.1fV%n", sensors.getCpuVoltage());
+    }
+
+    private static void printPowerSources(List<PowerSource> list) {
+        StringBuilder sb = new StringBuilder("Power: ");
+        if (list.size() == 0) {
+            sb.append("Unknown");
+        } else {
+            double timeRemaining = list.get(0).getTimeRemainingInstant();
+            if (timeRemaining < -1d) {
+                sb.append("Charging");
+            } else if (timeRemaining < 0d) {
+                sb.append("Calculating time remaining");
+            } else {
+                sb.append(String.format("%d:%02d remaining", (int) (timeRemaining / 3600),
+                        (int) (timeRemaining / 60) % 60));
+            }
+        }
+        for (PowerSource pSource : list) {
+            sb.append(String.format("%n %s @ %.1f%%", pSource.getName(), pSource.getRemainingCapacityPercent() * 100d));
+        }
+        System.out.println(sb.toString());
+    }
+
+    private static void printDisks(List<HWDiskStore> list) {
+        System.out.println("Disks:");
+        for (HWDiskStore disk : list) {
+            boolean readwrite = disk.getReads() > 0 || disk.getWrites() > 0;
+            System.out.format(" %s: (model: %s - S/N: %s) size: %s, reads: %s (%s), writes: %s (%s), xfer: %s ms%n",
+                    disk.getName(), disk.getModel(), disk.getSerial(),
+                    disk.getSize() > 0 ? FormatUtil.formatBytesDecimal(disk.getSize()) : "?",
+                    readwrite ? disk.getReads() : "?", readwrite ? FormatUtil.formatBytes(disk.getReadBytes()) : "?",
+                    readwrite ? disk.getWrites() : "?", readwrite ? FormatUtil.formatBytes(disk.getWriteBytes()) : "?",
+                    readwrite ? disk.getTransferTime() : "?");
+            List<HWPartition> partitions = disk.getPartitions();
+            if (partitions == null) {
+                // TODO Remove when all OS's implemented
+                continue;
+            }
+            for (HWPartition part : partitions) {
+                System.out.format(" |-- %s: %s (%s) Maj:Min=%d:%d, size: %s%s%n", part.getIdentification(),
+                        part.getName(), part.getType(), part.getMajor(), part.getMinor(),
+                        FormatUtil.formatBytesDecimal(part.getSize()),
+                        part.getMountPoint().isEmpty() ? "" : " @ " + part.getMountPoint());
+            }
+        }
+    }
+
+    private static void printFileSystem(FileSystem fileSystem) {
+        System.out.println("File System:");
+
+        System.out.format(" File Descriptors: %d/%d%n", fileSystem.getOpenFileDescriptors(),
+                fileSystem.getMaxFileDescriptors());
+
+        List<OSFileStore> fsArray = fileSystem.getFileStores();
+        for (OSFileStore fs : fsArray) {
+            long usable = fs.getUsableSpace();
+            long total = fs.getTotalSpace();
+            System.out.format(
+                    " %s (%s) [%s] %s of %s free (%.1f%%) is %s "
+                            + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s")
+                            + " and is mounted at %s%n",
+                    fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(),
+                    FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total,
+                    fs.getVolume(), fs.getLogicalVolume(), fs.getMount());
+        }
+    }
+
+    private static void printNetworkInterfaces(List<NetworkIF> list) {
+        System.out.println("Network interfaces:");
+        for (NetworkIF net : list) {
+            System.out.format(" Name: %s (%s)%n", net.getName(), net.getDisplayName());
+            System.out.format("   MAC Address: %s %n", net.getMacaddr());
+            System.out.format("   MTU: %s, Speed: %s %n", net.getMTU(), FormatUtil.formatValue(net.getSpeed(), "bps"));
+            System.out.format("   IPv4: %s %n", Arrays.toString(net.getIPv4addr()));
+            System.out.format("   IPv6: %s %n", Arrays.toString(net.getIPv6addr()));
+            boolean hasData = net.getBytesRecv() > 0 || net.getBytesSent() > 0 || net.getPacketsRecv() > 0
+                    || net.getPacketsSent() > 0;
+            System.out.format("   Traffic: received %s/%s%s; transmitted %s/%s%s %n",
+                    hasData ? net.getPacketsRecv() + " packets" : "?",
+                    hasData ? FormatUtil.formatBytes(net.getBytesRecv()) : "?",
+                    hasData ? " (" + net.getInErrors() + " err)" : "",
+                    hasData ? net.getPacketsSent() + " packets" : "?",
+                    hasData ? FormatUtil.formatBytes(net.getBytesSent()) : "?",
+                    hasData ? " (" + net.getOutErrors() + " err)" : "");
+        }
+    }
+
+    private static void printNetworkParameters(NetworkParams networkParams) {
+        System.out.println("Network parameters:");
+        System.out.format(" Host name: %s%n", networkParams.getHostName());
+        System.out.format(" Domain name: %s%n", networkParams.getDomainName());
+        System.out.format(" DNS servers: %s%n", Arrays.toString(networkParams.getDnsServers()));
+        System.out.format(" IPv4 Gateway: %s%n", networkParams.getIpv4DefaultGateway());
+        System.out.format(" IPv6 Gateway: %s%n", networkParams.getIpv6DefaultGateway());
+    }
+
+    private static void printDisplays(List<Display> list) {
+        System.out.println("Displays:");
+        int i = 0;
+        for (Display display : list) {
+            System.out.println(" Display " + i + ":");
+            System.out.println(display.toString());
+            i++;
+        }
+    }
+
+    private static void printUsbDevices(List<UsbDevice> list) {
+        System.out.println("USB Devices:");
+        for (UsbDevice usbDevice : list) {
+            System.out.println(usbDevice.toString());
+        }
+    }
+
+    public static void main(String[] args) throws IOException {
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        Logger rootLogger = loggerContext.getLogger("ROOT");
+        rootLogger.setLevel(Level.INFO);
+
+        SystemInfo si = new SystemInfo();
+        HardwareAbstractionLayer hal = si.getHardware();
+        OperatingSystem os = si.getOperatingSystem();
+
+        Predicate<OSProcess> chromeProcessFilter = p -> p.getName().equalsIgnoreCase("Chrome");
+        List<OSProcess> procs = os.getProcesses(chromeProcessFilter, OperatingSystem.ProcessSorting.CPU_DESC, 10);
+        procs.forEach(System.out::println);
+/*
+        System.out.println("Checking computer system...");
+        printComputerSystem(hal.getComputerSystem());
+
+        System.out.println("Checking Processor...");
+        printProcessor(hal.getProcessor());
+
+        System.out.println("Checking Memory...");
+        printMemory(hal.getMemory());
+
+        System.out.println("Checking CPU...");
+        printCpu(hal.getProcessor());
+
+        System.out.println("Checking Processes...");
+        printProcesses(os, hal.getMemory());*/
+
+        /*while (!Thread.interrupted()) {
+            //printSensors(hal.getSensors());
+            printProcesses(os, hal.getMemory());
+            try {
+                Thread.sleep(1_000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }*/
+
+        //System.out.println("Checking Sensors...");
+        //printSensors(hal.getSensors());
+/*
+        System.out.println("Checking Power sources...");
+        printPowerSources(hal.getPowerSources());
+
+        System.out.println("Checking Disks...");
+        printDisks(hal.getDiskStores());
+
+        System.out.println("Checking File System...");
+        printFileSystem(os.getFileSystem());
+
+        System.out.println("Checking Network interfaces...");
+        printNetworkInterfaces(hal.getNetworkIFs());
+
+        System.out.println("Checking Network parameterss...");
+        printNetworkParameters(os.getNetworkParams());
+
+        System.out.println("Checking Displays...");
+        printDisplays(hal.getDisplays());
+
+        System.out.println("Checking USB Devices...");
+        printUsbDevices(hal.getUsbDevices(true));*/
+    }
+}

+ 91 - 0
agent/src/test/java/WebSocketClient.java

@@ -0,0 +1,91 @@
+import cn.reghao.devops.common.machine.Machine;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.websocketx.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+public class WebSocketClient {
+    private final URI uri;
+    private Channel ch;
+    private static final EventLoopGroup group = new NioEventLoopGroup();
+
+    public WebSocketClient(final String uri) {
+        this.uri = URI.create(uri);
+    }
+
+    public void open() throws Exception {
+        Bootstrap b = new Bootstrap();
+        String protocol = uri.getScheme();
+        if (!"ws".equals(protocol)) {
+            throw new IllegalArgumentException("Unsupported protocol: " + protocol);
+        }
+
+        // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
+        // If you change it to V00, ping is not supported and remember to change
+        // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
+        final WebSocketClientHandler handler =
+                new WebSocketClientHandler(
+                        WebSocketClientHandshakerFactory.newHandshaker(
+                                uri, WebSocketVersion.V13, null, false, HttpHeaders.EMPTY_HEADERS, 1280000));
+
+        b.group(group)
+                .channel(NioSocketChannel.class)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    public void initChannel(SocketChannel ch) throws Exception {
+                        ChannelPipeline pipeline = ch.pipeline();
+                        pipeline.addLast("http-codec", new HttpClientCodec());
+                        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
+                        pipeline.addLast("ws-handler", handler);
+                    }
+                });
+
+        //System.out.println("WebSocket Client connecting");
+        ch = b.connect(uri.getHost(), uri.getPort()).sync().channel();
+        handler.handshakeFuture().sync();
+    }
+
+    public void close() throws InterruptedException {
+        System.out.println("WebSocket Client sending close");
+        ch.writeAndFlush(new CloseWebSocketFrame());
+        ch.closeFuture().sync();
+        group.shutdownGracefully();
+    }
+
+    public void send(final String text) throws IOException {
+        TextWebSocketFrame textMessage = new TextWebSocketFrame(text);
+        ch.writeAndFlush(textMessage);
+    }
+
+    public void eval1(final String text) throws IOException {
+        ByteBuf buffer = Unpooled.buffer();
+        buffer.writeBytes(text.getBytes(StandardCharsets.UTF_8));
+
+        BinaryWebSocketFrame binaryMessage = new BinaryWebSocketFrame(buffer);
+        ch.writeAndFlush(binaryMessage);
+    }
+
+    public static void main(String[] args) throws Exception {
+        String url = String.format("ws://%s:%s/ws/agent?token=%s", "localhost", 4020, Machine.ID);
+        final WebSocketClient client = new WebSocketClient(url);
+        client.open();
+        client.eval1("哈哈哈哈");
+    }
+}

+ 88 - 0
agent/src/test/java/WebSocketClientHandler.java

@@ -0,0 +1,88 @@
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import io.netty.util.CharsetUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Stephen Mallette (http://stephen.genoprime.com)
+ */
+@Slf4j
+public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
+    private final WebSocketClientHandshaker handshaker;
+    private ChannelPromise handshakeFuture;
+
+    public WebSocketClientHandler(final WebSocketClientHandshaker handshaker) {
+        this.handshaker = handshaker;
+    }
+
+    public ChannelFuture handshakeFuture() {
+        return handshakeFuture;
+    }
+
+    @Override
+    public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
+        handshakeFuture = ctx.newPromise();
+    }
+
+    @Override
+    public void channelActive(final ChannelHandlerContext ctx) throws Exception {
+        handshaker.handshake(ctx.channel());
+    }
+
+    @Override
+    public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
+        System.out.println("WebSocket Client disconnected!");
+    }
+
+    @Override
+    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
+        final Channel ch = ctx.channel();
+        if (!handshaker.isHandshakeComplete()) {
+            // web socket client connected
+            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
+            handshakeFuture.setSuccess();
+            return;
+        }
+
+        if (msg instanceof FullHttpResponse) {
+            final FullHttpResponse response = (FullHttpResponse) msg;
+            throw new Exception("Unexpected FullHttpResponse (getStatus=" + response.getStatus() + ", content="
+                    + response.content().toString(CharsetUtil.UTF_8) + ')');
+        }
+
+        final WebSocketFrame frame = (WebSocketFrame) msg;
+        if (frame instanceof TextWebSocketFrame) {
+            final TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
+            // uncomment to print request
+            // logger.info(textFrame.text());
+        } else if (frame instanceof PongWebSocketFrame) {
+        } else if (frame instanceof CloseWebSocketFrame)
+            ch.close();
+        else if (frame instanceof BinaryWebSocketFrame) {
+            // uncomment to print request
+            // logger.info(frame.content().toString());
+        }
+
+    }
+
+    @Override
+    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
+        cause.printStackTrace();
+
+        if (!handshakeFuture.isDone()) {
+            handshakeFuture.setFailure(cause);
+        }
+
+        ctx.close();
+    }
+}

+ 73 - 0
common/pom.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>devops</artifactId>
+        <groupId>cn.reghao.devops</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>common</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java</artifactId>
+            <version>3.2.12</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.docker-java</groupId>
+            <artifactId>docker-java-transport-httpclient5</artifactId>
+            <version>3.2.12</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+            <version>6.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.eclipse.jgit</groupId>
+            <artifactId>org.eclipse.jgit</artifactId>
+            <version>6.4.0.202211300538-r</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jgit</groupId>
+            <artifactId>org.eclipse.jgit.ssh.apache</artifactId>
+            <version>6.4.0.202211300538-r</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-model</artifactId>
+            <version>3.6.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.shared</groupId>
+            <artifactId>maven-invoker</artifactId>
+            <version>3.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.10.0</version>
+        </dependency>
+    </dependencies>
+</project>

+ 18 - 0
common/src/main/java/cn/reghao/devops/common/agent/app/dto/AppInfo.java

@@ -0,0 +1,18 @@
+package cn.reghao.devops.common.agent.app.dto;
+
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-03-08 10:01:20
+ */
+@Getter
+public class AppInfo {
+    private final String packType;
+    private final String appId;
+
+    public AppInfo(String packType, String appId) {
+        this.packType = packType;
+        this.appId = appId;
+    }
+}

+ 12 - 0
common/src/main/java/cn/reghao/devops/common/agent/app/iface/AppDeploy.java

@@ -0,0 +1,12 @@
+package cn.reghao.devops.common.agent.app.iface;
+
+import cn.reghao.devops.common.msg.event.EvtAppDeploy;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+
+/**
+ * @author reghao
+ * @date 2023-03-06 16:21:07
+ */
+public interface AppDeploy {
+    EvtAppStatResult deploy(EvtAppDeploy deployParam) throws Exception;
+}

+ 17 - 0
common/src/main/java/cn/reghao/devops/common/agent/app/iface/AppStat.java

@@ -0,0 +1,17 @@
+package cn.reghao.devops.common.agent.app.iface;
+
+import cn.reghao.devops.common.msg.event.EvtAppStat;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+
+/**
+ * 应用状态管理
+ *
+ * @author reghao
+ * @date 2023-03-06 15:22:48
+ */
+public interface AppStat {
+    EvtAppStatResult start(EvtAppStat evtAppStat) throws Exception;
+    EvtAppStatResult stop(EvtAppStat evtAppStat) throws Exception;
+    EvtAppStatResult restart(EvtAppStat evtAppStat) throws Exception;
+    EvtAppStatResult stat(EvtAppStat evtAppStat) throws Exception;
+}

+ 28 - 0
common/src/main/java/cn/reghao/devops/common/agent/app/iface/impl/AppDeployService.java

@@ -0,0 +1,28 @@
+package cn.reghao.devops.common.agent.app.iface.impl;
+
+import cn.reghao.devops.common.agent.app.iface.AppDeploy;
+import cn.reghao.devops.common.msg.constant.PackType;
+import cn.reghao.devops.common.msg.event.EvtAppDeploy;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+
+/**
+ * @author reghao
+ * @date 2023-03-06 16:21:30
+ */
+public class AppDeployService implements AppDeploy {
+    private final DockerApp dockerApp;
+
+    public AppDeployService(DockerApp dockerApp) {
+        this.dockerApp = dockerApp;
+    }
+
+    @Override
+    public EvtAppStatResult deploy(EvtAppDeploy deployParam) throws Exception {
+        String packType = deployParam.getPackType();
+        if (packType.equals(PackType.docker.name())) {
+            return dockerApp.deploy(deployParam);
+        } else {
+            throw new Exception("zip 打包没有实现");
+        }
+    }
+}

+ 66 - 0
common/src/main/java/cn/reghao/devops/common/agent/app/iface/impl/AppStatService.java

@@ -0,0 +1,66 @@
+package cn.reghao.devops.common.agent.app.iface.impl;
+
+import cn.reghao.devops.common.agent.app.iface.AppStat;
+import cn.reghao.devops.common.msg.constant.PackType;
+import cn.reghao.devops.common.msg.event.EvtAppStat;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+
+/**
+ * @author reghao
+ * @date 2023-03-06 16:19:24
+ */
+public class AppStatService implements AppStat {
+    private final DockerApp dockerApp;
+
+    public AppStatService(DockerApp dockerApp) {
+        this.dockerApp = dockerApp;
+    }
+
+    @Override
+    public EvtAppStatResult start(EvtAppStat evtAppStat) throws Exception {
+        String packType = evtAppStat.getPackType();
+        String appId = evtAppStat.getAppId();
+
+        if (packType.equals(PackType.docker.name())) {
+            return dockerApp.start(appId);
+        } else {
+            throw new Exception("zip 打包没有实现");
+        }
+    }
+
+    @Override
+    public EvtAppStatResult stop(EvtAppStat evtAppStat) throws Exception {
+        String packType = evtAppStat.getPackType();
+        String appId = evtAppStat.getAppId();
+
+        if (packType.equals(PackType.docker.name())) {
+            return dockerApp.stop(appId);
+        } else {
+            throw new Exception("zip 打包没有实现");
+        }
+    }
+
+    @Override
+    public EvtAppStatResult restart(EvtAppStat evtAppStat) throws Exception {
+        String packType = evtAppStat.getPackType();
+        String appId = evtAppStat.getAppId();
+
+        if (packType.equals(PackType.docker.name())) {
+            return dockerApp.restart(appId);
+        } else {
+            throw new Exception("zip 打包没有实现");
+        }
+    }
+
+    @Override
+    public EvtAppStatResult stat(EvtAppStat evtAppStat) throws Exception {
+        String packType = evtAppStat.getPackType();
+        String appId = evtAppStat.getAppId();
+
+        if (packType.equals(PackType.docker.name())) {
+            return dockerApp.stat(appId);
+        } else {
+            throw new Exception("zip 打包没有实现");
+        }
+    }
+}

+ 84 - 0
common/src/main/java/cn/reghao/devops/common/agent/app/iface/impl/DockerApp.java

@@ -0,0 +1,84 @@
+package cn.reghao.devops.common.agent.app.iface.impl;
+
+import cn.reghao.devops.common.docker.Docker;
+import cn.reghao.devops.common.docker.DockerImpl;
+import cn.reghao.devops.common.docker.po.Config;
+import cn.reghao.devops.common.docker.po.HostConfig;
+import cn.reghao.devops.common.machine.Machine;
+import cn.reghao.devops.common.msg.constant.NodeStatus;
+import cn.reghao.devops.common.msg.event.EvtAppDeploy;
+import cn.reghao.devops.common.msg.event.EvtAppStatResult;
+import cn.reghao.devops.common.util.NotAvailable;
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import com.github.dockerjava.api.command.InspectContainerResponse;
+
+/**
+ * @author reghao
+ * @date 2023-03-06 15:26:22
+ */
+public class DockerApp {
+    private final Docker docker = new DockerImpl();
+
+    public EvtAppStatResult deploy(EvtAppDeploy deployParam) throws Exception {
+        String appId = deployParam.getAppId();
+        String packagePath = deployParam.getPackagePath();
+        Config containerConfig = JsonConverter.jsonToObject(deployParam.getStartScript(), Config.class);
+        if (containerConfig == null) {
+            containerConfig = new Config(packagePath);
+        } else {
+            containerConfig.setImage(packagePath);
+            if (containerConfig.getHostConfig() == null) {
+                containerConfig.setHostConfig(new HostConfig());
+            }
+        }
+
+        docker.pull(packagePath);
+        InspectContainerResponse containerInfo = docker.createAndRun(appId, containerConfig);
+        return getStat(appId, containerInfo);
+    }
+
+    public EvtAppStatResult start(String appId) throws Exception {
+        InspectContainerResponse containerInfo = docker.start(appId);
+        return getStat(appId, containerInfo);
+    }
+
+    public EvtAppStatResult stop(String appId) throws Exception {
+        InspectContainerResponse containerInfo = docker.stop(appId);
+        return getStat(appId, containerInfo);
+    }
+
+    public EvtAppStatResult restart(String appId) throws Exception {
+        InspectContainerResponse containerInfo = docker.restart(appId);
+        return getStat(appId, containerInfo);
+    }
+
+    public EvtAppStatResult stat(String appId) throws Exception {
+        InspectContainerResponse containerInfo = docker.inspectContainer(appId);
+        return getStat(appId, containerInfo);
+    }
+
+    private EvtAppStatResult getStat(String appId, InspectContainerResponse containerInfo) {
+        EvtAppStatResult appStatResult = new EvtAppStatResult(appId, Machine.ID);
+        String image = containerInfo.getConfig().getImage();
+        if (image != null) {
+            appStatResult.setCommitId(image.split(":")[1]);
+        } else {
+            appStatResult.setCommitId(NotAvailable.na.getDesc());
+        }
+
+        InspectContainerResponse.ContainerState state = containerInfo.getState();
+        if (state == null || Boolean.FALSE.equals(state.getRunning())) {
+            appStatResult.setStatus(NodeStatus.Offline.name());
+            appStatResult.setStartTime(null);
+            appStatResult.setPid(-1);
+        } else {
+            String startedAt = state.getStartedAt();
+            appStatResult.setStatus(NodeStatus.Online.name());
+            appStatResult.setStartTime(DateTimeConverter.localDateTime(startedAt));
+            appStatResult.setPid(state.getPidLong().intValue());
+        }
+
+        return appStatResult;
+    }
+}

+ 81 - 0
common/src/main/java/cn/reghao/devops/common/agent/machine/MachineEvent.java

@@ -0,0 +1,81 @@
+package cn.reghao.devops.common.agent.machine;
+
+import cn.reghao.devops.common.machine.Machine;
+import cn.reghao.devops.common.msg.MessageSender;
+import cn.reghao.devops.common.msg.MsgQueue;
+import cn.reghao.devops.common.msg.event.EvtAgentHeartbeat;
+import cn.reghao.devops.common.msg.event.EvtAgentStart;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author reghao
+ * @date 2021-09-03 09:22:42
+ */
+@Slf4j
+public class MachineEvent {
+    private final ScheduledExecutorService scheduler;
+    private ScheduledFuture<?> heartbeatFuture;
+    private final Machine machine;
+    private final MessageSender messageSender;
+    private final int heartbeatInterval;
+    
+    public MachineEvent(MessageSender messageSender, Machine machine, int heartbeatInterval) {
+        this.scheduler = ThreadPoolWrapper.scheduledThreadPool("heartbeat", 1);
+        this.machine = machine;
+        this.messageSender = messageSender;
+        this.heartbeatInterval = heartbeatInterval;
+    }
+
+    public void agentStart() {
+        EvtAgentStart evtAgentStart = machine.detail();
+        EventMessage eventMessage = EventMessage.evt(evtAgentStart);
+        pub(eventMessage);
+
+        agentHeartbeat();
+    }
+
+    public void agentShutdown() {
+        ThreadPoolWrapper.shutdownScheduler(scheduler);
+        EvtAgentHeartbeat machineStat = machine.stat();
+        EventMessage eventMessage = EventMessage.evt(machineStat);
+        pub(eventMessage);
+    }
+
+    public void agentHeartbeat() {
+        heartbeatFuture = scheduler.scheduleAtFixedRate(new Heartbeat(), 5, heartbeatInterval, TimeUnit.SECONDS);
+    }
+
+    public void pauseHeartbeat() {
+        if (!heartbeatFuture.isCancelled()) {
+            heartbeatFuture.cancel(true);
+        }
+    }
+
+    private void pub(EventMessage eventMessage) {
+        if (messageSender.isConnected()) {
+            try {
+                messageSender.send(MsgQueue.managerTopic(), eventMessage);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        } else {
+            log.error("连接断开");
+        }
+    }
+
+    class Heartbeat implements Runnable {
+        @Override
+        public void run() {
+            EvtAgentHeartbeat machineStat = machine.stat();
+            EventMessage eventMessage = EventMessage.evt(machineStat);
+            pub(eventMessage);
+        }
+    }
+}

+ 66 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/Bootstrap.java

@@ -0,0 +1,66 @@
+package cn.reghao.devops.common.build.chain;
+
+import cn.reghao.devops.common.build.chain.impl.BuildChainResult;
+import cn.reghao.jutil.jdk.exception.ExceptionUtil;
+import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.result.ResultStatus;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 15:58:42
+ */
+public class Bootstrap<K extends HandlerParam, V extends HandlerResult> implements Supplier<HandlerResult> {
+    private Handler<K, V> first;
+    private final K buildParam;
+    private final V buildResult;
+
+    public Bootstrap(List<Handler<K, V>> list, K buildParam, V buildResult) {
+        init(list);
+        this.buildParam = buildParam;
+        this.buildResult = buildResult;
+    }
+
+    @Override
+    public V get() {
+        Result result;
+        try {
+            first.handle(buildParam, buildResult);
+            result = Result.result(ResultStatus.SUCCESS);
+        } catch (Exception e) {
+            String errMsg = ExceptionUtil.stackTrace(e);
+            if (errMsg.length() > 20_000) {
+                errMsg = errMsg.substring(0, 20_000);
+            }
+            result = Result.result(ResultStatus.FAIL, errMsg);
+        } finally {
+            /*if (buildResult instanceof BuildChainResult) {
+            }*/
+        }
+
+        BuildChainResult buildChainResult = (BuildChainResult) buildResult;
+        buildChainResult.setResult(result);
+        return buildResult;
+    }
+
+    /**
+     * 初始化职责链
+     *
+     * @param
+     * @return
+     * @date 2023-03-10 09:42:59
+     */
+    private void init(List<Handler<K, V>> list) {
+        if (list.isEmpty()) {
+            return;
+        }
+
+        this.first = list.get(0);
+        Handler<K, V> handler = first;
+        for (int i = 1; i < list.size(); i++) {
+            handler = handler.addNext(list.get(i));
+        }
+    }
+}

+ 20 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/BuildHandlers.java

@@ -0,0 +1,20 @@
+package cn.reghao.devops.common.build.chain;
+
+import cn.reghao.devops.common.build.chain.impl.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2022-05-09 11:14:25
+ */
+public class BuildHandlers {
+    public static List<Handler<BuildChainParam, BuildChainResult>> buildHandlerList() {
+        List<Handler<BuildChainParam, BuildChainResult>> list = new ArrayList<>();
+        list.add(new UpdateHandler());
+        list.add(new CompileHandler());
+        list.add(new PackHandler());
+        return list;
+    }
+}

+ 120 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/BuildTools.java

@@ -0,0 +1,120 @@
+package cn.reghao.devops.common.build.chain;
+
+import cn.reghao.devops.common.build.model.AppDto;
+import cn.reghao.devops.common.build.model.CompilerDto;
+import cn.reghao.devops.common.build.model.PackerDto;
+import cn.reghao.devops.common.build.model.RepoAuth;
+import cn.reghao.devops.common.build.model.constant.CompileType;
+import cn.reghao.devops.common.build.model.constant.RepoType;
+import cn.reghao.devops.common.build.tool.compiler.CodeCompiler;
+import cn.reghao.devops.common.build.tool.compiler.EmptyCompiler;
+import cn.reghao.devops.common.build.tool.compiler.MavenCompiler;
+import cn.reghao.devops.common.build.tool.compiler.ShellCompiler;
+import cn.reghao.devops.common.build.tool.packer.CodePacker;
+import cn.reghao.devops.common.build.tool.packer.DockerPack;
+import cn.reghao.devops.common.build.tool.packer.ZipPack;
+import cn.reghao.devops.common.build.tool.repo.CodeUpdater;
+import cn.reghao.devops.common.build.tool.repo.GitImpl;
+import cn.reghao.devops.common.docker.DockerImpl;
+import cn.reghao.devops.common.msg.constant.PackType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2022-05-07 14:35:11
+ */
+public class BuildTools {
+    // appId -> {codeUpdate,codeCompiler,codePacker} -> name
+    private static final Map<String, Map<String, String>> buildConfig = new HashMap<>();
+    private static final Map<String, CodeUpdater> codeUpdaterMap = new HashMap<>();
+    private static final Map<String, CodeCompiler> codeCompilerMap = new HashMap<>();
+    private static final Map<String, CodePacker> codePackerMap = new HashMap<>();
+
+    public static CodeUpdater getCodeUpdater(String appId) {
+        String codeUpdater = buildConfig.get(appId).get("codeUpdater");
+        return codeUpdaterMap.get(codeUpdater);
+    }
+
+    public static CodeCompiler getCodeCompiler(String appId) {
+        String codeCompiler = buildConfig.get(appId).get("codeCompiler");
+        return codeCompilerMap.get(codeCompiler);
+    }
+
+    public static CodePacker getCodePacker(String appId) {
+        String codePacker = buildConfig.get(appId).get("codePacker");
+        return codePackerMap.get(codePacker);
+    }
+
+    public static void removeApp(String appId) {
+        buildConfig.remove(appId);
+    }
+
+    public static void removeCodeUpdater(String repoAuthName) {
+        codeUpdaterMap.remove(repoAuthName);
+    }
+
+    public static void removeCodeCompiler(String compilerName) {
+        codeCompilerMap.remove(compilerName);
+    }
+
+    public static void removeCodePacker(String packerName) {
+        codePackerMap.remove(packerName);
+    }
+
+    public static void init(AppDto appDto) throws Exception {
+        String appId = appDto.getAppId();
+        RepoAuth repoAuth = appDto.getBuildConfigDto().getRepoAuth();
+        String repoAuthName = repoAuth.getName();
+        CodeUpdater codeUpdater = codeUpdaterMap.get(repoAuthName);
+        if (codeUpdater == null) {
+            switch (RepoType.valueOf(repoAuth.getType())) {
+                case git:
+                    codeUpdater = new GitImpl(repoAuth);
+                    break;
+                default:
+                    throw new Exception(repoAuth.getType() + " 仓库类型未实现");
+            }
+            codeUpdaterMap.put(repoAuthName, codeUpdater);
+        }
+        buildConfig.computeIfAbsent(appId, k -> new HashMap<>()).put("codeUpdater", repoAuthName);
+
+        CompilerDto compilerDto = appDto.getBuildConfigDto().getCompilerDto();
+        String compilerName = compilerDto.getName();
+        CodeCompiler codeCompiler = codeCompilerMap.get(compilerName);
+        if (codeCompiler == null) {
+            switch (CompileType.valueOf(compilerDto.getType())) {
+                case shell:
+                    codeCompiler = new ShellCompiler(compilerDto);
+                    break;
+                case maven:
+                    codeCompiler = new MavenCompiler(compilerDto.getHomePath());
+                    break;
+                case none:
+                    codeCompiler = new EmptyCompiler();
+                    break;
+                default:
+                    throw new Exception(compilerDto.getType() + " 编译类型未实现");
+            }
+            codeCompilerMap.put(compilerName, codeCompiler);
+        }
+        buildConfig.computeIfAbsent(appId, k -> new HashMap<>()).put("codeCompiler", compilerName);
+
+        PackerDto packerDto = appDto.getBuildConfigDto().getPackerDto();
+        String packerName = packerDto.getName();
+        CodePacker codePacker = codePackerMap.get(packerName);
+        if (codePacker == null) {
+            switch (PackType.valueOf(packerDto.getType())) {
+                case docker:
+                    codePacker = new DockerPack(packerDto.getTargetPath(), new DockerImpl());
+                    break;
+                default:
+                    codePacker = new ZipPack(packerDto.getBinDirname());
+                    break;
+            }
+            codePackerMap.put(packerName, codePacker);
+        }
+        buildConfig.computeIfAbsent(appId, k -> new HashMap<>()).put("codePacker", packerName);
+    }
+}

+ 16 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/Handler.java

@@ -0,0 +1,16 @@
+package cn.reghao.devops.common.build.chain;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 14:32:27
+ */
+public abstract class Handler<K extends HandlerParam, V extends HandlerResult> {
+    protected Handler<K, V> next;
+
+    public Handler<K, V> addNext(Handler<K, V> next) {
+        this.next = next;
+        return next;
+    }
+
+    public abstract void handle(final K buildParam, V buildResult) throws Exception;
+}

+ 11 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/HandlerParam.java

@@ -0,0 +1,11 @@
+package cn.reghao.devops.common.build.chain;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 15:56:38
+ */
+public class HandlerParam implements Serializable {
+    private static final long serialVersionUID = 1L;
+}

+ 11 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/HandlerResult.java

@@ -0,0 +1,11 @@
+package cn.reghao.devops.common.build.chain;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 15:56:46
+ */
+public class HandlerResult implements Serializable {
+    private static final long serialVersionUID = 1L;
+}

+ 17 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/impl/BuildChainParam.java

@@ -0,0 +1,17 @@
+package cn.reghao.devops.common.build.chain.impl;
+
+import cn.reghao.devops.common.build.chain.HandlerParam;
+import cn.reghao.devops.common.build.model.AppDto;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2022-05-07 15:41:14
+ */
+@AllArgsConstructor
+@Getter
+public class BuildChainParam extends HandlerParam {
+    private AppDto appDto;
+    private boolean deploy;
+}

+ 38 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/impl/BuildChainResult.java

@@ -0,0 +1,38 @@
+package cn.reghao.devops.common.build.chain.impl;
+
+import cn.reghao.devops.common.build.chain.HandlerResult;
+import cn.reghao.devops.common.build.model.AppDto;
+import cn.reghao.devops.common.build.model.BuildConfigDto;
+import cn.reghao.devops.common.build.model.BuildConsumedDto;
+import cn.reghao.devops.common.build.tool.repo.CommitInfo;
+import cn.reghao.jutil.jdk.result.Result;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2022-05-07 15:05:21
+ */
+@Setter
+@Getter
+public class BuildChainResult extends HandlerResult {
+    private String buildLogId;
+    private AppDto appDto;
+    @Deprecated
+    private BuildConfigDto buildConfigDto;
+    private String dockerfile;
+    private CommitInfo commitInfo;
+    private String packagePath;
+    private BuildConsumedDto buildConsumedDto;
+    private Result result;
+    private String buildBy;
+
+    public BuildChainResult(String buildLogId, AppDto appDto, String buildBy) {
+        this.buildLogId = buildLogId;
+        this.appDto = appDto;
+        this.buildConfigDto = appDto.getBuildConfigDto();
+        this.dockerfile = appDto.getDockerfile();
+        this.buildConsumedDto = new BuildConsumedDto();
+        this.buildBy = buildBy;
+    }
+}

+ 46 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/impl/CompileHandler.java

@@ -0,0 +1,46 @@
+package cn.reghao.devops.common.build.chain.impl;
+
+import cn.reghao.devops.common.build.chain.BuildTools;
+import cn.reghao.devops.common.build.chain.Handler;
+import cn.reghao.devops.common.build.model.AppDto;
+import cn.reghao.devops.common.build.model.CompilerDto;
+import cn.reghao.devops.common.build.model.LocalBuildDir;
+import cn.reghao.devops.common.build.model.constant.CompileType;
+import cn.reghao.devops.common.build.tool.compiler.CodeCompiler;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 16:02:44
+ */
+@Slf4j
+public class CompileHandler extends Handler<BuildChainParam, BuildChainResult> {
+    @Override
+    public void handle(final BuildChainParam buildParam, BuildChainResult buildResult) throws Exception {
+        AppDto appDto = buildParam.getAppDto();
+        String appId = appDto.getAppId();
+        log.info("编译 {} 代码...", appId);
+
+        CodeCompiler codeCompiler = BuildTools.getCodeCompiler(appId);
+        String appCompileDir = LocalBuildDir.compileDir + File.separator + appId;
+        String compileDir = appCompileDir + File.separator + appDto.getProjDirname();
+        long start = System.currentTimeMillis();
+        CompilerDto compilerDto = appDto.getBuildConfigDto().getCompilerDto();
+        String compileType = compilerDto.getType();
+        if (compileType.equals(CompileType.maven.getName())) {
+            codeCompiler.compile(appId, compileDir, appDto.getEnv(), appDto.getAppRootPath());
+        } else if (compileType.equals(CompileType.shell.getName())) {
+            codeCompiler.compile(appId, compileDir);
+        } else {
+            String errMsg = String.format("unknown compile type %s with app %s", compileType, appId);
+            throw new Exception(errMsg);
+        }
+
+        buildResult.getBuildConsumedDto().setCompileConsumed(System.currentTimeMillis()-start);
+        if (next != null) {
+            next.handle(buildParam, buildResult);
+        }
+    }
+}

+ 45 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/impl/PackHandler.java

@@ -0,0 +1,45 @@
+package cn.reghao.devops.common.build.chain.impl;
+
+import cn.reghao.devops.common.build.chain.BuildTools;
+import cn.reghao.devops.common.build.chain.Handler;
+import cn.reghao.devops.common.build.model.AppDto;
+import cn.reghao.devops.common.build.model.LocalBuildDir;
+import cn.reghao.devops.common.build.tool.packer.CodePacker;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 16:02:50
+ */
+@Slf4j
+public class PackHandler extends Handler<BuildChainParam, BuildChainResult> {
+    @Override
+    public void handle(final BuildChainParam buildParam, BuildChainResult buildResult) throws Exception {
+        AppDto appDto = buildParam.getAppDto();
+        String appId = appDto.getAppId();
+        log.info("打包 {} 应用...", appId);
+
+        String commitId = buildResult.getCommitInfo().getCommitId();
+        CodePacker codePacker = BuildTools.getCodePacker(appId);
+        String appCompileDir = LocalBuildDir.compileDir + File.separator + appId;
+        String compileDir = appCompileDir + File.separator + appDto.getProjDirname();
+        String appRootPath = compileDir + File.separator + appDto.getAppRootPath();
+        String dockerfile = appDto.getDockerfile();
+
+        long start = System.currentTimeMillis();
+        String localPath = codePacker.pack(appId, commitId, appRootPath, dockerfile);
+        buildResult.getBuildConsumedDto().setPackConsumed(System.currentTimeMillis()-start);
+
+        log.info("推送已构建的 {} 应用...", appId);
+        start = System.currentTimeMillis();
+        String packagePath = codePacker.push(localPath);
+        buildResult.setPackagePath(packagePath);
+        buildResult.getBuildConsumedDto().setPushConsumed(System.currentTimeMillis()-start);
+
+        if (next != null) {
+            next.handle(buildParam, buildResult);
+        }
+    }
+}

+ 54 - 0
common/src/main/java/cn/reghao/devops/common/build/chain/impl/UpdateHandler.java

@@ -0,0 +1,54 @@
+package cn.reghao.devops.common.build.chain.impl;
+
+import cn.reghao.devops.common.build.chain.BuildTools;
+import cn.reghao.devops.common.build.chain.Handler;
+import cn.reghao.devops.common.build.model.AppDto;
+import cn.reghao.devops.common.build.model.LocalBuildDir;
+import cn.reghao.devops.common.build.tool.BuilderUtil;
+import cn.reghao.devops.common.build.tool.repo.CodeUpdater;
+import cn.reghao.devops.common.build.tool.repo.CommitInfo;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+
+/**
+ * @author reghao
+ * @date 2022-05-06 16:02:38
+ */
+@Slf4j
+public class UpdateHandler extends Handler<BuildChainParam, BuildChainResult> {
+    @Override
+    public void handle(final BuildChainParam buildParam, BuildChainResult buildResult) throws Exception {
+        AppDto appDto = buildParam.getAppDto();
+        String appId = appDto.getAppId();
+        log.info("更新 {} 代码...", appId);
+
+        CodeUpdater codeUpdater = BuildTools.getCodeUpdater(appId);
+        long start = System.currentTimeMillis();
+        CommitInfo commitInfo = update(appDto, codeUpdater);
+        buildResult.getBuildConsumedDto().setPullConsumed(System.currentTimeMillis()-start);
+        buildResult.setCommitInfo(commitInfo);
+
+        if (next != null) {
+            next.handle(buildParam, buildResult);
+        }
+    }
+
+    public CommitInfo update(AppDto appDto, CodeUpdater codeUpdater) throws Exception {
+        String remote;
+        String branch;
+        String local = LocalBuildDir.localRepo + File.separator + appDto.getAppId();
+        remote = appDto.getAppRepo();
+        branch = appDto.getRepoBranch();
+        CommitInfo last = codeUpdater.latestCommitInfo(local, branch);
+        CommitInfo current = codeUpdater.pull(remote, branch, local);
+        if (last == null || last.getMsCommitTime() <= current.getMsCommitTime()) {
+            // 本地仓库不存在 or 本地仓库版本和远程仓库相同 or 本地仓库版本落后于远程仓库
+            BuilderUtil.copyToCompileDir(local, appDto);
+        } else if (last.getMsCommitTime() > current.getMsCommitTime()) {
+            throw new Exception("本地仓库代码比远程仓库更新");
+        }
+
+        return current;
+    }
+}

+ 32 - 0
common/src/main/java/cn/reghao/devops/common/build/model/AppDto.java

@@ -0,0 +1,32 @@
+package cn.reghao.devops.common.build.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-03-16 09:58:32
+ */
+@AllArgsConstructor
+@Getter
+public class AppDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String appId;
+    private String env;
+    // 仅在 cn.reghao.devops.manager.build.tool.BuilderUtil 中使用
+    @Deprecated
+    private String appType;
+    private String appRepo;
+    private String repoBranch;
+    private String projDirname;
+    private String appRootPath;
+    private String dockerfile;
+
+    private BuildConfigDto buildConfigDto;
+    /*private RepoAuth repoAuth;
+    private CompilerDto compilerDto;
+    private PackerDto packerDto;*/
+}

+ 16 - 0
common/src/main/java/cn/reghao/devops/common/build/model/BuildConfigDto.java

@@ -0,0 +1,16 @@
+package cn.reghao.devops.common.build.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-03-16 10:18:39
+ */
+@AllArgsConstructor
+@Getter
+public class BuildConfigDto {
+    private RepoAuth repoAuth;
+    private CompilerDto compilerDto;
+    private PackerDto packerDto;
+}

+ 22 - 0
common/src/main/java/cn/reghao/devops/common/build/model/BuildConsumedDto.java

@@ -0,0 +1,22 @@
+package cn.reghao.devops.common.build.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-03-16 10:31:35
+ */
+@Setter
+@Getter
+public class BuildConsumedDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // ms 时间戳
+    private Long pullConsumed;
+    private Long compileConsumed;
+    private Long packConsumed;
+    private Long pushConsumed;
+}

+ 21 - 0
common/src/main/java/cn/reghao/devops/common/build/model/CompilerDto.java

@@ -0,0 +1,21 @@
+package cn.reghao.devops.common.build.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-03-16 09:52:17
+ */
+@AllArgsConstructor
+@Getter
+public class CompilerDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String type;
+    private String name;
+    private String homePath;
+    private String compileScript;
+}

+ 20 - 0
common/src/main/java/cn/reghao/devops/common/build/model/LocalBuildDir.java

@@ -0,0 +1,20 @@
+package cn.reghao.devops.common.build.model;
+
+import java.io.Serializable;
+
+/**
+ * 本地构建目录
+ *
+ * @author reghao
+ * @date 2020-03-09 14:36:41
+ */
+public class LocalBuildDir implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // 本地仓库
+    public static String localRepo;
+    // 编译目录
+    public static String compileDir;
+    // 应用打包后存放的目录
+    public static String packDir;
+}

+ 21 - 0
common/src/main/java/cn/reghao/devops/common/build/model/PackerDto.java

@@ -0,0 +1,21 @@
+package cn.reghao.devops.common.build.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-03-16 09:52:31
+ */
+@AllArgsConstructor
+@Getter
+public class PackerDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String type;
+    private String name;
+    private String targetPath;
+    private String binDirname;
+}

+ 23 - 0
common/src/main/java/cn/reghao/devops/common/build/model/RepoAuth.java

@@ -0,0 +1,23 @@
+package cn.reghao.devops.common.build.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2023-03-16 09:24:53
+ */
+@AllArgsConstructor
+@Getter
+public class RepoAuth implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String type;
+    private String name;
+    private String authType;
+    private String username;
+    private String password;
+    private String rsaPrikey;
+}

+ 15 - 0
common/src/main/java/cn/reghao/devops/common/build/model/constant/CompileType.java

@@ -0,0 +1,15 @@
+package cn.reghao.devops.common.build.model.constant;
+
+/**
+ * 编译方式类型
+ *
+ * @author reghao
+ * @date 2019-10-18 14:31:29
+ */
+public enum CompileType {
+    none, shell, maven;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 19 - 0
common/src/main/java/cn/reghao/devops/common/build/model/constant/EnvType.java

@@ -0,0 +1,19 @@
+package cn.reghao.devops.common.build.model.constant;
+
+/**
+ * @author reghao
+ * @date 2023-12-01 14:17:44
+ */
+public enum EnvType {
+    dev("开发环境"),test("测试环境"),uat("预发布环境"),prod("生产环境");
+
+    private final String desc;
+
+    EnvType(String desc) {
+        this.desc = desc;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+}

+ 13 - 0
common/src/main/java/cn/reghao/devops/common/build/model/constant/RepoAuthType.java

@@ -0,0 +1,13 @@
+package cn.reghao.devops.common.build.model.constant;
+
+/**
+ * @author reghao
+ * @date 2021-02-05 18:50:01
+ */
+public enum RepoAuthType {
+    http, ssh, none;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 13 - 0
common/src/main/java/cn/reghao/devops/common/build/model/constant/RepoType.java

@@ -0,0 +1,13 @@
+package cn.reghao.devops.common.build.model.constant;
+
+/**
+ * @author reghao
+ * @date 2021-02-05 22:50:41
+ */
+public enum RepoType {
+    git;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 99 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/BuilderUtil.java

@@ -0,0 +1,99 @@
+package cn.reghao.devops.common.build.tool;
+
+import cn.reghao.devops.common.build.model.AppDto;
+import cn.reghao.devops.common.build.model.LocalBuildDir;
+import cn.reghao.devops.common.util.text.FileProcessor;
+import cn.reghao.devops.common.util.text.ReplaceCharacter;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
+
+/**
+ * @author reghao
+ * @date 2019-11-16 21:55:31
+ */
+public class BuilderUtil {
+    /**
+     * 复制源码到编译目录
+     *
+     * @param appLocalRepo 应用代码的本地仓库位置
+     * @date 2019-12-03 上午9:55
+     */
+    public static void copyToCompileDir(String appLocalRepo, AppDto appDto) throws IOException {
+        String appCompileDir = LocalBuildDir.compileDir + File.separator + appDto.getAppId();
+        File compileDir = new File(appCompileDir);
+        if (!compileDir.exists() && !compileDir.mkdirs()) {
+            throw new IOException(compileDir + "不存在");
+        } else {
+            FileUtils.cleanDirectory(compileDir);
+        }
+
+        copyDirContentToDir(appLocalRepo, appCompileDir);
+        // TODO Dotnet 应用需要额外的处理
+        if (appDto.getAppType().equals("dotnetCore")) {
+            // Dotnet 应用需要替换一些字符
+            replaceCharacter(appCompileDir);
+        }
+    }
+
+    static void copyDirContentToDir(String src, String dst) throws IOException {
+        File srcDir = new File(src);
+        File dstDir = new File(dst);
+        for (File file : Objects.requireNonNull(srcDir.listFiles())) {
+            if (file.isDirectory()) {
+                String dirname = file.getName();
+                File dstdir = new File(dst + File.separator + dirname);
+                FileUtils.copyDirectory(file, dstdir);
+            } else {
+                FileUtils.copyFileToDirectory(file, dstDir);
+            }
+        }
+    }
+
+    /**
+     * TODO 判断文件编码
+     *
+     * @param
+     * @return
+     * @date 2020-08-18 上午11:00
+     */
+    public static void replaceCharacter(String appCompileDir) {
+        traversal(appCompileDir, new ReplaceCharacter());
+    }
+
+    static void traversal(String dirPath, FileProcessor processor) {
+        Path path = Paths.get(dirPath);
+        try {
+            Files.walkFileTree(path, new FileVisitor<Path>() {
+                @Override
+                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+                    return FileVisitResult.CONTINUE;
+                }
+
+                // 处理目录中的文件
+                @Override
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                    processor.process(file.toFile());
+
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+}

+ 18 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/compiler/CodeCompiler.java

@@ -0,0 +1,18 @@
+package cn.reghao.devops.common.build.tool.compiler;
+
+/**
+ * 编译代码
+ *
+ * @author reghao
+ * @date 2020-01-21 16:17:22
+ */
+public interface CodeCompiler {
+    /**
+     * @param appId 应用标识
+     * @param appCompileHome 编译应用代码所处的目录
+     * @date 2020-01-21 下午4:20
+     */
+    void compile(String appId, String appCompileHome) throws Exception;
+    default void compile(String appId, String appCompileHome, String env, String appRootPath) throws Exception {
+    }
+}

+ 13 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/compiler/EmptyCompiler.java

@@ -0,0 +1,13 @@
+package cn.reghao.devops.common.build.tool.compiler;
+
+/**
+ * 没有编译器
+ *
+ * @author reghao
+ * @date 2019-10-12 23:55:01
+ */
+public class EmptyCompiler implements CodeCompiler {
+    @Override
+    public void compile(String appId, String appCompileHome) throws Exception {
+    }
+}

+ 119 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/compiler/MavenCompiler.java

@@ -0,0 +1,119 @@
+package cn.reghao.devops.common.build.tool.compiler;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.shared.invoker.*;
+import org.apache.maven.shared.utils.cli.CommandLineException;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Maven 编译,打包
+ *
+ * @author reghao
+ * @date 2019-10-12 23:55:01
+ */
+@Slf4j
+public class MavenCompiler implements CodeCompiler {
+    private final MavenXpp3Reader reader;
+    private final Invoker invoker;
+    private final String[] standaloneArgs;
+
+    public MavenCompiler(String mavenHome) {
+        System.setProperty("maven.home", mavenHome);
+        this.invoker = new DefaultInvoker();
+        this.reader = new MavenXpp3Reader();
+        this.standaloneArgs = new String[]{"clean", "package", "-Dmaven.test.skip=true"};
+    }
+
+    @Override
+    public void compile(String appId, String appCompileHome) throws Exception {
+        throw new Exception("未实现此方法");
+    }
+
+    @Override
+    public void compile(String appId, String appCompileHome, String env, String appRootPath) throws Exception {
+        String pomPath = appCompileHome + "/pom.xml";
+        boolean multiModule = isMultiModule(pomPath);
+        MavenBuild mavenBuild;
+        if (multiModule) {
+            mavenBuild = setMultiModuleInvocationRequest(pomPath, env, appRootPath);
+        } else {
+            mavenBuild = setStandaloneInvocationRequest(pomPath, env);
+        }
+
+        InvocationResult result = invoker.execute(mavenBuild.invocationRequest);
+        if (result.getExitCode() != 0) {
+            CommandLineException exception = result.getExecutionException();
+            if (exception != null) {
+                throw exception;
+            }
+            throw new Exception(mavenBuild.stringBuilder.toString());
+        }
+    }
+
+    private boolean isMultiModule(String pomPath) throws IOException, XmlPullParserException {
+        FileInputStream fis = new FileInputStream(pomPath);
+        Model model = reader.read(fis);
+        String packaging = model.getPackaging();
+        return "pom".equals(packaging);
+    }
+
+    /**
+     * mvn clean package -Dmaven.test.skip=true -Pdev
+     *
+     * @param
+     * @return
+     * @date 2022-05-12 下午2:56
+     */
+    private MavenBuild setStandaloneInvocationRequest(String pomPath, String env) {
+        MavenBuild mavenBuild = new MavenBuild();
+        InvocationRequest request = mavenBuild.invocationRequest;
+        request.setPomFile(new File(pomPath));
+        // mvn clean package -Dmaven.test.skip=true
+        request.setGoals(Arrays.asList(standaloneArgs));
+        // -Penv
+        request.setProfiles(Collections.singletonList(env));
+        request.setOutputHandler(s -> mavenBuild.stringBuilder.append(s).append(System.lineSeparator()));
+        return mavenBuild;
+    }
+
+    /**
+     * mvn clean package -Dmaven.test.skip=true -am -Pdev -pl manager
+     *
+     * @param
+     * @return
+     * @date 2020-05-12 上午10:36
+     */
+    private MavenBuild setMultiModuleInvocationRequest(String pomPath, String env, String appDirname) {
+        MavenBuild mavenBuild = new MavenBuild();
+        InvocationRequest request = mavenBuild.invocationRequest;
+        request.setPomFile(new File(pomPath));
+        // mvn clean package -Dmaven.test.skip=true
+        request.setGoals(Arrays.asList(standaloneArgs));
+        // -Penv
+        request.setProfiles(Collections.singletonList(env));
+        // -am
+        request.setAlsoMake(true);
+        // -pl app
+        request.setProjects(Collections.singletonList(appDirname));
+        request.setOutputHandler(s -> mavenBuild.stringBuilder.append(s).append(System.lineSeparator()));
+        return mavenBuild;
+    }
+
+    static class MavenBuild {
+        InvocationRequest invocationRequest;
+        StringBuilder stringBuilder;
+
+        MavenBuild() {
+            this.invocationRequest = new DefaultInvocationRequest();
+            this.stringBuilder = new StringBuilder();
+        }
+    }
+}

+ 33 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/compiler/ShellCompiler.java

@@ -0,0 +1,33 @@
+package cn.reghao.devops.common.build.tool.compiler;
+
+import cn.reghao.devops.common.build.model.CompilerDto;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.jutil.jdk.shell.ShellExecutor;
+import cn.reghao.jutil.jdk.shell.ShellResult;
+
+/**
+ * 编译代码
+ *
+ * @author reghao
+ * @date 2019-10-12 23:55:01
+ */
+public class ShellCompiler implements CodeCompiler {
+    private final ShellExecutor shell;
+    private final String[] compileCmds;
+
+    public ShellCompiler(CompilerDto compilerDto) {
+        this.compileCmds = compilerDto.getCompileScript().split(" && ");
+        this.shell = new ShellExecutor();
+    }
+
+    @Override
+    public void compile(String appId, String appCompileHome) throws Exception {
+        ShellResult result;
+        for (String compileScript : compileCmds) {
+            result = shell.exec(appCompileHome, compileScript.split("\\s+"));
+            if (!result.isSuccess()) {
+                throw new Exception(JsonConverter.objectToJson(result.getResult()));
+            }
+        }
+    }
+}

+ 24 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/packer/CodePacker.java

@@ -0,0 +1,24 @@
+package cn.reghao.devops.common.build.tool.packer;
+
+/**
+ * 打包应用
+ * TODO 考虑一下如何打 apt,yum 之类的包以及如何运行这些包
+ *
+ * @author reghao
+ * @date 2020-01-21 16:18:37
+ */
+public interface CodePacker {
+    /**
+     * 打包方式确定运行方式
+     *
+     * @param appId 应用 ID
+     * @param commitId 应用版本
+     * @param appRootPath 应用根目录
+     * @param dockerfile Docker 打包镜像需要的配置
+     * @return 返回可执行应用的路径
+     *
+     * @date 2020-01-21 下午4:19
+     */
+    String pack(String appId, String commitId, String appRootPath, String dockerfile) throws Exception;
+    String push(String localPath) throws Exception;
+}

+ 37 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/packer/DockerPack.java

@@ -0,0 +1,37 @@
+package cn.reghao.devops.common.build.tool.packer;
+
+import cn.reghao.devops.common.docker.Docker;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+
+/**
+ * 打包为 docker 应用
+ *
+ * @author reghao
+ * @date 2019-10-14 22:02:04
+ */
+@Slf4j
+public class DockerPack implements CodePacker {
+    private final String targetPath;
+    private final Docker docker;
+
+    public DockerPack(String targetPath, Docker docker) {
+        this.targetPath = targetPath;
+        this.docker = docker;
+    }
+
+    @Override
+    public String pack(String appId, String commitId, String appRootPath, String dockerfile) throws Exception {
+        String repo = targetPath + File.separator + appId;
+        String image = repo + ":" + commitId;
+        docker.build(image, appRootPath, dockerfile);
+        return image;
+    }
+
+    @Override
+    public String push(String localPath) throws Exception {
+        docker.push(localPath);
+        return localPath;
+    }
+}

+ 180 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/packer/ZipFiles.java

@@ -0,0 +1,180 @@
+package cn.reghao.devops.common.build.tool.packer;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * TODO: 提升压缩和解压性能
+ *
+ * @author reghao
+ * @date 2019-08-27 17:47:36
+ */
+@Slf4j
+public class ZipFiles {
+    /**
+     * 将目录压缩为一个文件
+     *
+     * @param
+     * @date 2019-08-28 上午12:42
+     */
+    public static void zip(String src, String dst) throws IOException {
+        // TODO: 判断 dst 是否为 src 的子目录
+        File srcFile = new File(src);
+        if (!srcFile.exists()) {
+            log.info("{} 不存在", src);
+            return;
+        }
+
+        String filePath;
+        if (dst.endsWith(".zip")) {
+            filePath = dst;
+        } else {
+            filePath = dst + ".zip";
+        }
+
+        File dstFile = new File(filePath);
+        if (dstFile.exists() && !dstFile.delete()) {
+            log.info("删除 {} 失败", dst);
+            return;
+        }
+
+        ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(dstFile));
+        zipFile(null, srcFile, zipOut);
+        zipOut.close();
+    }
+
+    /**
+     * 将目录中的文件压缩为一个文件
+     *
+     * @param
+     * @return
+     * @date 2020-03-11 下午11:12
+     */
+    public static void zip(File src, String dst) throws IOException {
+        // TODO: 判断 dst 是否为 src 的子目录
+        String filePath;
+        if (dst.endsWith(".zip")) {
+            filePath = dst;
+        } else {
+            filePath = dst + ".zip";
+        }
+        File dstFile = new File(filePath);
+        if (dstFile.exists() && !dstFile.delete()) {
+            log.info("删除 {} 失败", dst);
+            return;
+        }
+
+        ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(dstFile));
+        for (File file : Objects.requireNonNull(src.listFiles())) {
+            zipFile(null, file, zipOut);
+        }
+
+        zipOut.close();
+    }
+
+    /**
+     * 压缩
+     *
+     * @param
+     * @return
+     * @date 2019-08-28 上午12:42
+     */
+    private static void zipFile(String srcRoot, File file, ZipOutputStream zipOut) {
+        if (file == null || !file.exists()) {
+            return;
+        }
+
+        InputStream in;
+        try {
+            if (file.isFile()) {
+                if (srcRoot == null) {
+                    zipOut.putNextEntry(new ZipEntry(file.getName()));
+                } else {
+                    zipOut.putNextEntry(new ZipEntry(srcRoot + File.separator + file.getName()));
+                }
+
+                in = new FileInputStream(file);
+                int tmp;
+                while ((tmp = in.read()) != -1) {
+                    zipOut.write(tmp);
+                }
+                in.close();
+            } else if (file.isDirectory() && Objects.requireNonNull(file.listFiles()).length == 0) {
+                if (srcRoot == null) {
+                    zipOut.putNextEntry(new ZipEntry(file.getName() + File.separator));
+                } else {
+                    zipOut.putNextEntry(new ZipEntry(srcRoot + File.separator + file.getName() + File.separator));
+                }
+            } else {
+                File[] files = file.listFiles();
+                assert files != null;
+                Arrays.asList(files).forEach(file1 -> {
+                    if (srcRoot == null) {
+                        zipFile(file.getName(), file1, zipOut);
+                    } else {
+                        zipFile(srcRoot + File.separator + file.getName(), file1, zipOut);
+                    }
+                });
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 解压到指定目录
+     *
+     * @param
+     * @return
+     * @date 2019-08-28 上午12:41
+     */
+    public static void unzip(String src, String dest) throws IOException {
+        File file = new File(src);
+        if (!file.exists()) {
+            log.info("{} 不存在", src);
+            return;
+        }
+
+        File destFile = new File(dest);
+        if (!destFile.exists()) {
+            FileUtils.forceMkdir(destFile);
+        }
+
+        java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(file);
+        ZipInputStream zipIn = new ZipInputStream(new FileInputStream(file));
+        ZipEntry entry;
+        while ((entry = zipIn.getNextEntry()) != null) {
+            String filePath = dest + File.separator + entry.getName();
+            String dirPath = dirPath(filePath);
+            File dir = new File(dirPath);
+            if (!dir.exists()) {
+                FileUtils.forceMkdir(dir);
+            }
+
+            File unzipFile = new File(filePath);
+            if (!unzipFile.isDirectory()) {
+                InputStream in = zipFile.getInputStream(entry);
+                OutputStream out = new FileOutputStream(unzipFile);
+
+                int tmp;
+                while ((tmp = in.read()) != -1) {
+                    out.write(tmp);
+                }
+                in.close();
+                out.close();
+            }
+        }
+    }
+
+    static String dirPath(String path) {
+        int index = path.lastIndexOf(File.separator);
+        return path.substring(0, index);
+    }
+}

+ 51 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/packer/ZipPack.java

@@ -0,0 +1,51 @@
+package cn.reghao.devops.common.build.tool.packer;
+
+import cn.reghao.devops.common.build.model.LocalBuildDir;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author reghao
+ * @date 2020-03-11 13:37:36
+ */
+@Slf4j
+public class ZipPack implements CodePacker {
+    // TODO 必须指定构建存在的目录
+    private final String binDirname;
+
+    public ZipPack(String binDirname) {
+        this.binDirname = binDirname;
+    }
+
+    private void createTargetDir(String targetPath) throws IOException {
+        File dir = new File(targetPath);
+        if (!dir.exists()) {
+            FileUtils.forceMkdir(new File(targetPath));
+        }
+    }
+
+    @Override
+    public String pack(String appId, String commitId, String appRootPath, String dockerfile) throws Exception {
+        if (binDirname == null) {
+            throw new Exception("存放编译产物的目录 binDirname 不能是 null");
+        }
+
+        String sourceDirPath = appRootPath + File.separator + binDirname;
+        String targetDirPath = LocalBuildDir.packDir + File.separator + appId;
+        createTargetDir(targetDirPath);
+
+        String filename = String.format("%s_%s.zip", appId, commitId);
+        String dest = targetDirPath + File.separator + filename;
+        String url = String.format("/api/app/bd/dl/%s", filename);
+        ZipFiles.zip(new File(sourceDirPath), dest);
+        return url;
+    }
+
+    @Override
+    public String push(String localPath) {
+        return localPath;
+    }
+}

+ 18 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/repo/ChangedFile.java

@@ -0,0 +1,18 @@
+package cn.reghao.devops.common.build.tool.repo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2020-05-14 10:49:49
+ */
+@Data
+public class ChangedFile implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String changeType;
+    private String oldFilePath;
+    private String newFilePath;
+}

+ 38 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/repo/CodeUpdater.java

@@ -0,0 +1,38 @@
+package cn.reghao.devops.common.build.tool.repo;
+
+/**
+ * 更新代码
+ *
+ * @author reghao
+ * @date 2019-10-12 22:24:28
+ */
+public interface CodeUpdater {
+    /**
+     * 更新代码
+     *
+     * @param remote 远程代码仓库路径
+     * @param branch 分支(仅用于 GIT)
+     * @param local 本地代码仓库目录
+     * @return 当前代码仓库的版本
+     * @date 2019-10-12 下午11:08
+     */
+    CommitInfo pull(String remote, String branch, String local) throws Exception;
+
+    /**
+     * // TODO 拉取指定版本的代码
+     *
+     * @param commitId 指定的版本
+     * @return
+     * @date 2020-09-08 下午6:18
+     */
+    CommitInfo update(String remote, String branch, String local, String commitId) throws Exception;
+
+    /**
+     * 本地代码仓库最近一次提交的信息
+     * TODO 并不能获取到远程仓库的 URL,需要在调用后手动设置
+     *
+     * @param local 本地代码仓库路径
+     * @date 2019-10-30 下午5:23
+     */
+    CommitInfo latestCommitInfo(String local, String branch) throws Exception;
+}

+ 33 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/repo/CommitInfo.java

@@ -0,0 +1,33 @@
+package cn.reghao.devops.common.build.tool.repo;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 代码提交信息
+ *
+ * @author reghao
+ * @date 2020-05-13 22:50:38
+ */
+@NoArgsConstructor
+@Getter
+@Setter
+public class CommitInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String remote;
+    private String branch;
+    private String local;
+    // 代码是否更新
+    private Boolean updated;
+    private String commitId;
+    private String commitAuthor;
+    private String commitMsg;
+    // 毫秒级时间戳
+    private Long msCommitTime;
+    private List<ChangedFile> changedFiles;
+}

+ 63 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/repo/CustomCredentialProvider.java

@@ -0,0 +1,63 @@
+package cn.reghao.devops.common.build.tool.repo;
+
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.URIish;
+
+/**
+ * @author reghao
+ * @date 2023-03-13 09:07:46
+ */
+public class CustomCredentialProvider extends CredentialsProvider {
+    private final String passphrase;
+
+    public CustomCredentialProvider(String passphrase) {
+        // 私钥的访问密码(没有密码则传入 null)
+        this.passphrase = passphrase;
+    }
+
+    @Override
+    public boolean isInteractive() {
+        // Set this according to your requirement
+        return false;
+    }
+
+    @Override
+    public boolean supports(CredentialItem... items) {
+        // Set this according to your requirement
+        return true;
+    }
+
+    @Override
+    public boolean get(URIish uri, CredentialItem... items)
+            throws UnsupportedCredentialItem {
+
+        for (CredentialItem item : items) {
+            if (item instanceof CredentialItem.InformationalMessage) {
+                continue;
+            }
+            if (item instanceof CredentialItem.YesNoType) {
+                // Set this according to your requirement
+                ((CredentialItem.YesNoType) item).setValue(true);
+            } else if (item instanceof CredentialItem.CharArrayType) {
+                if (passphrase != null) {
+                    ((CredentialItem.CharArrayType) item)
+                            .setValue(passphrase.toCharArray());
+                } else {
+                    return false;
+                }
+            } else if (item instanceof CredentialItem.StringType) {
+                if (passphrase != null) {
+                    ((CredentialItem.StringType) item)
+                            .setValue(passphrase);
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 53 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/repo/CustomSshSessionFactory.java

@@ -0,0 +1,53 @@
+package cn.reghao.devops.common.build.tool.repo;
+
+import org.eclipse.jgit.transport.sshd.SshdSessionFactory;
+import org.eclipse.jgit.util.FS;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * https://stackoverflow.com/questions/67767455/setting-ssh-keys-to-use-with-jgit-with-ssh-from-apache-sshd
+ *
+ * @author reghao
+ * @date 2023-03-13 09:06:54
+ */
+public class CustomSshSessionFactory extends SshdSessionFactory {
+    public static final Path SSH_DIR = Path.of(FS.DETECTED.userHome() + "/.ssh");
+
+    private final Path privateKeyFile;
+    private final byte[] privateKey;
+
+    public CustomSshSessionFactory(String privateKey) {
+        //this.privateKeyFile = Path.of("rsa_prikey", SSH_DIR.toString());
+        this.privateKeyFile = Path.of("/tmp/rsa_prikey");
+        this.privateKey = privateKey.getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public File getSshDirectory() {
+        try {
+            return Files.createDirectories(SSH_DIR).toFile();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    // Return paths to private key files
+    @Override
+    protected List<Path> getDefaultIdentities(File sshDir) {
+        try {
+            Files.write(privateKeyFile, privateKey);
+            return Collections.singletonList(privateKeyFile);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return Collections.emptyList();
+    }
+}

+ 295 - 0
common/src/main/java/cn/reghao/devops/common/build/tool/repo/GitImpl.java

@@ -0,0 +1,295 @@
+package cn.reghao.devops.common.build.tool.repo;
+
+import cn.reghao.devops.common.build.model.RepoAuth;
+import cn.reghao.devops.common.build.model.constant.RepoAuthType;
+import org.eclipse.jgit.api.*;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.*;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Git 实现
+ *
+ * @author reghao
+ * @date 2019-10-12 22:22:00
+ */
+public class GitImpl implements CodeUpdater {
+    private UsernamePasswordCredentialsProvider credentials;
+    private SshSessionFactory sshSessionFactory;
+
+    public GitImpl(RepoAuth repoAuth) {
+        switch (RepoAuthType.valueOf(repoAuth.getAuthType())) {
+            case http:
+                httpAuth(repoAuth.getUsername(), repoAuth.getPassword());
+                break;
+            case ssh:
+                sshAuth(repoAuth.getRsaPrikey());
+                break;
+            default:
+        }
+    }
+
+    private void httpAuth(String username, String password) {
+        this.credentials = new UsernamePasswordCredentialsProvider(username, password);
+    }
+
+    private void sshAuth(String privateKey) {
+        this.sshSessionFactory = new CustomSshSessionFactory(privateKey);
+    }
+
+    /**
+     * 不存在则 clone,存在则 pull
+     *
+     * @param
+     * @return
+     * @date 2020-05-13 下午9:22
+     */
+    @Override
+    public CommitInfo pull(String remote, String branch, String local) throws Exception {
+        String localRepoDir = local + dirname(remote);
+        File file = new File(localRepoDir);
+        if (!file.exists() && !file.mkdirs()) {
+            throw new Exception("创建 " + localRepoDir + " 目录失败");
+        }
+
+        File localRepo = new File(localRepoDir + "/.git");
+        CommitInfo commitInfo;
+        if (!localRepo.exists()) {
+            commitInfo = clone(remote, branch, localRepoDir);
+        } else {
+            try (Repository repo = new FileRepository(localRepo.getAbsolutePath())) {
+                PullCommand gitPull = new Git(repo)
+                        .pull()
+                        .setTimeout(600)
+                        .setFastForward(MergeCommand.FastForwardMode.FF);
+                if (sshSessionFactory != null) {
+                    gitPull.setTransportConfigCallback(transport -> {
+                        if (transport instanceof SshTransport) {
+                            setSshTransport((SshTransport) transport);
+                        }
+                    });
+                    gitPull.call();
+                } else if (credentials != null) {
+                    gitPull.setCredentialsProvider(credentials);
+                    gitPull.call();
+                } else {
+                    gitPull.call();
+                }
+
+                commitInfo = commitInfo(repo, branch);
+                commitInfo.setChangedFiles(changedFileList(repo, branch));
+            }
+        }
+        commitInfo.setRemote(remote);
+        commitInfo.setBranch(branch);
+        return commitInfo;
+    }
+
+    private CommitInfo clone(String remote, String branch, String local) throws Exception {
+        CloneCommand gitClone;
+        if (sshSessionFactory != null) {
+            gitClone = Git.cloneRepository()
+                    .setTimeout(600)
+                    .setURI(remote)
+                    .setBranch(branch)
+                    .setDirectory(new File(local))
+                    .setTransportConfigCallback(transport -> {
+                        if (transport instanceof SshTransport) {
+                            setSshTransport((SshTransport) transport);
+                        }
+                    });
+        } else if (credentials != null) {
+            gitClone = Git.cloneRepository()
+                    .setURI(remote)
+                    .setBranch(branch)
+                    .setDirectory(new File(local))
+                    .setCredentialsProvider(credentials);
+        } else {
+            gitClone = Git.cloneRepository()
+                    .setURI(remote)
+                    .setBranch(branch)
+                    .setDirectory(new File(local));
+        }
+
+        try (Git git = gitClone.setDepth(1).call()) {
+            Repository repo = git.getRepository();
+            CommitInfo commitInfo = commitInfo(repo, branch);
+            commitInfo.setChangedFiles(changedFileList(repo, branch));
+            return commitInfo;
+        }
+    }
+
+    private void setSshTransport(SshTransport sshTransport) {
+        // Set passphrase using a CustomCredentialsProvider
+        sshTransport.setCredentialsProvider(new CustomCredentialProvider(null));
+        sshTransport.setSshSessionFactory(sshSessionFactory);
+    }
+
+    private CommitInfo commitInfo(Repository repo, String branch) throws Exception {
+        Ref head = repo.findRef("refs/heads/" + branch);
+        if (head == null) {
+            String msg = String.format("%s 分支不存在", branch);
+            throw new Exception(msg);
+        }
+
+        ObjectId objectId = head.getObjectId();
+        try (RevWalk walk = new RevWalk(repo)) {
+            // 最近一次提交的信息
+            RevCommit lastCommit = walk.parseCommit(objectId);
+            long commitTime = lastCommit.getCommitTime()*1000L;
+            String commitAuthor = lastCommit.getAuthorIdent().getName();
+            String commitMsg = lastCommit.getFullMessage();
+
+            CommitInfo commitInfo = new CommitInfo();
+            commitInfo.setCommitId(objectId.name().substring(0, 8));
+            commitInfo.setMsCommitTime(commitTime);
+            commitInfo.setCommitAuthor(commitAuthor);
+            if (commitMsg.length() > 1000) {
+                commitInfo.setCommitMsg(commitMsg.substring(0, 1000));
+            } else {
+                commitInfo.setCommitMsg(commitMsg);
+            }
+            return commitInfo;
+        }
+    }
+
+    private List<ChangedFile> changedFileList(Repository repo, String branch) throws Exception {
+        Ref headMaster = repo.findRef("refs/heads/" + branch);
+        ObjectId objectId = headMaster.getObjectId();
+        try (RevWalk walk = new RevWalk(repo)) {
+            // 最近一次提交
+            RevCommit newCommit = walk.parseCommit(objectId);
+            walk.markStart(newCommit);
+            // 遍历两次找到 newCommit 的前一次提交,newCommit 不是第一次提交
+            walk.next();
+            RevCommit oldCommit = walk.next();
+            if (oldCommit == null) {
+                // TODO 仓库中只有一个 commit 时(第一次提交)的处理
+                return Collections.emptyList();
+            }
+
+            ObjectId newHead = newCommit.getTree().getId();
+            ObjectId oldHead = oldCommit.getTree().getId();
+
+            try (ObjectReader reader = repo.newObjectReader()) {
+                CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
+                newTreeIter.reset(reader, newHead);
+                CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
+                oldTreeIter.reset(reader, oldHead);
+                try (Git git = new Git(repo)) {
+                    List<DiffEntry> diffs= git.diff()
+                            .setNewTree(newTreeIter)
+                            .setOldTree(oldTreeIter)
+                            .call();
+
+                    List<ChangedFile> changedFiles = new ArrayList<>();
+                    for (DiffEntry entry : diffs) {
+                        ChangedFile changedFile = new ChangedFile();
+                        changedFile.setChangeType(entry.getChangeType().name());
+                        changedFile.setOldFilePath(entry.getOldPath());
+                        changedFile.setNewFilePath(entry.getNewPath());
+                        changedFiles.add(changedFile);
+                    }
+
+                    return changedFiles;
+                }
+            }
+        }
+    }
+
+    @Override
+    public CommitInfo update(String remote, String branch, String local, String commitId) throws Exception {
+        // TODO 待实现
+        return null;
+    }
+
+    @Override
+    public CommitInfo latestCommitInfo(String local, String branch) throws Exception {
+        File localRepo = new File(local + "/.git");
+        if (!localRepo.exists()) {
+            return null;
+        }
+
+        try (Repository repo = new FileRepository(localRepo.getAbsolutePath())) {
+            return commitInfo(repo, branch);
+        }
+    }
+
+    private String dirname(String gitUrl) {
+        int lastIndex = gitUrl.lastIndexOf("/");
+        return gitUrl.substring(lastIndex).split(".git")[0];
+    }
+
+    public List<String> remoteBranches(String remote) {
+        List<String> branches = new ArrayList<>();
+        try {
+            Collection<Ref> refList = Git.lsRemoteRepository().setRemote(remote)
+                    .setCredentialsProvider(credentials).call();
+            for (Ref ref : refList) {
+                String refName = ref.getName();
+                if (refName.startsWith("refs/heads/")) {
+                    // 需要进行筛选
+                    String branchName = refName.replace("refs/heads/", "");
+                    branches.add(branchName);
+                }
+            }
+            return branches;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return branches;
+    }
+
+    /**
+     * 提交代码并 push 到远程仓库
+     *
+     * @param
+     * @return
+     * @date 2022-08-03 下午2:56
+     */
+    public void commitAndPush(String local, String commitMsg) throws Exception {
+        Repository repository = new FileRepositoryBuilder().setGitDir(new File(local + "/.git")).build();
+        Git git = new Git(repository);
+        RemoteListCommand remoteListCommand = git.remoteList();
+        List<RemoteConfig> list = remoteListCommand.call();
+        if (list.isEmpty()) {
+            throw new Exception("RemoteConfig 不存在");
+        }
+
+        RemoteConfig remoteConfig = list.get(0);
+        List<URIish> urIishList = remoteConfig.getURIs();
+        if (urIishList.isEmpty()) {
+            throw new Exception("远程仓库 URL 不存在");
+        }
+
+        URIish urIish = urIishList.get(0);
+        String pushUrl = urIish.toString();
+
+        AddCommand addCommand = git.add();
+        addCommand.addFilepattern(".").call();
+
+        CommitCommand commitCommand = git.commit();
+        commitCommand.setCommitter("reghao", "reghaodev@gmail.com");
+        commitCommand.setAuthor("reghao", "reghaodev@gmail.com");
+        commitCommand.setAll(true);
+
+        // git commit -m "commit message"
+        RevCommit revCommit = commitCommand.setMessage(commitMsg).call();
+        PushCommand pushCommand = git.push();
+        pushCommand.setRemote(pushUrl).setCredentialsProvider(credentials).call();
+    }
+}

+ 30 - 0
common/src/main/java/cn/reghao/devops/common/docker/Docker.java

@@ -0,0 +1,30 @@
+package cn.reghao.devops.common.docker;
+
+import cn.reghao.devops.common.docker.po.Config;
+import com.github.dockerjava.api.command.InspectContainerResponse;
+import com.github.dockerjava.api.model.Container;
+import com.github.dockerjava.api.model.Image;
+
+import java.util.List;
+
+/**
+ * Docker 客户端
+ *
+ * @author reghao
+ * @date 2021-10-27 04:17:38
+ */
+public interface Docker {
+    void build(String repoTag, String compileHome, String dockerfileContent) throws Exception;
+    void build(String repoTag, String compileHome) throws Exception;
+    void push(String image) throws Exception;
+    void pull(String repoTag) throws Exception;
+    void stopAndDelete(String containerName) throws Exception;
+    InspectContainerResponse createAndRun(String containerName, Config containerConfig) throws Exception;
+    InspectContainerResponse start(String containerName);
+    InspectContainerResponse stop(String containerName);
+    InspectContainerResponse restart(String containerName);
+    InspectContainerResponse inspectContainer(String containerName);
+    List<Image> images();
+    List<Container> ps(boolean isAll);
+    InspectContainerResponse ps(String containerName);
+}

+ 269 - 0
common/src/main/java/cn/reghao/devops/common/docker/DockerImpl.java

@@ -0,0 +1,269 @@
+package cn.reghao.devops.common.docker;
+
+import cn.reghao.devops.common.docker.po.Config;
+import cn.reghao.jutil.jdk.exception.ExceptionUtil;
+import cn.reghao.jutil.jdk.converter.DateTimeConverter;
+import cn.reghao.jutil.jdk.text.TextFile;
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.command.*;
+import com.github.dockerjava.api.model.*;
+import com.github.dockerjava.core.DefaultDockerClientConfig;
+import com.github.dockerjava.core.DockerClientConfig;
+import com.github.dockerjava.core.DockerClientImpl;
+import com.github.dockerjava.core.command.PushImageResultCallback;
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+import com.github.dockerjava.transport.DockerHttpClient;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Docker 客户端
+ *
+ * @author reghao
+ * @date 2021-10-27 03:41:38
+ */
+public class DockerImpl implements Docker {
+    private final DockerClient dockerClient;
+    private final TextFile textFile = new TextFile();
+
+    public DockerImpl() {
+        DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
+                .withDockerHost("unix:///var/run/docker.sock")
+                .withDockerTlsVerify(false)
+                //.withDockerCertPath("/home/user/.docker")
+                //.withRegistryUsername(registryUser)
+                //.withRegistryPassword(registryPass)
+                //.withRegistryEmail(registryMail)
+                //.withRegistryUrl(registryUrl)
+                .build();
+
+        DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
+                .dockerHost(config.getDockerHost())
+                .sslConfig(config.getSSLConfig())
+                .maxConnections(100)
+                .connectionTimeout(Duration.ofSeconds(30))
+                .responseTimeout(Duration.ofSeconds(45))
+                .build();
+
+        this.dockerClient = DockerClientImpl.getInstance(config, httpClient);
+    }
+
+    @Override
+    public void build(String repoTag, String compileHome, String dockerfileContent) throws Exception {
+        File dockerfile = new File(compileHome + "/Dockerfile.tmp");
+        try {
+            textFile.write(dockerfile, dockerfileContent);
+            String imageId = dockerClient.buildImageCmd()
+                    .withDockerfile(dockerfile)
+                    // repoTag 格式为 docker.reghao.cn/devops:12345678
+                    .withTags(Set.of(repoTag))
+                    .start()
+                    .awaitImageId();
+        } catch (Exception e) {
+            throw new Exception(ExceptionUtil.errorMsg(e));
+        }
+    }
+
+    @Override
+    public void build(String repoTag, String compileHome) throws Exception {
+        String dockerfilePath = compileHome + "/Dockerfile";
+        dockerClient.buildImageCmd()
+                .withDockerfile(new File(dockerfilePath))
+                .withTags(Set.of(repoTag))
+                .start()
+                .awaitImageId();
+    }
+
+    @Override
+    public void push(String image) throws Exception {
+        try {
+            dockerClient.pushImageCmd(image).exec(new PushImageResultCallback()).awaitCompletion();
+        } catch (InterruptedException e) {
+            throw new Exception(ExceptionUtil.errorMsg(e));
+        }
+        /*dockerClient.pushImageCmd(image).exec(new ResultCallback<PushResponseItem>() {
+            @Override
+            public void onStart(Closeable closeable) {
+
+            }
+
+            @Override
+            public void onNext(PushResponseItem object) {
+                System.out.println(object.getStatus());
+            }
+
+            @Override
+            public void onError(Throwable throwable) {
+
+            }
+
+            @Override
+            public void onComplete() {
+
+            }
+
+            @Override
+            public void close() throws IOException {
+
+            }
+        }).onComplete();*/
+    }
+
+    @Override
+    public void pull(String image) throws Exception {
+        try {
+            dockerClient.pullImageCmd(image).exec(new PullImageResultCallback()).awaitCompletion();
+        } catch (InterruptedException e) {
+            throw new Exception(ExceptionUtil.errorMsg(e));
+        }
+        /*dockerClient.pullImageCmd(image).exec(new ResultCallback<PullResponseItem>() {
+            public void onStart(Closeable closeable) {
+                System.out.println("开始下载!");
+            }
+
+            public void onNext(PullResponseItem object) {
+                // 实时显示出下载信息
+                System.out.println(object.getStatus());
+            }
+
+            public void onError(Throwable throwable) {
+                throwable.printStackTrace();
+            }
+
+            public void onComplete() {
+                System.out.println("下载完毕!");
+            }
+
+            public void close() throws IOException {
+                System.out.println("下载完毕...");
+            }
+        }).onComplete();*/
+    }
+
+    private String getContainerIdByName(String containerName) {
+        List<Container> list = dockerClient.listContainersCmd()
+                .withShowAll(true)
+                .withNameFilter(List.of(containerName))
+                .exec();
+
+        for (Container container : list) {
+            Set<String> set = new HashSet<>(Arrays.asList(container.getNames()));
+            if (set.contains("/" + containerName)) {
+                return container.getId();
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void stopAndDelete(String containerName) {
+        String containerId = getContainerIdByName(containerName);
+        if (containerId != null) {
+            InspectContainerResponse containerInfo = dockerClient.inspectContainerCmd(containerId).exec();
+            InspectContainerResponse.ContainerState state = containerInfo.getState();
+            boolean isRunning = state.getRunning() != null ? state.getRunning() : false;
+            if (isRunning) {
+                dockerClient.stopContainerCmd(containerId).exec();
+            }
+            dockerClient.removeContainerCmd(containerId).exec();
+        }
+    }
+
+    @Override
+    public InspectContainerResponse createAndRun(String containerName, Config containerConfig) throws Exception {
+        stopAndDelete(containerName);
+
+        HostConfig hostConfig = HostConfig.newHostConfig()
+                .withNetworkMode("host")
+                .withRestartPolicy(RestartPolicy.unlessStoppedRestart());
+        String image = containerConfig.getImage();
+        CreateContainerCmd createContainerCmd = dockerClient.createContainerCmd(image)
+                .withName(containerName)
+                .withHostConfig(hostConfig);
+
+        List<String> env = containerConfig.getEnv();
+        if (env != null) {
+            createContainerCmd.withEnv(env);
+        }
+
+        CreateContainerResponse response = createContainerCmd.exec();
+        String containerId = response.getId();
+        dockerClient.startContainerCmd(containerId).exec();
+        return dockerClient.inspectContainerCmd(containerId).exec();
+    }
+
+    @Override
+    public InspectContainerResponse start(String containerName) {
+        String containerId = getContainerIdByName(containerName);
+        dockerClient.startContainerCmd(containerId).exec();
+        return dockerClient.inspectContainerCmd(containerId).exec();
+    }
+
+    @Override
+    public InspectContainerResponse stop(String containerName) {
+        String containerId = getContainerIdByName(containerName);
+        dockerClient.stopContainerCmd(containerId).exec();
+        return dockerClient.inspectContainerCmd(containerId).exec();
+    }
+
+    @Override
+    public InspectContainerResponse restart(String containerName) {
+        String containerId = getContainerIdByName(containerName);
+        dockerClient.restartContainerCmd(containerId).exec();
+        return dockerClient.inspectContainerCmd(containerId).exec();
+    }
+
+    @Override
+    public InspectContainerResponse inspectContainer(String containerName) {
+        String containerId = getContainerIdByName(containerName);
+        return dockerClient.inspectContainerCmd(containerId).exec();
+    }
+
+    @Override
+    public List<Image> images() {
+        List<Image> images = dockerClient.listImagesCmd().exec();
+
+        images.forEach(image -> {
+            long created = image.getCreated();
+            String[] repoTags = image.getRepoTags();
+            if (repoTags.length == 0) {
+                return;
+            }
+
+            String[] repoTag = repoTags[0].split(":");
+            String repo = repoTag[0];
+            String tag = repoTag[1];
+            System.out.printf("%s:%s -> %s\n", repo, tag, DateTimeConverter.format(created*1000));
+        });
+
+        return images;
+    }
+
+    @Override
+    public List<Container> ps(boolean isAll) {
+        return dockerClient.listContainersCmd()
+                .withShowAll(isAll)
+                .exec();
+    }
+
+    @Override
+    public InspectContainerResponse ps(String appId) {
+        List<Container> list = dockerClient.listContainersCmd()
+                .withNameFilter(List.of(appId))
+                .exec();
+
+        if (list.isEmpty()) {
+            return null;
+        }
+
+        Container container = list.get(0);
+        String containerId = container.getId();
+        return dockerClient.inspectContainerCmd(containerId).exec();
+    }
+}

+ 46 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Config.java

@@ -0,0 +1,46 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * docker 容器创建时的配置
+ *
+ * @author reghao
+ * @date 2020-01-14 23:11:21
+ */
+@Data
+public class Config {
+    @SerializedName("AttachStderr") private boolean attachStderr;
+    @SerializedName("AttachStdin") private boolean attachStdin;
+    @SerializedName("AttachStdout") private boolean attachStdout;
+    @SerializedName("Cmd") private List<String> cmd;
+    @SerializedName("Domainname") private String domainname;
+    @SerializedName("Env") private List<String> env;
+    @SerializedName("Healthcheck") private Healthcheck healthcheck;
+    @SerializedName("Hostname") private String hostname;
+    @SerializedName("Image") private String image;
+    @SerializedName("Labels") private Labels labels;
+    @SerializedName("MacAddress") private String macAddress;
+    @SerializedName("NetworkDisabled") private boolean networkDisabled;
+    @SerializedName("OpenStdin") private boolean openStdin;
+    @SerializedName("StdinOnce") private boolean stdinOnce;
+    @SerializedName("Tty") private boolean tty;
+    @SerializedName("User") private String user;
+    @SerializedName("Volumes") private Volumes volumes;
+    @SerializedName("WorkingDir") private String workingDir;
+    @SerializedName("StopSignal") private String stopSignal;
+    @SerializedName("StopTimeout") private int stopTimeout;
+
+    @SerializedName("Entrypoint") private String entrypoint;
+    @SerializedName("ExposedPorts") private ExposedPorts exposedPorts;
+    @SerializedName("HostConfig") private HostConfig hostConfig;
+    @SerializedName("NetworkingConfig") private NetworkingConfig networkingConfig;
+
+    public Config(String image) {
+        this.image = image;
+        this.hostConfig = new HostConfig();
+    }
+}

+ 18 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Data.java

@@ -0,0 +1,18 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2020-05-19 20:50:24
+ */
+@Getter
+@Setter
+public class Data {
+    @SerializedName("LowerDir") private String lowerDir;
+    @SerializedName("MergedDir") private String mergedDir;
+    @SerializedName("UpperDir") private String upperDir;
+    @SerializedName("WorkDir") private String workDir;
+}

+ 11 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/ExposedPorts.java

@@ -0,0 +1,11 @@
+package cn.reghao.devops.common.docker.po;
+
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-10 01:30:17
+ */
+@Data
+public class ExposedPorts {
+}

+ 16 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/GraphDriver.java

@@ -0,0 +1,16 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2020-05-19 20:51:13
+ */
+@Getter
+@Setter
+public class GraphDriver {
+    @SerializedName("Data") private Data data;
+    @SerializedName("Name") private String name;
+}

+ 17 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Health.java

@@ -0,0 +1,17 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 10:16:50
+ */
+@Data
+public class Health {
+    @SerializedName("Status") private String status;
+    @SerializedName("FailingStreak") private int failingStreak;
+    @SerializedName("Log") private List<Log> log;
+}

+ 15 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Healthcheck.java

@@ -0,0 +1,15 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 10:02:59
+ */
+@Data
+public class Healthcheck {
+    @SerializedName("Test") private List<String> test;
+}

+ 23 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/HostConfig.java

@@ -0,0 +1,23 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * TODO 部分
+ *
+ * @author reghao
+ * @date 2020-01-14 23:16:52
+ */
+@AllArgsConstructor
+@Data
+public class HostConfig {
+    @SerializedName("NetworkMode") private String networkMode;
+    @SerializedName("RestartPolicy") private RestartPolicy restartPolicy;
+
+    public HostConfig() {
+        this.networkMode = "host";
+        this.restartPolicy = new RestartPolicy();
+    }
+}

+ 14 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Labels.java

@@ -0,0 +1,14 @@
+package cn.reghao.devops.common.docker.po;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2021-02-10 01:28:38
+ */
+@Data
+public class Labels {
+    private Map<String, String> map;
+}

+ 16 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Log.java

@@ -0,0 +1,16 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 10:19:45
+ */
+@Data
+public class Log {
+    @SerializedName("Start") private String start;
+    @SerializedName("End") private String end;
+    @SerializedName("ExitCode") private int exitCode;
+    @SerializedName("Output") private String output;
+}

+ 11 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/NetworkingConfig.java

@@ -0,0 +1,11 @@
+package cn.reghao.devops.common.docker.po;
+
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-10 01:33:05
+ */
+@Data
+public class NetworkingConfig {
+}

+ 21 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/RestartPolicy.java

@@ -0,0 +1,21 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2020-05-19 15:00:50
+ */
+@AllArgsConstructor
+@Data
+public class RestartPolicy {
+    @SerializedName("Name") private String name;
+    @SerializedName("MaximumRetryCount") private int maximumRetryCount;
+
+    public RestartPolicy() {
+        this.name = "on-failure";
+        this.maximumRetryCount = 3;
+    }
+}

+ 24 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/State.java

@@ -0,0 +1,24 @@
+package cn.reghao.devops.common.docker.po;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2020-05-19 21:04:06
+ */
+@Data
+public class State {
+    @SerializedName("Error") private String error;
+    @SerializedName("ExitCode") private int exitCode;
+    @SerializedName("FinishedAt") private String finishedAt;
+    @SerializedName("Health") private Health health;
+    @SerializedName("OOMKilled") private boolean oomKilled;
+    @SerializedName("Dead") private boolean dead;
+    @SerializedName("Paused") private boolean paused;
+    @SerializedName("Pid") private int pid;
+    @SerializedName("Running") private boolean running;
+    @SerializedName("Restarting") private boolean restarting;
+    @SerializedName("StartedAt") private String startedAt;
+    @SerializedName("Status") private String status;
+}

+ 14 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/Volumes.java

@@ -0,0 +1,14 @@
+package cn.reghao.devops.common.docker.po;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2021-02-10 01:29:10
+ */
+@Data
+public class Volumes {
+    private Map<String, Object> map;
+}

+ 21 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/Bridge.java

@@ -0,0 +1,21 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 09:37:50
+ */
+@Data
+public class Bridge {
+    @SerializedName("NetworkID") private String networkId;
+    @SerializedName("EndpointID") private String endpointId;
+    @SerializedName("Gateway") private String gateway;
+    @SerializedName("IPAddress") private String ipAddress;
+    @SerializedName("IPPrefixLen") private String ipPrefixLen;
+    @SerializedName("IPv6Gateway") private String ipv6Gateway;
+    @SerializedName("GlobalIPv6Address") private String globalIpv6Address;
+    @SerializedName("GlobalIPv6PrefixLen") private String globalIpv6PrefixLen;
+    @SerializedName("MacAddress") private String macAddress;
+}

+ 33 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/Container.java

@@ -0,0 +1,33 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import cn.reghao.devops.common.docker.po.HostConfig;
+import cn.reghao.devops.common.docker.po.Labels;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * docker ps 返回值
+ *
+ * @author reghao
+ * @date 2019-12-10 18:30:02
+ */
+@Data
+public class Container {
+    @SerializedName("Id") private String id;
+    @SerializedName("Names") private List<String> names;
+    @SerializedName("Image") private String image;
+    @SerializedName("ImageID") private String imageId;
+    @SerializedName("Command") private String command;
+    @SerializedName("Created") private long created;
+    @SerializedName("State") private String state;
+    @SerializedName("Status") private String status;
+    @SerializedName("Ports") private List<Port> ports;
+    @SerializedName("Labels") private Labels labels;
+    @SerializedName("SizeRw") private int sizeRw;
+    @SerializedName("SizeRootFs") private int sizeRootFs;
+    @SerializedName("HostConfig") private HostConfig hostConfig;
+    @SerializedName("NetworkSettings") private NetworkSettings networkSettings;
+    @SerializedName("Mounts") private List<Mount> mounts;
+}

+ 47 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/ContainerInfo.java

@@ -0,0 +1,47 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import cn.reghao.devops.common.docker.po.GraphDriver;
+import cn.reghao.devops.common.docker.po.HostConfig;
+import cn.reghao.devops.common.docker.po.State;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * docker inspect 返回值
+ *
+ * @author reghao
+ * @date 2019-12-10 18:30:02
+ */
+@Data
+public class ContainerInfo {
+    @SerializedName("AppArmorProfile") private String appArmorProfile;
+    @SerializedName("Args") private List<String> args;
+    @SerializedName("Config") private Config config;
+    @SerializedName("Created") private String created;
+    @SerializedName("Driver") private String driver;
+    @SerializedName("ExecIDs") private List<String> execIds;
+    @SerializedName("GraphDriver") private GraphDriver graphDriver;
+    @SerializedName("HostConfig") private HostConfig hostConfig;
+    @SerializedName("HostnamePath") private String hostnamePath;
+    @SerializedName("HostsPath") private String hostsPath;
+    @SerializedName("LogPath") private String logPath;
+    @SerializedName("Id") private String id;
+    @SerializedName("Image") private String image;
+    @SerializedName("MountLabel") private String mountLabel;
+    @SerializedName("Name") private String name;
+    @SerializedName("NetworkSettings") private NetworkSettings networkSettings;
+    @SerializedName("Path") private String path;
+    @SerializedName("ProcessLabel") private String processLabel;
+    @SerializedName("ResolvConfPath") private String resolvConfPath;
+    @SerializedName("RestartCount") private int restartCount;
+    @SerializedName("State") private State state;
+    @SerializedName("Mounts") private List<Mount> mounts;
+
+    @Data
+    static public class Config {
+        @SerializedName("Image") private String image;
+        @SerializedName("StopSignal") private String stopSignal;
+    }
+}

+ 19 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/Mount.java

@@ -0,0 +1,19 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 09:43:58
+ */
+@Data
+public class Mount {
+    @SerializedName("Name") private String name;
+    @SerializedName("Source") private String source;
+    @SerializedName("Destination") private String destination;
+    @SerializedName("Driver") private String driver;
+    @SerializedName("Mode") private String mode;
+    @SerializedName("RW") private boolean rw;
+    @SerializedName("Propagation") private String propagation;
+}

+ 13 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/NetworkSettings.java

@@ -0,0 +1,13 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 09:41:50
+ */
+@Data
+public class NetworkSettings {
+    @SerializedName("Networks") private Networks networks;
+}

+ 13 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/Networks.java

@@ -0,0 +1,13 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 09:41:10
+ */
+@Data
+public class Networks {
+    @SerializedName("bridge") private Bridge bridge;
+}

+ 15 - 0
common/src/main/java/cn/reghao/devops/common/docker/po/result/Port.java

@@ -0,0 +1,15 @@
+package cn.reghao.devops.common.docker.po.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author reghao
+ * @date 2021-02-25 09:33:19
+ */
+@Data
+public class Port {
+    @SerializedName("PrivatePort") private int privatePort;
+    @SerializedName("PublicPort") private int publicPort;
+    @SerializedName("Type") private String type;
+}

+ 33 - 0
common/src/main/java/cn/reghao/devops/common/machine/Cpu.java

@@ -0,0 +1,33 @@
+package cn.reghao.devops.common.machine;
+
+import cn.reghao.jutil.jdk.machine.data.MachineData;
+import cn.reghao.jutil.jdk.machine.data.detail.CpuDetail;
+import cn.reghao.jutil.jdk.machine.data.stat.CpuStat;
+import oshi.hardware.CentralProcessor;
+
+/**
+ * @author reghao
+ * @date 2019-10-25 13:12:36
+ */
+public class Cpu implements MachineData<CpuDetail, CpuStat> {
+    private CentralProcessor processor;
+
+    public Cpu(CentralProcessor processor) {
+        this.processor = processor;
+    }
+
+    @Override
+    public CpuDetail detail() {
+        CentralProcessor.ProcessorIdentifier processorIdentifier = processor.getProcessorIdentifier();
+        String vendor = processorIdentifier.getVendor();
+        String name = processorIdentifier.getName();
+        int physicalCore = processor.getPhysicalProcessorCount();
+        int logicalCore = processor.getLogicalProcessorCount();
+        return new CpuDetail(vendor, name, physicalCore, logicalCore);
+    }
+
+    @Override
+    public CpuStat stat() {
+        return null;
+    }
+}

+ 86 - 0
common/src/main/java/cn/reghao/devops/common/machine/Disk.java

@@ -0,0 +1,86 @@
+package cn.reghao.devops.common.machine;
+
+import cn.reghao.jutil.jdk.machine.data.MachineData;
+import cn.reghao.jutil.jdk.machine.data.detail.DiskDetail;
+import cn.reghao.jutil.jdk.machine.data.stat.DiskStat;
+import oshi.SystemInfo;
+import oshi.software.os.OperatingSystem;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2019-10-25 13:21:25
+ */
+public class Disk implements MachineData<List<DiskDetail>, List<DiskStat>> {
+    private final OperatingSystem os;
+
+    public Disk(SystemInfo si) {
+        this.os = si.getOperatingSystem();
+        //this.os = new SystemInfo().getOperatingSystem();
+    }
+
+    @Override
+    public List<DiskDetail> detail() {
+        return os.getFileSystem().getFileStores().stream()
+                .map(osFileStore -> {
+                    String vol = osFileStore.getVolume();
+                    String mount = osFileStore.getMount();
+                    String fsType = osFileStore.getType();
+
+                    long total = osFileStore.getTotalSpace();
+                    // non-root 用户可用的磁盘空间
+                    long userAvail = osFileStore.getUsableSpace();
+                    // root 用户可用的磁盘空间
+                    long rootAvail = osFileStore.getFreeSpace();
+
+                    long inodeTotal = osFileStore.getTotalInodes();
+                    long inodeFree = osFileStore.getFreeInodes();
+
+                    return new DiskDetail(vol, mount, fsType, total, userAvail, total-userAvail, inodeTotal, inodeFree);
+                })
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<DiskStat> stat() {
+        return null;
+    }
+
+    /**
+     * 根据一个路径确定它所在的分区
+     *
+     * @param
+     * @return
+     * @date 2021-02-06 下午11:25
+     */
+    public DiskDetail diskDetail(String path) {
+        List<DiskDetail> diskInfos = detail();
+        // 根据挂载路径的长度降序
+        diskInfos.sort((o1, o2) -> o2.getMountedOn().length() - o1.getMountedOn().length());
+
+        List<DiskDetail> candidates = new ArrayList<>();
+        for (DiskDetail diskInfo : diskInfos) {
+            String mountedOn = diskInfo.getMountedOn();
+            String[] array = path.split(mountedOn);
+            if (array.length == 0) {
+                return diskInfo;
+            } else if (array.length == 2) {
+                if (array[1].startsWith("/") || "/".equals(mountedOn)) {
+                    candidates.add(diskInfo);
+                }
+            } else if (array.length > 2) {
+                candidates.add(diskInfo);
+            }
+        }
+
+        if (!candidates.isEmpty()) {
+            // TODO 如果 candidates 大于 0 则判断准确的那个
+            return candidates.get(0);
+        } else {
+            return null;
+        }
+    }
+}

+ 69 - 0
common/src/main/java/cn/reghao/devops/common/machine/Machine.java

@@ -0,0 +1,69 @@
+package cn.reghao.devops.common.machine;
+
+import cn.reghao.devops.common.msg.event.EvtAgentHeartbeat;
+import cn.reghao.devops.common.msg.event.EvtAgentStart;
+import cn.reghao.devops.common.version.AppVersion;
+import cn.reghao.jutil.jdk.machine.data.detail.*;
+import cn.reghao.jutil.jdk.machine.id.MachineId;
+import cn.reghao.jutil.jdk.machine.id.MachineIdLinux;
+import oshi.SystemInfo;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2020-10-22 15:47:58
+ */
+public class Machine {
+    public final static String ID;
+    public final static String IPV4;
+    static {
+        MachineId machineId = new MachineIdLinux();
+        ID = machineId.id();
+        // TODO ip 发生变化时会引发数据不一致
+        IPV4 = machineId.ipv4();
+    }
+
+    private final Os os;
+    private final Network network;
+    private final Cpu cpu;
+    private final Memory memory;
+    private final Disk disk;
+
+    public Machine() {
+        SystemInfo si = new SystemInfo();
+        this.os = new Os(si.getOperatingSystem());
+        this.network = new Network();
+        this.cpu = new Cpu(si.getHardware().getProcessor());
+        this.memory = new Memory(si.getHardware().getMemory());
+        this.disk = new Disk(si);
+    }
+
+    public EvtAgentStart detail() {
+        EvtAgentStart machineDetail = new EvtAgentStart();
+        machineDetail.setMachineId(Machine.ID);
+        machineDetail.setAppVersion(AppVersion.getVersion());
+
+        machineDetail.setOsDetail(os.detail());
+        List<NetworkDetail> list = network.detail().stream()
+                .peek(networkDetail -> {
+                    networkDetail.setPubicIpv4("0.0.0.0");
+                })
+                .collect(Collectors.toList());
+        machineDetail.setNetworkDetails(list);
+
+        machineDetail.setCpuDetail(cpu.detail());
+        machineDetail.setMemoryDetail(memory.detail());
+        machineDetail.setDiskDetails(disk.detail());
+        return machineDetail;
+    }
+
+    public EvtAgentHeartbeat stat() {
+        EvtAgentHeartbeat machineStat = new EvtAgentHeartbeat();
+        machineStat.setMachineId(Machine.ID);
+        machineStat.setMemoryDetail(memory.detail());
+        machineStat.setDiskDetails(disk.detail());
+        return machineStat;
+    }
+}

+ 53 - 0
common/src/main/java/cn/reghao/devops/common/machine/Memory.java

@@ -0,0 +1,53 @@
+package cn.reghao.devops.common.machine;
+
+import cn.reghao.devops.common.util.jvm.po.MemoryStat;
+import cn.reghao.jutil.jdk.machine.data.MachineData;
+import cn.reghao.jutil.jdk.machine.data.detail.MemoryDetail;
+import oshi.hardware.GlobalMemory;
+import oshi.hardware.VirtualMemory;
+
+/**
+ * @author reghao
+ * @date 2019-10-25 13:21:25
+ */
+public class Memory implements MachineData<MemoryDetail, MemoryStat> {
+    private final GlobalMemory globalMemory;
+    private final VirtualMemory virtualMemory;
+    private MemoryDetail memoryDetail;
+
+    public Memory(GlobalMemory globalMemory) {
+        this.globalMemory = globalMemory;
+        this.virtualMemory = globalMemory.getVirtualMemory();
+    }
+
+    @Override
+    public MemoryDetail detail() {
+        long total = globalMemory.getTotal();
+        long avail = globalMemory.getAvailable();
+        long swapTotal = virtualMemory.getSwapTotal();
+        long swapUsed = virtualMemory.getSwapUsed();
+
+        if (memoryDetail == null) {
+            memoryDetail = new MemoryDetail(total, avail, total-avail, swapTotal, swapTotal-swapUsed);
+        }
+
+        return memoryDetail;
+    }
+
+    @Override
+    public MemoryStat stat() {
+        if (memoryDetail == null) {
+            memoryDetail = detail();
+        }
+
+        long total = globalMemory.getTotal();
+        long avail = globalMemory.getAvailable();
+        memoryDetail.setAvail(avail);
+        memoryDetail.setUsed(total-avail);
+
+        long swapTotal = virtualMemory.getSwapTotal();
+        long swapUsed = virtualMemory.getSwapUsed();
+        memoryDetail.setAvail(swapTotal-swapUsed);
+        return null;
+    }
+}

+ 73 - 0
common/src/main/java/cn/reghao/devops/common/machine/Network.java

@@ -0,0 +1,73 @@
+package cn.reghao.devops.common.machine;
+
+import cn.reghao.jutil.jdk.machine.data.MachineData;
+import cn.reghao.jutil.jdk.machine.data.detail.NetworkDetail;
+import cn.reghao.jutil.jdk.machine.data.stat.NetworkStat;
+
+import java.net.*;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2021-10-16 18:45:39
+ */
+public class Network implements MachineData<List<NetworkDetail>, NetworkStat> {
+    @Override
+    public List<NetworkDetail> detail() {
+        List<NetworkDetail> list = new ArrayList<>();
+        try {
+            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+            // 遍历主机的网络接口
+            while (interfaces.hasMoreElements()) {
+                NetworkInterface iface = interfaces.nextElement();
+                String ifaceName = iface.getName();
+                // 过滤掉 localhost 和虚拟网卡
+                if (ifaceName.startsWith("lo") || ifaceName.startsWith("docker")
+                        || ifaceName.startsWith("v") || ifaceName.startsWith("br")) {
+                    continue;
+                }
+                String ifaceMac = macAddr(iface.getHardwareAddress());
+                NetworkDetail networkDetail = new NetworkDetail(ifaceName, ifaceMac);
+
+                Enumeration<InetAddress> inetAddrs = iface.getInetAddresses();
+                while (inetAddrs.hasMoreElements()) {
+                    InetAddress address = inetAddrs.nextElement();
+                    if (!address.isLoopbackAddress()) {
+                        if (address instanceof Inet4Address) {
+                            networkDetail.setIpv4(address.getHostAddress());
+                        } else if (address instanceof Inet6Address) {
+                            networkDetail.setIpv6(address.getHostAddress());
+                        }
+                    }
+                }
+
+                list.add(networkDetail);
+            }
+        } catch (SocketException e) {
+            e.printStackTrace();
+        }
+        return list;
+    }
+
+    /**
+     * 返回 52-54-00-bf-ab-2d 格式的 MAC 地址
+     *
+     * @param
+     * @return
+     * @date 2020-10-14 下午1:53
+     */
+    private static String macAddr(byte[] addr) {
+        String[] hexadecimal = new String[addr.length];
+        for (int i = 0; i < addr.length; i++) {
+            hexadecimal[i] = String.format("%02x", addr[i]);
+        }
+        return String.join("-", hexadecimal);
+    }
+
+    @Override
+    public NetworkStat stat() {
+        return null;
+    }
+}

+ 30 - 0
common/src/main/java/cn/reghao/devops/common/machine/Os.java

@@ -0,0 +1,30 @@
+package cn.reghao.devops.common.machine;
+
+import cn.reghao.jutil.jdk.machine.data.MachineData;
+import cn.reghao.jutil.jdk.machine.data.detail.OsDetail;
+import cn.reghao.jutil.jdk.machine.data.stat.OsStat;
+import oshi.software.os.OperatingSystem;
+
+/**
+ * @author reghao
+ * @date 2020-10-20 23:17:30
+ */
+public class Os implements MachineData<OsDetail, OsStat> {
+    private OperatingSystem os;
+
+    public Os(OperatingSystem os) {
+        this.os = os;
+    }
+
+    @Override
+    public OsDetail detail() {
+        OsDetail osDetail = new OsDetail();
+        osDetail.setBootTime(os.getSystemBootTime());
+        return osDetail;
+    }
+
+    @Override
+    public OsStat stat() {
+        return null;
+    }
+}

+ 17 - 0
common/src/main/java/cn/reghao/devops/common/msg/MessageSender.java

@@ -0,0 +1,17 @@
+package cn.reghao.devops.common.msg;
+
+import java.io.IOException;
+
+/**
+ * @author reghao
+ * @date 2023-02-23 09:25:57
+ */
+public interface MessageSender {
+    default void connect() {
+    }
+    void setConnected(boolean status);
+    boolean isConnected();
+    void send(String dest, Object message) throws IOException;
+    default void close() {
+    }
+}

+ 14 - 0
common/src/main/java/cn/reghao/devops/common/msg/MsgQueue.java

@@ -0,0 +1,14 @@
+package cn.reghao.devops.common.msg;
+
+import cn.reghao.devops.common.msg.constant.AppId;
+
+/**
+ * @author reghao
+ * @date 2021-09-14 17:10:31
+ */
+@Deprecated
+public class MsgQueue {
+    public static String managerTopic() {
+        return String.format("devops.%s", AppId.manager.name());
+    }
+}

+ 9 - 0
common/src/main/java/cn/reghao/devops/common/msg/constant/AppId.java

@@ -0,0 +1,9 @@
+package cn.reghao.devops.common.msg.constant;
+
+/**
+ * @author reghao
+ * @date 2021-08-27 17:32:08
+ */
+public enum AppId {
+    manager, build, agent
+}

+ 9 - 0
common/src/main/java/cn/reghao/devops/common/msg/constant/AppStatOps.java

@@ -0,0 +1,9 @@
+package cn.reghao.devops.common.msg.constant;
+
+/**
+ * @author reghao
+ * @date 2020-12-25 19:15:00
+ */
+public enum AppStatOps {
+    start, stop, restart, stat
+}

+ 9 - 0
common/src/main/java/cn/reghao/devops/common/msg/constant/NodeStatus.java

@@ -0,0 +1,9 @@
+package cn.reghao.devops.common.msg.constant;
+
+/**
+ * @author reghao
+ * @date 2021-08-27 17:33:20
+ */
+public enum NodeStatus {
+    Online, Offline, Timeout
+}

+ 15 - 0
common/src/main/java/cn/reghao/devops/common/msg/constant/PackType.java

@@ -0,0 +1,15 @@
+package cn.reghao.devops.common.msg.constant;
+
+/**
+ * 打包方式类型
+ *
+ * @author reghao
+ * @date 2019-11-15 21:59:35
+ */
+public enum PackType {
+    docker, zip;
+
+    public String getName() {
+        return this.name();
+    }
+}

+ 11 - 0
common/src/main/java/cn/reghao/devops/common/msg/constant/Protocol.java

@@ -0,0 +1,11 @@
+package cn.reghao.devops.common.msg.constant;
+
+/**
+ * agent 和 manager 通信使用的协议
+ *
+ * @author reghao
+ * @date 2023-03-01 13:47:55
+ */
+public enum Protocol {
+    local, ws, amqp
+}

+ 26 - 0
common/src/main/java/cn/reghao/devops/common/msg/event/EvtAgentHeartbeat.java

@@ -0,0 +1,26 @@
+package cn.reghao.devops.common.msg.event;
+
+import cn.reghao.jutil.jdk.event.message.Event;
+import cn.reghao.jutil.jdk.machine.data.detail.DiskDetail;
+import cn.reghao.jutil.jdk.machine.data.detail.MemoryDetail;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2022-05-05 13:59:15
+ */
+@Getter
+@Setter
+public class EvtAgentHeartbeat extends Event {
+    private String machineId;
+    private MemoryDetail memoryDetail;
+    private List<DiskDetail> diskDetails;
+    private Long timestamp;
+
+    public EvtAgentHeartbeat() {
+        this.timestamp = System.currentTimeMillis();
+    }
+}

+ 33 - 0
common/src/main/java/cn/reghao/devops/common/msg/event/EvtAgentStart.java

@@ -0,0 +1,33 @@
+package cn.reghao.devops.common.msg.event;
+
+import cn.reghao.devops.common.version.AppVersion;
+import cn.reghao.jutil.jdk.event.message.Event;
+import cn.reghao.jutil.jdk.machine.data.detail.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2022-05-05 13:59:15
+ */
+@Getter
+@Setter
+public class EvtAgentStart extends Event {
+    @Deprecated
+    private String protocol;
+    private String machineId;
+    private AppVersion appVersion;
+
+    private OsDetail osDetail;
+    private List<NetworkDetail> networkDetails;
+    private CpuDetail cpuDetail;
+    private MemoryDetail memoryDetail;
+    private List<DiskDetail> diskDetails;
+    private Long timestamp;
+
+    public EvtAgentStart() {
+        this.timestamp = System.currentTimeMillis();
+    }
+}

+ 19 - 0
common/src/main/java/cn/reghao/devops/common/msg/event/EvtAppDeploy.java

@@ -0,0 +1,19 @@
+package cn.reghao.devops.common.msg.event;
+
+import cn.reghao.jutil.jdk.event.message.Event;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2022-05-05 13:56:22
+ */
+@Getter
+@Setter
+public class EvtAppDeploy extends Event {
+    private String packType;
+    private String appId;
+    private String packagePath;
+    private String startScript;
+    private String startHome;
+}

+ 22 - 0
common/src/main/java/cn/reghao/devops/common/msg/event/EvtAppStat.java

@@ -0,0 +1,22 @@
+package cn.reghao.devops.common.msg.event;
+
+import cn.reghao.devops.common.msg.constant.AppStatOps;
+import cn.reghao.jutil.jdk.event.message.Event;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2023-03-06 16:04:48
+ */
+@Getter
+public class EvtAppStat extends Event {
+    private final String packType;
+    private final String appId;
+    private final String ops;
+
+    public EvtAppStat(String packType, String appId, AppStatOps appStatOps) {
+        this.packType = packType;
+        this.appId = appId;
+        this.ops = appStatOps.name();
+    }
+}

Some files were not shown because too many files changed in this diff