feat: NEZ-2030 nz-talon组件 OTA 接口开发
This commit is contained in:
@@ -2,8 +2,10 @@ package net.geedge.confagent;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@PropertySource(value = { "classpath:version.properties" }, encoding = "utf-8", ignoreResourceNotFound = true)
|
||||||
public class ConfagentApplication {
|
public class ConfagentApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.geedge.confagent.controller;
|
package net.geedge.confagent.controller;
|
||||||
|
|
||||||
import cn.hutool.log.Log;
|
import cn.hutool.log.Log;
|
||||||
|
import net.geedge.confagent.util.Tool;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
public abstract class BaseController {
|
public abstract class BaseController {
|
||||||
@@ -22,6 +23,15 @@ public abstract class BaseController {
|
|||||||
|
|
||||||
@Value("${confagent.promtail.config}")
|
@Value("${confagent.promtail.config}")
|
||||||
protected String promtailConfPath;
|
protected String promtailConfPath;
|
||||||
|
|
||||||
|
@Value("${version}")
|
||||||
|
protected String currentVersion;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aes 对称加密 密钥
|
||||||
|
*/
|
||||||
|
public static final byte[] AES_SECRET_KEY = Tool.HexUtil.decodeHex("bde5430614b21baf1c53bd6f616d1a39");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用 /-/reload 接口,热加载 server 配置文件,
|
* 调用 /-/reload 接口,热加载 server 配置文件,
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package net.geedge.confagent.controller;
|
||||||
|
|
||||||
|
import cn.hutool.log.Log;
|
||||||
|
import net.geedge.confagent.util.ConfagentUtil;
|
||||||
|
import net.geedge.confagent.util.R;
|
||||||
|
import net.geedge.confagent.util.RCode;
|
||||||
|
import net.geedge.confagent.util.Tool;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class OTAController extends BaseController{
|
||||||
|
|
||||||
|
private static Log log = Log.get();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfagentUtil confagentUtil;
|
||||||
|
|
||||||
|
private static final String UPDATE_PACKAGE_PATH = "/tmp/nezha/nz-talon/%s/%s";
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/ota")
|
||||||
|
public R ota(HttpServletRequest request, @RequestParam(name = "file", required = false) MultipartFile file, String md5 )throws IOException{
|
||||||
|
|
||||||
|
//检查token
|
||||||
|
String token = request.getHeader("Authorization");
|
||||||
|
if (Tool.StrUtil.isBlank(token)) {
|
||||||
|
return R.error(RCode.AUTH_TOKEN_ISNULL);
|
||||||
|
}else {
|
||||||
|
if (confagentUtil.checkToken(token).getCode() != RCode.SUCCESS.getCode())return R.error(RCode.AUTH_TOKEN_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
//将文件缓存到 /tmp 目录下
|
||||||
|
String updateFileName = file.getOriginalFilename();
|
||||||
|
File updatePackagePath = Tool.FileUtil.file(String.format(UPDATE_PACKAGE_PATH , UUID.randomUUID().toString().replaceAll("-", ""), updateFileName));
|
||||||
|
updatePackagePath.mkdirs();
|
||||||
|
try {
|
||||||
|
file.transferTo(updatePackagePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return R.error(RCode.OTA_FILE_CACHE_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
//校验MD5
|
||||||
|
String updateFileMd5 = Tool.DigestUtil.md5Hex(new FileInputStream(updatePackagePath));
|
||||||
|
log.info("updateFileMd5 : {}",updateFileMd5);
|
||||||
|
md5 = Tool.AesUtil.decrypt(md5 , AES_SECRET_KEY);
|
||||||
|
if(!updateFileMd5.equals(md5)){
|
||||||
|
return R.error(RCode.OTA_MD5_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
//校验版本号
|
||||||
|
String fileName = file.getOriginalFilename();
|
||||||
|
String updateVersion = fileName.split("-")[2];
|
||||||
|
updateVersion = updateVersion.substring(0,updateVersion.lastIndexOf("."));
|
||||||
|
log.info("current version : {} , update version : {}", currentVersion , updateVersion);
|
||||||
|
if( !(Tool.DateUtil.betweenDay(Tool.DateUtil.parseDate(updateVersion),Tool.DateUtil.parseDate(currentVersion), false) > 0)){
|
||||||
|
return R.error(RCode.OTA_VERSION_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//检查atd服务是否启动
|
||||||
|
InputStream atdStatus = Tool.RuntimeUtil.exec("systemctl status atd").getInputStream();
|
||||||
|
String atdStatusStr = Tool.IoUtil.read(atdStatus).toString();
|
||||||
|
if(!atdStatusStr.contains("Active: active (running)")){
|
||||||
|
return R.error(RCode.OTA_ATD_SERVICE_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
//延迟一分钟更新 使用echo 将更新命令传入at
|
||||||
|
String atCommand ="echo \"rpm -Uvh %s\" | at now +1 minutes";
|
||||||
|
atCommand = String.format(atCommand,updatePackagePath);
|
||||||
|
log.info("update nz-agent : {}", atCommand);
|
||||||
|
new ProcessBuilder("/bin/bash", "-c", atCommand).start();
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,6 +18,11 @@ public enum RCode {
|
|||||||
PROMTAIL_START_CMD_ERROR(10009,"The promtail start command is error"),
|
PROMTAIL_START_CMD_ERROR(10009,"The promtail start command is error"),
|
||||||
PROMTAIL_STOP_CMD_ERROR(10010,"The promtail stop command is error"),
|
PROMTAIL_STOP_CMD_ERROR(10010,"The promtail stop command is error"),
|
||||||
|
|
||||||
|
OTA_FILE_CACHE_ERROR(10011,"Cache rpm package failed"),
|
||||||
|
OTA_MD5_ERROR(10012,"MD5 is error"),
|
||||||
|
OTA_VERSION_ERROR(10013,"The updated version is lower than the current version"),
|
||||||
|
OTA_ATD_SERVICE_DOWN(10014,"The ATD service is down"),
|
||||||
|
|
||||||
ERROR(999, "error"); //通用错误/未知错误
|
ERROR(999, "error"); //通用错误/未知错误
|
||||||
private RCode(Integer code, String msg) {
|
private RCode(Integer code, String msg) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import java.lang.reflect.Type;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.Temporal;
|
import java.time.temporal.Temporal;
|
||||||
import java.time.temporal.TemporalAccessor;
|
import java.time.temporal.TemporalAccessor;
|
||||||
@@ -24,6 +26,10 @@ import java.util.Collection;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import javax.tools.JavaCompiler;
|
import javax.tools.JavaCompiler;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
@@ -1103,4 +1109,66 @@ public class Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
//aes 工具
|
||||||
|
public static class AesUtil {
|
||||||
|
|
||||||
|
private static final String KEY_ALGORITHM = "AES";
|
||||||
|
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";// 默认的加密算法
|
||||||
|
|
||||||
|
public static String encrypt(String content, byte[] key) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
|
||||||
|
|
||||||
|
byte[] byteContent = content.getBytes("utf-8");
|
||||||
|
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器
|
||||||
|
|
||||||
|
byte[] result = cipher.doFinal(byteContent);// 加密
|
||||||
|
|
||||||
|
return cn.hutool.core.util.HexUtil.encodeHexStr(result);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decrypt(String content, byte[] key) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 实例化
|
||||||
|
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
|
||||||
|
|
||||||
|
// 使用密钥初始化,设置为解密模式
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
|
||||||
|
|
||||||
|
// 执行操作
|
||||||
|
byte[] result = cipher.doFinal(cn.hutool.core.util.HexUtil.decodeHex(content));
|
||||||
|
String s = new String(result, "utf-8");
|
||||||
|
return s;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SecretKeySpec getSecretKey(byte[] key) {
|
||||||
|
// 返回生成指定算法密钥生成器的 KeyGenerator 对象
|
||||||
|
KeyGenerator kg = null;
|
||||||
|
try {
|
||||||
|
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
|
||||||
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
random.setSeed(key);
|
||||||
|
// AES 要求密钥长度为 128
|
||||||
|
kg.init(128, random);
|
||||||
|
// 生成一个密钥
|
||||||
|
SecretKey secretKey = kg.generateKey();
|
||||||
|
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: prod
|
active: prod
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 100MB
|
||||||
|
max-request-size: 100MB
|
||||||
|
enabled: true
|
||||||
|
|
||||||
confagent:
|
confagent:
|
||||||
tokenFile: ./config/token.auth
|
tokenFile: ./config/token.auth
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ mkdir -p $RPM_TALON_PATH $RPM_PROMTAIL_PATH
|
|||||||
mkdir -p $RPM_TALON_PATH/config
|
mkdir -p $RPM_TALON_PATH/config
|
||||||
|
|
||||||
echo 'packaging nz-talon ...'
|
echo 'packaging nz-talon ...'
|
||||||
|
#添加版本信息
|
||||||
|
cat >./src/main/resources/version.properties<<EOF
|
||||||
|
version=${BRANCH_ARRAY[1]}
|
||||||
|
commit=$CI_COMMIT_SHORT_SHA
|
||||||
|
buildDate=`date +'%Y%m%d%H%m%S'`
|
||||||
|
EOF
|
||||||
# mvn 编译 nz-talon
|
# mvn 编译 nz-talon
|
||||||
mvn clean install -Dxjar.password=111111 -Dxjar.excludes=/db/*,/static/**/*
|
mvn clean install -Dxjar.password=111111 -Dxjar.excludes=/db/*,/static/**/*
|
||||||
cd ./target && go build xjar.go
|
cd ./target && go build xjar.go
|
||||||
|
|||||||
Reference in New Issue
Block a user