mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-08-31 01:41:39 +08:00
feat:新增升级模块
This commit is contained in:
+51
-27
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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 @@
|
||||
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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Vendored
+1
-1
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package wafupdate
|
||||
|
||||
func hideFile(path string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user