feat:新增升级模块

This commit is contained in:
samwaf
2023-09-05 23:57:38 +08:00
parent d42a485d3c
commit a9fcb0803a
25 changed files with 1102 additions and 58 deletions
+51 -27
View File
@@ -2,13 +2,11 @@ package api
import (
"SamWaf/global"
"SamWaf/innerbean"
"SamWaf/model"
"SamWaf/model/common/response"
"SamWaf/utils/zlog"
"encoding/json"
"io/ioutil"
"net/http"
"SamWaf/wafupdate"
"github.com/gin-gonic/gin"
)
@@ -24,44 +22,70 @@ func (w *WafSysInfoApi) SysVersionApi(c *gin.Context) {
}
func (w *WafSysInfoApi) CheckVersionApi(c *gin.Context) {
resp, err := http.Get(global.GUPDATE_VERSION_URL)
if err != nil {
zlog.Error("读取版本信息失败", err.Error())
response.FailWithMessage("读取版本信息网络失败", c)
var updater = &wafupdate.Updater{
CurrentVersion: global.GWAF_RELEASE_VERSION, // Manually update the const, or set it using `go build -ldflags="-X main.VERSION=<newver>" -o hello-updater src/hello-updater/main.go`
ApiURL: global.GUPDATE_VERSION_URL, // The server hosting `$CmdName/$GOOS-$ARCH.json` which contains the checksum for the binary
BinURL: global.GUPDATE_VERSION_URL, // The server hosting the zip file containing the binary application which is a fallback for the patch method
DiffURL: global.GUPDATE_VERSION_URL, // The server hosting the binary patch diff for incremental updates
Dir: "tmp_update/", // The directory created by the app when run which stores the cktime file
CmdName: "samwaf_update", // The app name which is appended to the ApiURL to look for an update
//ForceCheck: true, // For this example, always check for an update unless the version is "dev"
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
available, newVer, desc, err := updater.UpdateAvailable()
if err != nil {
zlog.Error("读取版本信息失败", err.Error())
response.FailWithMessage("读取版本信息内容失败", c)
response.FailWithMessage("发生错误", c)
return
}
var updateInfo = model.UpdateVersion{}
json.Unmarshal(body, &updateInfo)
if updateInfo.VersionCode > global.GetCurrentVersionInt() {
if available {
response.OkWithDetailed(model.VersionInfo{
Version: global.GWAF_RELEASE_VERSION,
VersionName: global.GWAF_RELEASE_VERSION_NAME,
VersionRelease: global.GWAF_RELEASE,
NeedUpdate: true,
VersionNew: newVer,
VersionDesc: desc,
}, "有新版本", c)
} else {
response.OkWithDetailed(model.VersionInfo{
Version: global.GWAF_RELEASE_VERSION,
VersionName: global.GWAF_RELEASE_VERSION_NAME,
VersionRelease: global.GWAF_RELEASE,
NeedUpdate: false,
}, "已经是最新版本", c)
response.FailWithMessage("没有最新版本", c)
return
}
}
// TODO 去升级
// 去升级
func (w *WafSysInfoApi) UpdateApi(c *gin.Context) {
var updater = &wafupdate.Updater{
CurrentVersion: global.GWAF_RELEASE_VERSION, // Manually update the const, or set it using `go build -ldflags="-X main.VERSION=<newver>" -o hello-updater src/hello-updater/main.go`
ApiURL: global.GUPDATE_VERSION_URL, // The server hosting `$CmdName/$GOOS-$ARCH.json` which contains the checksum for the binary
BinURL: global.GUPDATE_VERSION_URL, // The server hosting the zip file containing the binary application which is a fallback for the patch method
DiffURL: global.GUPDATE_VERSION_URL, // The server hosting the binary patch diff for incremental updates
Dir: "tmp_update/", // The directory created by the app when run which stores the cktime file
CmdName: "samwaf_update", // The app name which is appended to the ApiURL to look for an update
//ForceCheck: true, // For this example, always check for an update unless the version is "dev"
OnSuccessfulUpdate: func() {
zlog.Info("OnSuccessfulUpdate 升级成功")
global.GWAF_CHAN_UPDATE <- 1
//发送websocket 推送消息
global.GQEQUE_MESSAGE_DB.PushBack(innerbean.UpdateResultMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "升级结果", Server: global.GWAF_CUSTOM_SERVER_NAME},
Msg: "升级成功",
Success: "true",
})
},
}
go func() {
// try to update
err := updater.BackgroundRun()
if err != nil {
response.OkWithDetailed(model.VersionInfo{
Version: global.GWAF_RELEASE_VERSION,
VersionName: global.GWAF_RELEASE_VERSION_NAME,
VersionRelease: global.GWAF_RELEASE,
}, "获取成功", c)
//发送websocket 推送消息
global.GQEQUE_MESSAGE_DB.PushBack(innerbean.UpdateResultMessageInfo{
BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "升级结果", Server: global.GWAF_CUSTOM_SERVER_NAME},
Msg: "升级错误",
Success: "False",
})
zlog.Error("Failed to update app:", err)
}
}()
response.OkWithMessage("已发起升级,等待通知结果", c)
}
+1 -1
View File
@@ -26,7 +26,7 @@ func TestCheckVersionApi(t *testing.T) {
// 创建一个基于 Gin 的引擎
r := gin.Default()
global.GWAF_RELEASE_VERSION = "111"
global.GWAF_RELEASE_VERSION = "v1.0.0"
r.GET("/samwaf/sysinfo/checkversion", new(WafSysInfoApi).CheckVersionApi)
// 创建一个模拟的 HTTP 请求
req, err := http.NewRequest(http.MethodGet, "/samwaf/sysinfo/checkversion", nil)
-5
View File
@@ -1,5 +0,0 @@
SET CGO_ENABLED=1
SET GOOS=windows
SET GOARCH=amd64
SET GIN_MODE=release
go build -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20230830 -X SamWaf/global.GWAF_RELEASE_VERSION=113 -s -w" -o %cd%/release/SamWaf64.exe main.go && %cd%/upx/win64/upx -9 %cd%/release/SamWaf64.exe
+5
View File
@@ -0,0 +1,5 @@
SET CGO_ENABLED=1
SET GOOS=windows
SET GOARCH=amd64
SET GIN_MODE=release
go build -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20230905 -X SamWaf/global.GWAF_RELEASE_VERSION=v1.0.115 -s -w" -o %cd%/release/SamWaf64.exe main.go && %cd%/upx/win64/upx -9 %cd%/release/SamWaf64.exe
+1 -1
View File
@@ -2,4 +2,4 @@ SET CGO_ENABLED=1
SET GOOS=windows
SET GOARCH=amd64
SET GIN_MODE=release
go build -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20230830 -X SamWaf/global.GWAF_RELEASE_VERSION=113 -s -w" -o %cd%/release/SamWaf64.exe main.go
go build -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20230905 -X SamWaf/global.GWAF_RELEASE_VERSION=115 -s -w" -o %cd%/release/SamWaf64.exe main.go
+1 -1
View File
@@ -1 +1 @@
docker run --rm -v "$PWD":/media/sf_SamWaf -w /media/sf_SamWaf -e CGO_ENABLED=1 -e GOPROXY=https://goproxy.cn,direct golang:1.19 go build -v -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20230830 -X SamWaf/global.GWAF_RELEASE_VERSION=113 -s -w -extldflags "-static"" -o /media/sf_SamWaf/release/SamWafLinux64 main.go
docker run --rm -v "$PWD":/media/sf_SamWaf -w /media/sf_SamWaf -e CGO_ENABLED=1 -e GOPROXY=https://goproxy.cn,direct golang:1.19 go build -v -ldflags="-X SamWaf/global.GWAF_RELEASE=true -X SamWaf/global.GWAF_RELEASE_VERSION_NAME=20230905 -X SamWaf/global.GWAF_RELEASE_VERSION=v1.0.115 -s -w -extldflags "-static"" -o /media/sf_SamWaf/release/SamWafLinux64 main.go
+20 -19
View File
@@ -20,29 +20,30 @@ const (
var (
/*本机信息**/
GWAF_RUNTIME_IP string = "127.0.0.1" //本机当前外网IP
GWAF_RUNTIME_AREA string = "" //本机当前所在区域
GWAF_RUNTIME_IP string = "127.0.0.1" //本机当前外网IP
GWAF_RUNTIME_AREA string = "" //本机当前所在区域
GWAF_RUNTIME_SERVER_TYPE bool = false //当前是是否以服务形式启动
GWAF_GLOBAL_HOST_NAME string = "全局网站:0" //全局网站
GWAF_LOCAL_DB *gorm.DB //通用本地数据库,尊重用户隐私
GWAF_LOCAL_LOG_DB *gorm.DB //通用本地数据库存日志数据,尊重用户隐私
GWAF_REMOTE_DB *gorm.DB //仅当用户使用云数据库
GWAF_LOCAL_SERVER_PORT int = 26666 // 本地local端口
GWAF_USER_CODE string // 当前识别号
GWAF_CUSTOM_SERVER_NAME string // 当前服务器自定义名称
GWAF_TENANT_ID string // 当前租户ID
GWAF_RELEASE string = "false" // 当前是否为发行版
GWAF_RELEASE_VERSION_NAME string = "1.0" // 发行版的版本号名称
GWAF_RELEASE_VERSION string = "1" // 发行版的版本号
GWAF_LAST_UPDATE_TIME time.Time // 上次时间
GWAF_DLP dlpheader.EngineAPI // 脱敏引擎
GWAF_LOCAL_DB *gorm.DB //通用本地数据库,尊重用户隐私
GWAF_LOCAL_LOG_DB *gorm.DB //通用本地数据库存日志数据,尊重用户隐私
GWAF_REMOTE_DB *gorm.DB //仅当用户使用云数据库
GWAF_LOCAL_SERVER_PORT int = 26666 // 本地local端口
GWAF_USER_CODE string // 当前识别号
GWAF_CUSTOM_SERVER_NAME string // 当前服务器自定义名称
GWAF_TENANT_ID string // 当前租户ID
GWAF_RELEASE string = "false" // 当前是否为发行版
GWAF_RELEASE_VERSION_NAME string = "1.0" // 发行版的版本号名称
GWAF_RELEASE_VERSION string = "v1.0.0" // 发行版的版本号
GWAF_LAST_UPDATE_TIME time.Time // 上次时间
GWAF_DLP dlpheader.EngineAPI // 脱敏引擎
/**链聚合**/
GWAF_CHAN_HOST = make(chan model.Hosts, 10) //主机链
GWAF_CHAN_ENGINE = make(chan int, 10) //引擎链
GWAF_CHAN_MSG = make(chan spec.ChanCommonHost, 10) //全局通讯包
GWAF_CHAN_HOST = make(chan model.Hosts, 10) //主机链
GWAF_CHAN_ENGINE = make(chan int, 10) //引擎链
GWAF_CHAN_MSG = make(chan spec.ChanCommonHost, 10) //全局通讯包
GWAF_CHAN_UPDATE = make(chan int, 10) //升级后处理链
/*****CACHE相关*********/
GCACHE_WAFCACHE *cache.WafCache //cache
@@ -65,7 +66,7 @@ var (
GCONFIG_RECORD_RESP int64 = 0 // 是否记录响应记录 record_resp
//升级相关
GUPDATE_VERSION_URL string = "http://update.binaite.net/version.json"
GUPDATE_VERSION_URL string = "http://127.0.0.1:81/"
)
func GetCurrentVersionInt() int {
+3 -1
View File
@@ -13,12 +13,15 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/hyperjumptech/grule-rule-engine v1.11.0
github.com/kardianos/service v1.2.2
github.com/kr/binarydist v0.1.0
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20220907060842-b2ba5d58e48d
github.com/satori/go.uuid v1.2.0
github.com/spf13/viper v1.13.0
github.com/stretchr/testify v1.8.0
go.uber.org/zap v1.21.0
golang.org/x/mod v0.4.2
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
@@ -71,7 +74,6 @@ require (
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
+3
View File
@@ -193,6 +193,8 @@ github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -345,6 +347,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+10
View File
@@ -33,6 +33,16 @@ type RuleMessageInfo struct {
Ip string `json:"ip"`
}
/*
*
升级结果
*/
type UpdateResultMessageInfo struct {
BaseMessageInfo
Msg string `json:"msg"`
Success string `json:"success"`
}
func (r RuleMessageInfo) ToFormat() map[string]*wechat.DataItem {
Data := map[string]*wechat.DataItem{}
Data["domain"] = &wechat.DataItem{
+19 -1
View File
@@ -7,4 +7,22 @@ export function SysVersionApi(params) {
method: 'get',
params: params
})
}
}
//查询是否需要升级版本信息
export function CheckVersionApi(params) {
return request({
url: 'sysinfo/checkversion',
method: 'get',
params: params
})
}
//升级
export function DoUpdateApi(params) {
return request({
url: 'sysinfo/update',
method: 'get',
params: params
})
}
+61 -1
View File
@@ -1,5 +1,13 @@
<template>
<div :class="layoutCls">
<t-dialog :visible.sync="update_visible" @confirm="handleDoUpdate" header="有新版本啦">
<p> <icon name="tangerinr" color="orange" />
{{update_new_ver}}</p>
<p>
{{update_desc}}</p>
</t-dialog>
<t-head-menu :class="menuCls" :theme="theme" expandType="popup" :value="active">
<template #logo>
<span v-if="showLogo" class="header-logo-container" @click="handleNav('/dashboard/base')">
@@ -20,7 +28,11 @@
<!-- 全局通知 -->
<notice />
<t-tooltip placement="bottom" content="升级">
<t-button :disabled="isUpdateloading" theme="default" shape="square" variant="text" @click="checkVersion">
<RotateIcon />
</t-button>
</t-tooltip>
<t-tooltip placement="bottom" content="重启">
<t-button :disabled="isResetloading" theme="default" shape="square" variant="text" @click="resetServer">
<PoweroffIcon />
@@ -73,12 +85,18 @@
PoweroffIcon,
SettingIcon,
ChevronDownIcon,
RotateIcon,
Icon
} from 'tdesign-icons-vue';
import {
prefix
} from '@/config/global';
import LogoFull from '@/assets/assets-logo-full.svg';
import {
CheckVersionApi,DoUpdateApi
} from '@/apis/sysinfo';
import Notice from './Notice.vue';
import Search from './Search.vue';
import MenuContent from './MenuContent.vue';
@@ -96,6 +114,8 @@
PoweroffIcon,
SettingIcon,
ChevronDownIcon,
RotateIcon,
Icon
},
props: {
theme: String,
@@ -129,6 +149,11 @@
visibleNotice: false,
isSearchFocus: false,
isResetloading:false,
/**更新内容**/
isUpdateloading:false,
update_visible:false,
update_new_ver:"",
update_desc:"",
current_account:"not login",
};
},
@@ -210,6 +235,41 @@
that.isResetloading = false
})
.finally(() => {});
} ,
checkVersion(){
let that = this;
CheckVersionApi().then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
//that.$message.success(resdata.msg);
that.update_visible = true
that.update_new_ver = resdata.data.version_new
that.update_desc = resdata.data.version_desc
}else{
that.$message.warning(resdata.msg);
}
})
.catch((e: Error) => {
that.$message.warning("检测版本异常,请检测网络");
})
},
handleDoUpdate(){
//处理升级
let that = this;
DoUpdateApi().then((res) => {
let resdata = res
console.log(resdata)
if (resdata.code === 0) {
that.$message.success(resdata.msg);
that.update_visible = false
}else{
that.$message.warning(resdata.msg);
}
})
.catch((e: Error) => {
console.log(e);
})
}
},
});
+37
View File
@@ -23,6 +23,7 @@ import (
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"runtime"
"strconv"
@@ -232,6 +233,34 @@ func (m *wafSystenService) run() {
wafEngine.HostTarget[host.Host+":"+strconv.Itoa(host.Port)].Host.GUARD_STATUS = host.GUARD_STATUS
zlog.Debug("规则", zap.Any("主机", host))
break
case update := <-global.GWAF_CHAN_UPDATE:
if update == 1 {
//需要重新启动
if global.GWAF_RUNTIME_SERVER_TYPE == false {
zlog.Info("服务形式重启")
// 获取当前执行文件的路径
executablePath, err := os.Executable()
if err != nil {
fmt.Println("Error:", err)
return
}
// 使用filepath包提取文件名
//executableName := filepath.Base(executablePath)
var cmd *exec.Cmd
cmd = exec.Command(executablePath, "restart")
cmd.Run()
// 等待新实例完成
err = cmd.Wait()
if err != nil {
fmt.Println("Error:", err)
return
}
} else {
zlog.Info("非服务形式重启,请手工打开")
os.Exit(0)
}
}
}
}
@@ -286,6 +315,14 @@ func main() {
return
}
if service.Interactive() {
zlog.Info("main general run true")
global.GWAF_RUNTIME_SERVER_TYPE = service.Interactive()
} else {
zlog.Info("main server run false")
global.GWAF_RUNTIME_SERVER_TYPE = service.Interactive()
}
// 以常规方式运行
err = s.Run()
if err != nil {
+2
View File
@@ -5,4 +5,6 @@ type VersionInfo struct {
Version string `json:"version"`
VersionName string `json:"version_name"`
VersionRelease string `json:"version_release"`
VersionNew string `json:"version_new"`
VersionDesc string `json:"version_desc"`
}
+1
View File
@@ -0,0 +1 @@
C:\huawei\goproject\SamWaf\setup\go_gen_updatefile\go_gen_updatefile.exe -o C:\huawei\goproject\SamWaf\release\web\samwaf_update C:\huawei\goproject\SamWaf\release\SamWaf64.exe v1.0.30 fixsamebug
Binary file not shown.
@@ -0,0 +1,185 @@
package main
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"github.com/kr/binarydist"
)
var version, genDir string
type current struct {
Version string
Sha256 []byte
Desc string
}
func generateSha256(path string) []byte {
h := sha256.New()
b, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println(err)
}
h.Write(b)
sum := h.Sum(nil)
return sum
//return base64.URLEncoding.EncodeToString(sum)
}
type gzReader struct {
z, r io.ReadCloser
}
func (g *gzReader) Read(p []byte) (int, error) {
return g.z.Read(p)
}
func (g *gzReader) Close() error {
g.z.Close()
return g.r.Close()
}
func newGzReader(r io.ReadCloser) io.ReadCloser {
var err error
g := new(gzReader)
g.r = r
g.z, err = gzip.NewReader(r)
if err != nil {
panic(err)
}
return g
}
func createUpdate(path string, platform string, desc string) {
c := current{Version: version, Sha256: generateSha256(path), Desc: desc}
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
fmt.Println("error:", err)
}
err = ioutil.WriteFile(filepath.Join(genDir, platform+".json"), b, 0755)
if err != nil {
panic(err)
}
os.MkdirAll(filepath.Join(genDir, version), 0755)
var buf bytes.Buffer
w := gzip.NewWriter(&buf)
f, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
w.Write(f)
w.Close() // You must close this first to flush the bytes to the buffer.
err = ioutil.WriteFile(filepath.Join(genDir, version, platform+".gz"), buf.Bytes(), 0755)
files, err := ioutil.ReadDir(genDir)
if err != nil {
fmt.Println(err)
}
for _, file := range files {
if file.IsDir() == false {
continue
}
if file.Name() == version {
continue
}
os.Mkdir(filepath.Join(genDir, file.Name(), version), 0755)
fName := filepath.Join(genDir, file.Name(), platform+".gz")
old, err := os.Open(fName)
if err != nil {
// Don't have an old release for this os/arch, continue on
continue
}
fName = filepath.Join(genDir, version, platform+".gz")
newF, err := os.Open(fName)
if err != nil {
fmt.Fprintf(os.Stderr, "Can't open %s: error: %s\n", fName, err)
os.Exit(1)
}
ar := newGzReader(old)
defer ar.Close()
br := newGzReader(newF)
defer br.Close()
patch := new(bytes.Buffer)
if err := binarydist.Diff(ar, br, patch); err != nil {
panic(err)
}
ioutil.WriteFile(filepath.Join(genDir, file.Name(), version, platform), patch.Bytes(), 0755)
}
}
func printUsage() {
fmt.Println("")
fmt.Println("Positional arguments:")
fmt.Println("\tSingle platform: go_gen_updatefile myapp 1.2 fixbug")
fmt.Println("\tCross platform: go_gen_updatefile /tmp/mybinares/ 1.2 fixbug")
}
func createBuildDir() {
os.MkdirAll(genDir, 0755)
}
func main() {
outputDirFlag := flag.String("o", "public", "Output directory for writing updates")
var defaultPlatform string
goos := os.Getenv("GOOS")
goarch := os.Getenv("GOARCH")
if goos != "" && goarch != "" {
defaultPlatform = goos + "-" + goarch
} else {
defaultPlatform = runtime.GOOS + "-" + runtime.GOARCH
}
platformFlag := flag.String("platform", defaultPlatform,
"Target platform in the form OS-ARCH. Defaults to running os/arch or the combination of the environment variables GOOS and GOARCH if both are set.")
flag.Parse()
if flag.NArg() < 2 {
flag.Usage()
printUsage()
os.Exit(0)
}
platform := *platformFlag
appPath := flag.Arg(0)
version = flag.Arg(1)
desc := flag.Arg(2)
genDir = *outputDirFlag
createBuildDir()
// If dir is given create update for each file
fi, err := os.Stat(appPath)
if err != nil {
panic(err)
}
if fi.IsDir() {
files, err := ioutil.ReadDir(appPath)
if err == nil {
for _, file := range files {
createUpdate(filepath.Join(appPath, file.Name()), file.Name(), desc)
}
os.Exit(0)
}
}
createUpdate(appPath, platform, desc)
}
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sam网站应用级入侵防御系统后台(Web Application Firewall</title>
<script type="module" crossorigin src="./assets/index.1d988e1b.js"></script>
<script type="module" crossorigin src="./assets/index.a7e27a20.js"></script>
<link rel="stylesheet" href="./assets/style.caefab01.css">
</head>
<body>
+22
View File
@@ -95,6 +95,28 @@ func ProcessDequeEngine() {
operatorMessage := messageinfo.(innerbean.OperatorMessageInfo)
utils.NotifyHelperApp.SendNoticeInfo(operatorMessage)
break
case innerbean.UpdateResultMessageInfo:
//升级结果
updatemessage := messageinfo.(innerbean.UpdateResultMessageInfo)
//发送websocket
for _, ws := range global.GWebSocket {
if ws != nil {
//写入ws数据
msgBytes, err := json.Marshal(model.MsgPacket{
MessageId: uuid.NewV4().String(),
MessageType: "升级结果",
MessageData: updatemessage.Msg,
MessageAttach: nil,
MessageDateTime: time.Now().Format("2006-01-02 15:04:05"),
MessageUnReadStatus: true,
})
err = ws.WriteMessage(1, msgBytes)
if err != nil {
continue
}
}
}
break
}
//zlog.Info("MESSAGE", messageinfo)
+8
View File
@@ -0,0 +1,8 @@
//go:build !windows
// +build !windows
package wafupdate
func hideFile(path string) error {
return nil
}
+19
View File
@@ -0,0 +1,19 @@
package wafupdate
import (
"syscall"
"unsafe"
)
func hideFile(path string) error {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
setFileAttributes := kernel32.NewProc("SetFileAttributesW")
r1, _, err := setFileAttributes.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))), 2)
if r1 == 0 {
return err
} else {
return nil
}
}
+56
View File
@@ -0,0 +1,56 @@
package wafupdate
import (
"fmt"
"io"
"net/http"
)
// Requester interface allows developers to customize the method in which
// requests are made to retrieve the version and binary.
type Requester interface {
Fetch(url string) (io.ReadCloser, error)
}
// HTTPRequester is the normal requester that is used and does an HTTP
// to the URL location requested to retrieve the specified data.
type HTTPRequester struct{}
// Fetch will return an HTTP request to the specified url and return
// the body of the result. An error will occur for a non 200 status code.
func (httpRequester *HTTPRequester) Fetch(url string) (io.ReadCloser, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
}
return resp.Body, nil
}
// mockRequester used for some mock testing to ensure the requester contract
// works as specified.
type mockRequester struct {
currentIndex int
fetches []func(string) (io.ReadCloser, error)
}
func (mr *mockRequester) handleRequest(requestHandler func(string) (io.ReadCloser, error)) {
if mr.fetches == nil {
mr.fetches = []func(string) (io.ReadCloser, error){}
}
mr.fetches = append(mr.fetches, requestHandler)
}
func (mr *mockRequester) Fetch(url string) (io.ReadCloser, error) {
if len(mr.fetches) <= mr.currentIndex {
return nil, fmt.Errorf("no for currentIndex %d to mock", mr.currentIndex)
}
current := mr.fetches[mr.currentIndex]
mr.currentIndex++
return current(url)
}
+428
View File
@@ -0,0 +1,428 @@
package wafupdate
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/url"
"os"
"path/filepath"
"runtime"
"time"
"github.com/kr/binarydist"
"golang.org/x/mod/semver"
)
const (
// holds a timestamp which triggers the next update
upcktimePath = "cktime" // path to timestamp file relative to u.Dir
plat = runtime.GOOS + "-" + runtime.GOARCH // ex: linux-amd64
)
var (
ErrHashMismatch = errors.New("new file hash mismatch after patch")
defaultHTTPRequester = HTTPRequester{}
)
// Updater is the configuration and runtime data for doing an update.
//
// Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location.
//
// Example:
//
// updater := &selfupdate.Updater{
// CurrentVersion: version,
// ApiURL: "http://updates.yourdomain.com/",
// BinURL: "http://updates.yourdownmain.com/",
// DiffURL: "http://updates.yourdomain.com/",
// Dir: "update/",
// CmdName: "myapp", // app name
// }
// if updater != nil {
// go updater.BackgroundRun()
// }
type Updater struct {
CurrentVersion string // Currently running version. `dev` is a special version here and will cause the updater to never update.
ApiURL string // Base URL for API requests (JSON files).
CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary.
BinURL string // Base URL for full binary downloads.
DiffURL string // Base URL for diff downloads.
Dir string // Directory to store selfupdate state.
ForceCheck bool // Check for update regardless of cktime timestamp
CheckTime int // Time in hours before next check
RandomizeTime int // Time in hours to randomize with CheckTime
Requester Requester // Optional parameter to override existing HTTP request handler
Info struct {
Version string
Sha256 []byte
Desc string
}
OnSuccessfulUpdate func() // Optional function to run after an update has successfully taken place
}
func (u *Updater) getExecRelativeDir(dir string) string {
filename, _ := os.Executable()
path := filepath.Join(filepath.Dir(filename), dir)
return path
}
func canUpdate() (err error) {
// get the directory the file exists in
path, err := os.Executable()
if err != nil {
return
}
fileDir := filepath.Dir(path)
fileName := filepath.Base(path)
// attempt to open a file in the file's directory
newPath := filepath.Join(fileDir, fmt.Sprintf(".%s.new", fileName))
fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return
}
fp.Close()
_ = os.Remove(newPath)
return
}
// BackgroundRun starts the update check and apply cycle.
func (u *Updater) BackgroundRun() error {
if err := os.MkdirAll(u.getExecRelativeDir(u.Dir), 0755); err != nil {
// fail
return err
}
// check to see if we want to check for updates based on version
// and last update time
if u.WantUpdate() {
if err := canUpdate(); err != nil {
// fail
return err
}
u.SetUpdateTime()
if err := u.Update(); err != nil {
return err
}
}
return nil
}
// WantUpdate returns boolean designating if an update is desired. If the app's version
// is `dev` WantUpdate will return false. If u.ForceCheck is true or cktime is after now
// WantUpdate will return true.
func (u *Updater) WantUpdate() bool {
if u.CurrentVersion == "dev" || (!u.ForceCheck && u.NextUpdate().After(time.Now())) {
return false
}
return true
}
// NextUpdate returns the next time update should be checked
func (u *Updater) NextUpdate() time.Time {
path := u.getExecRelativeDir(u.Dir + upcktimePath)
nextTime := readTime(path)
return nextTime
}
// SetUpdateTime writes the next update time to the state file
func (u *Updater) SetUpdateTime() bool {
path := u.getExecRelativeDir(u.Dir + upcktimePath)
wait := time.Duration(u.CheckTime) * time.Hour
// Add 1 to random time since max is not included
waitrand := time.Duration(rand.Intn(u.RandomizeTime+1)) * time.Hour
return writeTime(path, time.Now().Add(wait+waitrand))
}
// ClearUpdateState writes current time to state file
func (u *Updater) ClearUpdateState() {
path := u.getExecRelativeDir(u.Dir + upcktimePath)
os.Remove(path)
}
// UpdateAvailable checks if update is available and returns version
func (u *Updater) UpdateAvailable() (bool, string, string, error) {
path, err := os.Executable()
if err != nil {
return false, "", "", err
}
old, err := os.Open(path)
if err != nil {
return false, "", "", err
}
defer old.Close()
err = u.fetchInfo()
if err != nil {
return false, "", "", err
}
// 比较版本号
cmp := semver.Compare(u.Info.Version, u.CurrentVersion)
// 如果更新的版本大于当前版本,返回 true,表示有可用的更新
if cmp > 0 {
return true, u.Info.Version, u.Info.Desc, nil
} else {
// 否则,返回 false,表示没有可用的更新
return false, "", "", nil
}
}
// Update initiates the self update process
func (u *Updater) Update() error {
path, err := os.Executable()
if err != nil {
return err
}
if resolvedPath, err := filepath.EvalSymlinks(path); err == nil {
path = resolvedPath
}
// go fetch latest updates manifest
err = u.fetchInfo()
if err != nil {
return err
}
// 检测是新版本才更新,否则不更新
cmp := semver.Compare(u.Info.Version, u.CurrentVersion)
if cmp <= 0 {
return nil
}
old, err := os.Open(path)
if err != nil {
return err
}
defer old.Close()
bin, err := u.fetchAndVerifyPatch(old)
if err != nil {
if err == ErrHashMismatch {
log.Println("update: hash mismatch from patched binary")
} else {
if u.DiffURL != "" {
log.Println("update: patching binary,", err)
}
}
// if patch failed grab the full new bin
bin, err = u.fetchAndVerifyFullBin()
if err != nil {
if err == ErrHashMismatch {
log.Println("update: hash mismatch from full binary")
} else {
log.Println("update: fetching full binary,", err)
}
return err
}
}
// close the old binary before installing because on windows
// it can't be renamed if a handle to the file is still open
old.Close()
err, errRecover := fromStream(bytes.NewBuffer(bin))
if errRecover != nil {
return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
}
if err != nil {
return err
}
// update was successful, run func if set
if u.OnSuccessfulUpdate != nil {
u.OnSuccessfulUpdate()
}
return nil
}
func fromStream(updateWith io.Reader) (err error, errRecover error) {
updatePath, err := os.Executable()
if err != nil {
return
}
var newBytes []byte
newBytes, err = ioutil.ReadAll(updateWith)
if err != nil {
return
}
// get the directory the executable exists in
updateDir := filepath.Dir(updatePath)
filename := filepath.Base(updatePath)
// Copy the contents of of newbinary to a the new executable file
newPath := filepath.Join(updateDir, fmt.Sprintf(".%s.new", filename))
fp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return
}
defer fp.Close()
_, err = io.Copy(fp, bytes.NewReader(newBytes))
// if we don't call fp.Close(), windows won't let us move the new executable
// because the file will still be "in use"
fp.Close()
// this is where we'll move the executable to so that we can swap in the updated replacement
oldPath := filepath.Join(updateDir, fmt.Sprintf(".%s.old", filename))
// delete any existing old exec file - this is necessary on Windows for two reasons:
// 1. after a successful update, Windows can't remove the .old file because the process is still running
// 2. windows rename operations fail if the destination file already exists
_ = os.Remove(oldPath)
// move the existing executable to a new file in the same directory
err = os.Rename(updatePath, oldPath)
if err != nil {
return
}
// move the new exectuable in to become the new program
err = os.Rename(newPath, updatePath)
if err != nil {
// copy unsuccessful
errRecover = os.Rename(oldPath, updatePath)
} else {
// copy successful, remove the old binary
errRemove := os.Remove(oldPath)
// windows has trouble with removing old binaries, so hide it instead
if errRemove != nil {
_ = hideFile(oldPath)
}
}
return
}
// fetchInfo fetches the update JSON manifest at u.ApiURL/appname/platform.json
// and updates u.Info.
func (u *Updater) fetchInfo() error {
r, err := u.fetch(u.ApiURL + url.QueryEscape(u.CmdName) + "/" + url.QueryEscape(plat) + ".json")
if err != nil {
return err
}
defer r.Close()
err = json.NewDecoder(r).Decode(&u.Info)
if err != nil {
return err
}
if len(u.Info.Sha256) != sha256.Size {
return errors.New("bad cmd hash in info")
}
return nil
}
func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
bin, err := u.fetchAndApplyPatch(old)
if err != nil {
return nil, err
}
if !verifySha(bin, u.Info.Sha256) {
return nil, ErrHashMismatch
}
return bin, nil
}
func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
r, err := u.fetch(u.DiffURL + url.QueryEscape(u.CmdName) + "/" + url.QueryEscape(u.CurrentVersion) + "/" + url.QueryEscape(u.Info.Version) + "/" + url.QueryEscape(plat))
if err != nil {
return nil, err
}
defer r.Close()
var buf bytes.Buffer
err = binarydist.Patch(old, &buf, r)
return buf.Bytes(), err
}
func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
bin, err := u.fetchBin()
if err != nil {
return nil, err
}
verified := verifySha(bin, u.Info.Sha256)
if !verified {
return nil, ErrHashMismatch
}
return bin, nil
}
func (u *Updater) fetchBin() ([]byte, error) {
r, err := u.fetch(u.BinURL + url.QueryEscape(u.CmdName) + "/" + url.QueryEscape(u.Info.Version) + "/" + url.QueryEscape(plat) + ".gz")
if err != nil {
return nil, err
}
defer r.Close()
buf := new(bytes.Buffer)
gz, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
if _, err = io.Copy(buf, gz); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (u *Updater) fetch(url string) (io.ReadCloser, error) {
if u.Requester == nil {
return defaultHTTPRequester.Fetch(url)
}
readCloser, err := u.Requester.Fetch(url)
if err != nil {
return nil, err
}
if readCloser == nil {
return nil, fmt.Errorf("Fetch was expected to return non-nil ReadCloser")
}
return readCloser, nil
}
func readTime(path string) time.Time {
p, err := ioutil.ReadFile(path)
if os.IsNotExist(err) {
return time.Time{}
}
if err != nil {
return time.Now().Add(1000 * time.Hour)
}
t, err := time.Parse(time.RFC3339, string(p))
if err != nil {
return time.Now().Add(1000 * time.Hour)
}
return t
}
func verifySha(bin []byte, sha []byte) bool {
h := sha256.New()
h.Write(bin)
return bytes.Equal(h.Sum(nil), sha)
}
func writeTime(path string, t time.Time) bool {
return ioutil.WriteFile(path, []byte(t.Format(time.RFC3339)), 0644) == nil
}
+166
View File
@@ -0,0 +1,166 @@
package wafupdate
import (
"bytes"
"io"
"testing"
"time"
)
func TestUpdaterFetchMustReturnNonNilReaderCloser(t *testing.T) {
mr := &mockRequester{}
mr.handleRequest(
func(url string) (io.ReadCloser, error) {
return nil, nil
})
updater := createUpdater(mr)
updater.CheckTime = 24
updater.RandomizeTime = 24
err := updater.BackgroundRun()
if err != nil {
equals(t, "Fetch was expected to return non-nil ReadCloser", err.Error())
} else {
t.Log("Expected an error")
t.Fail()
}
}
func TestUpdaterWithEmptyPayloadNoErrorNoUpdate(t *testing.T) {
mr := &mockRequester{}
mr.handleRequest(
func(url string) (io.ReadCloser, error) {
equals(t, "http://updates.yourdomain.com/myapp/linux-amd64.json", url)
return newTestReaderCloser("{}"), nil
})
updater := createUpdater(mr)
updater.CheckTime = 24
updater.RandomizeTime = 24
err := updater.BackgroundRun()
if err != nil {
t.Errorf("Error occurred: %#v", err)
}
}
func TestUpdaterCheckTime(t *testing.T) {
mr := &mockRequester{}
mr.handleRequest(
func(url string) (io.ReadCloser, error) {
equals(t, "http://updates.yourdomain.com/myapp/linux-amd64.json", url)
return newTestReaderCloser("{}"), nil
})
// Run test with various time
runTestTimeChecks(t, mr, 0, 0, false)
runTestTimeChecks(t, mr, 0, 5, true)
runTestTimeChecks(t, mr, 1, 0, true)
runTestTimeChecks(t, mr, 100, 100, true)
}
// Helper function to run check time tests
func runTestTimeChecks(t *testing.T, mr *mockRequester, checkTime int, randomizeTime int, expectUpdate bool) {
updater := createUpdater(mr)
updater.ClearUpdateState()
updater.CheckTime = checkTime
updater.RandomizeTime = randomizeTime
updater.BackgroundRun()
if updater.WantUpdate() == expectUpdate {
t.Errorf("WantUpdate returned %v; want %v", updater.WantUpdate(), expectUpdate)
}
maxHrs := time.Duration(updater.CheckTime+updater.RandomizeTime) * time.Hour
maxTime := time.Now().Add(maxHrs)
if !updater.NextUpdate().Before(maxTime) {
t.Errorf("NextUpdate should less than %s hrs (CheckTime + RandomizeTime) from now; now %s; next update %s", maxHrs, time.Now(), updater.NextUpdate())
}
if maxHrs > 0 && !updater.NextUpdate().After(time.Now()) {
t.Errorf("NextUpdate should be after now")
}
}
func TestUpdaterWithEmptyPayloadNoErrorNoUpdateEscapedPath(t *testing.T) {
mr := &mockRequester{}
mr.handleRequest(
func(url string) (io.ReadCloser, error) {
equals(t, "http://updates.yourdomain.com/myapp%2Bfoo/darwin-amd64.json", url)
return newTestReaderCloser("{}"), nil
})
updater := createUpdaterWithEscapedCharacters(mr)
err := updater.BackgroundRun()
if err != nil {
t.Errorf("Error occurred: %#v", err)
}
}
func TestUpdateAvailable(t *testing.T) {
mr := &mockRequester{}
mr.handleRequest(
func(url string) (io.ReadCloser, error) {
equals(t, "http://updates.yourdomain.com/myapp/linux-amd64.json", url)
return newTestReaderCloser(`{
"Version": "2023-07-09-66c6c12",
"Sha256": "Q2vvTOW0p69A37StVANN+/ko1ZQDTElomq7fVcex/02="
}`), nil
})
updater := createUpdater(mr)
version, err := updater.UpdateAvailable()
if err != nil {
t.Errorf("Error occurred: %#v", err)
}
equals(t, "2023-07-09-66c6c12", version)
}
func createUpdater(mr *mockRequester) *Updater {
return &Updater{
CurrentVersion: "1.2",
ApiURL: "http://updates.yourdomain.com/",
BinURL: "http://updates.yourdownmain.com/",
DiffURL: "http://updates.yourdomain.com/",
Dir: "update/",
CmdName: "myapp", // app name
Requester: mr,
}
}
func createUpdaterWithEscapedCharacters(mr *mockRequester) *Updater {
return &Updater{
CurrentVersion: "1.2+foobar",
ApiURL: "http://updates.yourdomain.com/",
BinURL: "http://updates.yourdownmain.com/",
DiffURL: "http://updates.yourdomain.com/",
Dir: "update/",
CmdName: "myapp+foo", // app name
Requester: mr,
}
}
func equals(t *testing.T, expected, actual interface{}) {
if expected != actual {
t.Logf("Expected: %#v got %#v\n", expected, actual)
t.Fail()
}
}
type testReadCloser struct {
buffer *bytes.Buffer
}
func newTestReaderCloser(payload string) io.ReadCloser {
return &testReadCloser{buffer: bytes.NewBufferString(payload)}
}
func (trc *testReadCloser) Read(p []byte) (n int, err error) {
return trc.buffer.Read(p)
}
func (trc *testReadCloser) Close() error {
return nil
}
+2
View File
@@ -0,0 +1,2 @@
1.允许指定文件