Initial commit (code only without large binaries)
This commit is contained in:
302
EdgeAdmin/internal/apps/app_cmd.go
Normal file
302
EdgeAdmin/internal/apps/app_cmd.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
usages []string
|
||||
options []*CommandHelpOption
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
Code string
|
||||
Description string
|
||||
}
|
||||
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usages = append(this.usages, usage)
|
||||
return this
|
||||
}
|
||||
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
Description: description,
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// Print 打印
|
||||
func (this *AppCmd) Print() {
|
||||
fmt.Println(this.product + " v" + this.version)
|
||||
|
||||
fmt.Println("Usage:")
|
||||
for _, usage := range this.usages {
|
||||
fmt.Println(" " + usage)
|
||||
}
|
||||
|
||||
if len(this.options) > 0 {
|
||||
fmt.Println("")
|
||||
fmt.Println("Options:")
|
||||
|
||||
spaces := 20
|
||||
max := 40
|
||||
for _, option := range this.options {
|
||||
l := len(option.Code)
|
||||
if l < max && l > spaces {
|
||||
spaces = l + 4
|
||||
}
|
||||
}
|
||||
|
||||
for _, option := range this.options {
|
||||
if len(option.Code) > max {
|
||||
fmt.Println("")
|
||||
fmt.Println(" " + option.Code)
|
||||
option.Code = ""
|
||||
}
|
||||
|
||||
fmt.Printf(" %-"+strconv.Itoa(spaces)+"s%s\n", option.Code, ": "+option.Description)
|
||||
}
|
||||
}
|
||||
|
||||
if len(this.appendStrings) > 0 {
|
||||
fmt.Println("")
|
||||
for _, s := range this.appendStrings {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
Callback: callback,
|
||||
})
|
||||
}
|
||||
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "-v", "version", "-version", "--version":
|
||||
this.runVersion()
|
||||
return
|
||||
case "?", "help", "-help", "h", "-h":
|
||||
this.runHelp()
|
||||
return
|
||||
case "start":
|
||||
this.runStart()
|
||||
return
|
||||
case "stop":
|
||||
this.runStop()
|
||||
return
|
||||
case "restart":
|
||||
this.RunRestart()
|
||||
return
|
||||
case "status":
|
||||
this.runStatus()
|
||||
return
|
||||
}
|
||||
|
||||
// 查找指令
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == args[0] {
|
||||
directive.Callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("unknown command '" + args[0] + "'")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
// 运行主函数
|
||||
main()
|
||||
}
|
||||
|
||||
// 版本号
|
||||
func (this *AppCmd) runVersion() {
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
|
||||
}
|
||||
|
||||
// 帮助
|
||||
func (this *AppCmd) runHelp() {
|
||||
this.Print()
|
||||
}
|
||||
|
||||
// 启动
|
||||
func (this *AppCmd) runStart() {
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
var cmd = exec.Command(this.exe())
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Println(this.product+" start failed:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
// 停止
|
||||
func (this *AppCmd) runStop() {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// RunRestart 重启
|
||||
func (this *AppCmd) RunRestart() {
|
||||
this.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
this.runStart()
|
||||
}
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
func (this *AppCmd) exe() string {
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
exe = os.Args[0]
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
func (this *AppCmd) createSymLinks() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorList = []string{}
|
||||
|
||||
// bin
|
||||
{
|
||||
var target = "/usr/bin/" + teaconst.ProcessName
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != exe {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(exe, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log
|
||||
{
|
||||
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
|
||||
var target = "/var/log/" + teaconst.ProcessName + ".log"
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != realPath {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(realPath, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
return errors.New(strings.Join(errorList, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
EdgeAdmin/internal/apps/directive.go
Normal file
6
EdgeAdmin/internal/apps/directive.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package apps
|
||||
|
||||
type Directive struct {
|
||||
Arg string
|
||||
Callback func()
|
||||
}
|
||||
108
EdgeAdmin/internal/apps/log_writer.go
Normal file
108
EdgeAdmin/internal/apps/log_writer.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
fp *os.File
|
||||
c chan string
|
||||
}
|
||||
|
||||
func (this *LogWriter) Init() {
|
||||
// 创建目录
|
||||
var dir = files.NewFile(Tea.LogDir())
|
||||
if !dir.Exists() {
|
||||
err := dir.Mkdir()
|
||||
if err != nil {
|
||||
log.Println("[LOG]create log dir failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 打开要写入的日志文件
|
||||
var logPath = Tea.LogFile("run.log")
|
||||
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Println("[LOG]open log file failed: " + err.Error())
|
||||
} else {
|
||||
this.fp = fp
|
||||
}
|
||||
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
stat, err := fp.Stat()
|
||||
if err == nil {
|
||||
totalSize = stat.Size()
|
||||
}
|
||||
|
||||
for message := range this.c {
|
||||
totalSize += int64(len(message))
|
||||
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[LOG]write log failed: " + err.Error())
|
||||
} else {
|
||||
// 如果太大则Truncate
|
||||
if totalSize > maxFileSize {
|
||||
_ = fp.Truncate(0)
|
||||
totalSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *LogWriter) Write(message string) {
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
// 文件和行号
|
||||
var file string
|
||||
var line int
|
||||
if Tea.IsTesting() {
|
||||
var callDepth = 3
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(callDepth)
|
||||
if ok {
|
||||
file = this.packagePath(file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(file) > 0 {
|
||||
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
|
||||
} else {
|
||||
log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
this.c <- message
|
||||
}
|
||||
|
||||
func (this *LogWriter) Close() {
|
||||
if this.fp != nil {
|
||||
_ = this.fp.Close()
|
||||
}
|
||||
|
||||
close(this.c)
|
||||
}
|
||||
|
||||
func (this *LogWriter) packagePath(path string) string {
|
||||
var pieces = strings.Split(path, "/")
|
||||
if len(pieces) >= 2 {
|
||||
return strings.Join(pieces[len(pieces)-2:], "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
283
EdgeAdmin/internal/configloaders/admin_module.go
Normal file
283
EdgeAdmin/internal/configloaders/admin_module.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type AdminModuleCode = string
|
||||
|
||||
const (
|
||||
AdminModuleCodeDashboard AdminModuleCode = "dashboard" // 看板
|
||||
AdminModuleCodeServer AdminModuleCode = "server" // 网站
|
||||
AdminModuleCodeNode AdminModuleCode = "node" // 节点
|
||||
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
|
||||
AdminModuleCodeNS AdminModuleCode = "ns" // 域名服务
|
||||
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
|
||||
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
|
||||
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
|
||||
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
|
||||
AdminModuleCodeLog AdminModuleCode = "log" // 日志
|
||||
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
|
||||
AdminModuleCodeTicket AdminModuleCode = "ticket" // 工单
|
||||
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
|
||||
)
|
||||
|
||||
var sharedAdminModuleMapping = map[int64]*AdminModuleList{} // adminId => AdminModuleList
|
||||
|
||||
func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
|
||||
if len(sharedAdminModuleMapping) > 0 {
|
||||
return sharedAdminModuleMapping, nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modulesResp, err := rpcClient.AdminRPC().FindAllAdminModules(rpcClient.Context(0), &pb.FindAllAdminModulesRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapping := map[int64]*AdminModuleList{}
|
||||
for _, m := range modulesResp.AdminModules {
|
||||
list := &AdminModuleList{
|
||||
IsSuper: m.IsSuper,
|
||||
Fullname: m.Fullname,
|
||||
Theme: m.Theme,
|
||||
Lang: m.Lang,
|
||||
}
|
||||
|
||||
for _, pbModule := range m.Modules {
|
||||
list.Modules = append(list.Modules, &systemconfigs.AdminModule{
|
||||
Code: pbModule.Code,
|
||||
AllowAll: pbModule.AllowAll,
|
||||
Actions: pbModule.Actions,
|
||||
})
|
||||
}
|
||||
|
||||
mapping[m.AdminId] = list
|
||||
}
|
||||
|
||||
sharedAdminModuleMapping = mapping
|
||||
|
||||
return sharedAdminModuleMapping, nil
|
||||
}
|
||||
|
||||
func NotifyAdminModuleMappingChange() error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
sharedAdminModuleMapping = map[int64]*AdminModuleList{}
|
||||
_, err := loadAdminModuleMapping()
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckAdmin 检查用户是否存在
|
||||
func CheckAdmin(adminId int64) bool {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
// 如果还没有数据,则尝试加载
|
||||
if len(sharedAdminModuleMapping) == 0 {
|
||||
_, _ = loadAdminModuleMapping()
|
||||
}
|
||||
|
||||
_, ok := sharedAdminModuleMapping[adminId]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AllowModule 检查模块是否允许访问
|
||||
func AllowModule(adminId int64, module string) bool {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
if module == AdminModuleCodeCommon {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(sharedAdminModuleMapping) == 0 {
|
||||
_, _ = loadAdminModuleMapping()
|
||||
}
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Allow(module)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FindFirstAdminModule 获取管理员第一个可访问模块
|
||||
func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
list, ok2 := sharedAdminModuleMapping[adminId]
|
||||
if ok2 {
|
||||
if list.IsSuper {
|
||||
return AdminModuleCodeDashboard, true
|
||||
} else if len(list.Modules) > 0 {
|
||||
return list.Modules[0].Code, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindAdminFullname 查找某个管理员名称
|
||||
func FindAdminFullname(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Fullname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindAdminTheme 查找某个管理员选择的风格
|
||||
func FindAdminTheme(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Theme
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminTheme 设置某个管理员的风格
|
||||
func UpdateAdminTheme(adminId int64, theme string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
// FindAdminLang 查找某个管理员选择的语言
|
||||
func FindAdminLang(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Lang
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminLang 修改某个管理员选择的语言
|
||||
func UpdateAdminLang(adminId int64, langCode string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Lang = langCode
|
||||
}
|
||||
}
|
||||
|
||||
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var adminId = actionPtr.Object().Session().GetInt64(teaconst.SessionAdminId)
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
var result = ""
|
||||
if ok {
|
||||
result = list.Lang
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
result = langs.ParseLangFromAction(actionPtr)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AllModuleMaps 所有权限列表
|
||||
func AllModuleMaps(langCode string) []maps.Map {
|
||||
var m = []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Dashboard),
|
||||
"code": AdminModuleCodeDashboard,
|
||||
"url": "/dashboard",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Servers),
|
||||
"code": AdminModuleCodeServer,
|
||||
"url": "/servers",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Nodes),
|
||||
"code": AdminModuleCodeNode,
|
||||
"url": "/clusters",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_DNS),
|
||||
"code": AdminModuleCodeDNS,
|
||||
"url": "/dns",
|
||||
},
|
||||
}
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, maps.Map{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_NS),
|
||||
"code": AdminModuleCodeNS,
|
||||
"url": "/ns",
|
||||
})
|
||||
}
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Users),
|
||||
"code": AdminModuleCodeUser,
|
||||
"url": "/users",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Admins),
|
||||
"code": AdminModuleCodeAdmin,
|
||||
"url": "/admins",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Finance),
|
||||
"code": AdminModuleCodeFinance,
|
||||
"url": "/finance",
|
||||
},
|
||||
}...)
|
||||
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Plans),
|
||||
"code": AdminModuleCodePlan,
|
||||
"url": "/plans",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Tickets),
|
||||
"code": AdminModuleCodeTicket,
|
||||
"url": "/tickets",
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Logs),
|
||||
"code": AdminModuleCodeLog,
|
||||
"url": "/log",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Settings),
|
||||
"code": AdminModuleCodeSetting,
|
||||
"url": "/settings",
|
||||
},
|
||||
}...)
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package configloaders
|
||||
23
EdgeAdmin/internal/configloaders/admin_module_list.go
Normal file
23
EdgeAdmin/internal/configloaders/admin_module_list.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package configloaders
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
|
||||
type AdminModuleList struct {
|
||||
IsSuper bool
|
||||
Modules []*systemconfigs.AdminModule
|
||||
Fullname string
|
||||
Theme string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func (this *AdminModuleList) Allow(module string) bool {
|
||||
if this.IsSuper {
|
||||
return true
|
||||
}
|
||||
for _, m := range this.Modules {
|
||||
if m.Code == module {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
14
EdgeAdmin/internal/configloaders/admin_module_test.go
Normal file
14
EdgeAdmin/internal/configloaders/admin_module_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadAdminModuleMapping(t *testing.T) {
|
||||
m, err := loadAdminModuleMapping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logs.PrintAsJSON(m, t)
|
||||
}
|
||||
133
EdgeAdmin/internal/configloaders/admin_ui_config.go
Normal file
133
EdgeAdmin/internal/configloaders/admin_ui_config.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
|
||||
|
||||
func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadAdminUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.AdminUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func ReloadAdminUIConfig() error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
sharedAdminUIConfig = nil
|
||||
_, err := loadAdminUIConfig()
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedAdminUIConfig = uiConfig
|
||||
|
||||
// timezone
|
||||
updateTimeZone(uiConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowFinance 是否显示财务信息
|
||||
func ShowFinance() bool {
|
||||
config, _ := LoadAdminUIConfig()
|
||||
if config != nil && !config.ShowFinance {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
if sharedAdminUIConfig != nil {
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedAdminUIConfig = defaultAdminUIConfig()
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
var config = &systemconfigs.AdminUIConfig{}
|
||||
config.DNSResolver.Type = nodeconfigs.DNSResolverTypeDefault // 默认值
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedAdminUIConfig = defaultAdminUIConfig()
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
// timezone
|
||||
updateTimeZone(config)
|
||||
|
||||
sharedAdminUIConfig = config
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
|
||||
var config = &systemconfigs.AdminUIConfig{
|
||||
ProductName: langs.DefaultMessage(codes.AdminUI_DefaultProductName),
|
||||
AdminSystemName: langs.DefaultMessage(codes.AdminUI_DefaultSystemName),
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
DefaultPageSize: 10,
|
||||
TimeZone: nodeconfigs.DefaultTimeZoneLocation,
|
||||
}
|
||||
config.DNSResolver.Type = nodeconfigs.DNSResolverTypeDefault
|
||||
return config
|
||||
}
|
||||
|
||||
// 修改时区
|
||||
func updateTimeZone(config *systemconfigs.AdminUIConfig) {
|
||||
if len(config.TimeZone) > 0 {
|
||||
location, err := time.LoadLocation(config.TimeZone)
|
||||
if err == nil && time.Local != location {
|
||||
time.Local = location
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EdgeAdmin/internal/configloaders/admin_ui_config_test.go
Normal file
29
EdgeAdmin/internal/configloaders/admin_ui_config_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadUIConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
t.Logf("%p", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUIConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config)
|
||||
}
|
||||
}
|
||||
5
EdgeAdmin/internal/configloaders/locker.go
Normal file
5
EdgeAdmin/internal/configloaders/locker.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package configloaders
|
||||
|
||||
import "sync"
|
||||
|
||||
var locker sync.Mutex
|
||||
83
EdgeAdmin/internal/configloaders/log_config.go
Normal file
83
EdgeAdmin/internal/configloaders/log_config.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedLogConfig *systemconfigs.LogConfig = nil
|
||||
|
||||
const (
|
||||
LogSettingName = "adminLogConfig"
|
||||
)
|
||||
|
||||
func LoadLogConfig() (*systemconfigs.LogConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadLogConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.LogConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateLogConfig(logConfig *systemconfigs.LogConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(logConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: LogSettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedLogConfig = logConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLogConfig() (*systemconfigs.LogConfig, error) {
|
||||
if sharedLogConfig != nil {
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: LogSettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedLogConfig = systemconfigs.DefaultLogConfig()
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.LogConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[LOG_MANAGER]" + err.Error())
|
||||
sharedLogConfig = systemconfigs.DefaultLogConfig()
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
sharedLogConfig = config
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
118
EdgeAdmin/internal/configloaders/security_config.go
Normal file
118
EdgeAdmin/internal/configloaders/security_config.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
SecuritySettingName = "adminSecurityConfig"
|
||||
|
||||
FrameNone = ""
|
||||
FrameDeny = "DENY"
|
||||
FrameSameOrigin = "SAMEORIGIN"
|
||||
)
|
||||
|
||||
var sharedSecurityConfig *systemconfigs.SecurityConfig = nil
|
||||
|
||||
func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadSecurityConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateSecurityConfig(securityConfig *systemconfigs.SecurityConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(securityConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: SecuritySettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = securityConfig.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedSecurityConfig = securityConfig
|
||||
|
||||
// 通知更新
|
||||
events.Notify(events.EventSecurityConfigChanged)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
if sharedSecurityConfig != nil {
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: SecuritySettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedSecurityConfig = NewSecurityConfig()
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
var config = &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[SECURITY_MANAGER]" + err.Error())
|
||||
sharedSecurityConfig = NewSecurityConfig()
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecurityConfig = config
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
// NewSecurityConfig create new security config
|
||||
func NewSecurityConfig() *systemconfigs.SecurityConfig {
|
||||
return &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
}
|
||||
32
EdgeAdmin/internal/configloaders/security_config_test.go
Normal file
32
EdgeAdmin/internal/configloaders/security_config_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadSecurityConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadSecurityConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
t.Logf("%p", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSecurityConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadSecurityConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i == 0 {
|
||||
config.Frame = "DENY"
|
||||
}
|
||||
t.Log(config.Frame)
|
||||
}
|
||||
}
|
||||
79
EdgeAdmin/internal/configloaders/user_price_config_plus.go
Normal file
79
EdgeAdmin/internal/configloaders/user_price_config_plus.go
Normal file
@@ -0,0 +1,79 @@
|
||||
//go:build plus
|
||||
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserPriceConfig *userconfigs.UserPriceConfig = nil
|
||||
|
||||
func LoadUserPriceConfig() (*userconfigs.UserPriceConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserPriceConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(userconfigs.UserPriceConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func ResetUserPriceConfig(priceConfig *userconfigs.UserPriceConfig) {
|
||||
sharedUserPriceConfig = priceConfig
|
||||
}
|
||||
|
||||
func UpdateUserPriceConfig(priceConfig *userconfigs.UserPriceConfig) error {
|
||||
priceConfigJSON, err := json.Marshal(priceConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserPriceConfig,
|
||||
ValueJSON: priceConfigJSON,
|
||||
})
|
||||
sharedUserPriceConfig = priceConfig
|
||||
return err
|
||||
}
|
||||
|
||||
func loadUserPriceConfig() (*userconfigs.UserPriceConfig, error) {
|
||||
if sharedUserPriceConfig != nil {
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserPriceConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserPriceConfig = userconfigs.DefaultUserPriceConfig()
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
|
||||
var config = userconfigs.DefaultUserPriceConfig()
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("loadUserPriceConfig: " + err.Error())
|
||||
sharedUserPriceConfig = userconfigs.DefaultUserPriceConfig()
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
sharedUserPriceConfig = config
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
35
EdgeAdmin/internal/configloaders/user_sender_config_plus.go
Normal file
35
EdgeAdmin/internal/configloaders/user_sender_config_plus.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
)
|
||||
|
||||
// LoadUserSenderConfig 加载用户媒介发送配置
|
||||
func LoadUserSenderConfig() (*userconfigs.UserSenderConfig, error) {
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserSenderConfig})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data = configResp.ValueJSON
|
||||
if len(data) == 0 {
|
||||
return userconfigs.DefaultUserSenderConfig(), nil
|
||||
}
|
||||
|
||||
var config = userconfigs.DefaultUserSenderConfig()
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
81
EdgeAdmin/internal/configloaders/user_ui_config_plus.go
Normal file
81
EdgeAdmin/internal/configloaders/user_ui_config_plus.go
Normal file
@@ -0,0 +1,81 @@
|
||||
//go:build plus
|
||||
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
|
||||
|
||||
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserUIConfig,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedUserUIConfig = uiConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
if sharedUserUIConfig != nil {
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserUIConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserUIConfig = systemconfigs.NewUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
var config = systemconfigs.NewUserUIConfig()
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedUserUIConfig = systemconfigs.NewUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
sharedUserUIConfig = config
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
226
EdgeAdmin/internal/configs/api_config.go
Normal file
226
EdgeAdmin/internal/configs/api_config.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const ConfigFileName = "api_admin.yaml"
|
||||
const oldConfigFileName = "api.yaml"
|
||||
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
} `yaml:"rpc,omitempty"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
|
||||
NodeId string `yaml:"nodeId"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
// LoadAPIConfig 加载API配置
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
// 候选文件
|
||||
var realFile = Tea.ConfigFile(ConfigFileName)
|
||||
var oldRealFile = Tea.ConfigFile(oldConfigFileName)
|
||||
var isFromLocal = false
|
||||
var paths = []string{realFile, oldRealFile}
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/"+ConfigFileName)
|
||||
}
|
||||
paths = append(paths, "/etc/"+teaconst.ProcessName+"/"+ConfigFileName)
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
var isFromOld = false
|
||||
for _, path := range paths {
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
if path == realFile || path == oldRealFile {
|
||||
isFromLocal = true
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
isFromOld = path == oldRealFile
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, errors.New("init error: " + err.Error())
|
||||
}
|
||||
|
||||
if !isFromLocal {
|
||||
// 恢复文件
|
||||
_ = os.WriteFile(realFile, data, 0666)
|
||||
}
|
||||
|
||||
// 自动生成新配置文件
|
||||
if isFromOld {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ResetAPIConfig 重置配置
|
||||
func ResetAPIConfig() error {
|
||||
var filename = ConfigFileName
|
||||
|
||||
// 重置 configs/api_admin.yaml
|
||||
{
|
||||
var configFile = Tea.ConfigFile(filename)
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 ~/.edge-admin/api_admin.yaml
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 /etc/edge-admin/api_admin.yaml
|
||||
{
|
||||
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsNewInstalled() bool {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
|
||||
_, err = os.Stat(homeDir + "/." + teaconst.ProcessName + "/" + filename)
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteFile 写入API配置
|
||||
func (this *APIConfig) WriteFile(path string) error {
|
||||
data, err := yaml.Marshal(this)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
|
||||
// 写入 ~/.edge-admin/
|
||||
// 这个用来判断用户是否为重装,所以比较重要
|
||||
var filename = filepath.Base(path)
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
dir := homeDir + "/." + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入 /etc/edge-admin
|
||||
{
|
||||
var dir = "/etc/" + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone 克隆当前配置
|
||||
func (this *APIConfig) Clone() *APIConfig {
|
||||
return &APIConfig{
|
||||
NodeId: this.NodeId,
|
||||
Secret: this.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *APIConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
if len(this.RPCEndpoints) == 0 {
|
||||
return errors.New("no valid 'rpc.endpoints'")
|
||||
}
|
||||
|
||||
if len(this.NodeId) == 0 {
|
||||
return errors.New("'nodeId' required")
|
||||
}
|
||||
if len(this.Secret) == 0 {
|
||||
return errors.New("'secret' required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
30
EdgeAdmin/internal/configs/api_config_test.go
Normal file
30
EdgeAdmin/internal/configs/api_config_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadAPIConfig(t *testing.T) {
|
||||
config, err := LoadAPIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
|
||||
func TestAPIConfig_WriteFile(t *testing.T) {
|
||||
var config = &APIConfig{}
|
||||
err := config.WriteFile("/tmp/api_config.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
6
EdgeAdmin/internal/configs/nodes/node_config.go
Normal file
6
EdgeAdmin/internal/configs/nodes/node_config.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package nodes
|
||||
|
||||
type NodeConfig struct {
|
||||
Id string `json:"id" yaml:"id"`
|
||||
Secret string `json:"secret" yaml:"secret"`
|
||||
}
|
||||
44
EdgeAdmin/internal/configs/plus_config_plus.go
Normal file
44
EdgeAdmin/internal/configs/plus_config_plus.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package configs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
)
|
||||
|
||||
var plusConfigFile = "plus.cache.json"
|
||||
|
||||
type PlusConfig struct {
|
||||
IsPlus bool `json:"isPlus"`
|
||||
Edition string `json:"edition"`
|
||||
Components []string `json:"components"`
|
||||
DayTo string `json:"dayTo"`
|
||||
}
|
||||
|
||||
func ReadPlusConfig() *PlusConfig {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(plusConfigFile))
|
||||
if err != nil {
|
||||
return &PlusConfig{IsPlus: false}
|
||||
}
|
||||
var config = &PlusConfig{IsPlus: false}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return config
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func WritePlusConfig(config *PlusConfig) error {
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(Tea.ConfigFile(plusConfigFile), configJSON, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
3
EdgeAdmin/internal/configs/secret.go
Normal file
3
EdgeAdmin/internal/configs/secret.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package configs
|
||||
|
||||
var Secret = ""
|
||||
51
EdgeAdmin/internal/configs/simple_db_config.go
Normal file
51
EdgeAdmin/internal/configs/simple_db_config.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package configs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SimpleDBConfig struct {
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
Database string `yaml:"database"`
|
||||
Host string `yaml:"host"`
|
||||
BoolFields []string `yaml:"boolFields,omitempty"`
|
||||
}
|
||||
|
||||
func (this *SimpleDBConfig) GenerateOldConfig(targetFile string) error {
|
||||
var dbConfig = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: url.QueryEscape(this.User) + ":" + url.QueryEscape(this.Password) + "@tcp(" + this.Host + ")/" + url.PathEscape(this.Database) + "?charset=utf8mb4&timeout=30s&multiStatements=true",
|
||||
Prefix: "edge",
|
||||
}
|
||||
dbConfig.Models.Package = "internal/db/models"
|
||||
|
||||
var config = &dbs.Config{
|
||||
DBs: map[string]*dbs.DBConfig{
|
||||
Tea.Env: dbConfig,
|
||||
},
|
||||
}
|
||||
config.Default.DB = Tea.Env
|
||||
config.Fields = map[string][]string{
|
||||
"bool": this.BoolFields,
|
||||
}
|
||||
|
||||
oldConfigYAML, encodeErr := yaml.Marshal(config)
|
||||
if encodeErr != nil {
|
||||
return encodeErr
|
||||
}
|
||||
|
||||
err := os.WriteFile(targetFile, oldConfigYAML, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create database config file failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
8
EdgeAdmin/internal/const/build.go
Normal file
8
EdgeAdmin/internal/const/build.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = true
|
||||
const BuildPlus = false
|
||||
const Tag = "community"
|
||||
8
EdgeAdmin/internal/const/build_plus.go
Normal file
8
EdgeAdmin/internal/const/build_plus.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = false
|
||||
const BuildPlus = true
|
||||
const Tag = "plus"
|
||||
21
EdgeAdmin/internal/const/const.go
Normal file
21
EdgeAdmin/internal/const/const.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.7" //1.3.9
|
||||
|
||||
APINodeVersion = "1.4.7" //1.3.9
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
Role = "admin"
|
||||
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
CookieSID = "geadsid"
|
||||
SessionAdminId = "adminId"
|
||||
|
||||
SystemdServiceName = "edge-admin"
|
||||
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}&version=${version}"
|
||||
)
|
||||
|
||||
6
EdgeAdmin/internal/const/plus.go
Normal file
6
EdgeAdmin/internal/const/plus.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
var IsPlus = false
|
||||
6
EdgeAdmin/internal/const/plus_plus.go
Normal file
6
EdgeAdmin/internal/const/plus_plus.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package teaconst
|
||||
|
||||
var IsPlus = false
|
||||
32
EdgeAdmin/internal/const/vars.go
Normal file
32
EdgeAdmin/internal/const/vars.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package teaconst
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
IsRecoverMode = false
|
||||
|
||||
IsDemoMode = false
|
||||
ErrorDemoOperation = "DEMO模式下无法进行创建、修改、删除等操作"
|
||||
|
||||
NewVersionCode = "" // 有新的版本
|
||||
NewVersionDownloadURL = "" // 新版本下载地址
|
||||
|
||||
IsMain = checkMain()
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
func checkMain() bool {
|
||||
if len(os.Args) == 1 ||
|
||||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
|
||||
return true
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
return strings.HasSuffix(exe, ".test") ||
|
||||
strings.HasSuffix(exe, ".test.exe") ||
|
||||
strings.Contains(exe, "___")
|
||||
}
|
||||
58
EdgeAdmin/internal/csrf/token_manager.go
Normal file
58
EdgeAdmin/internal/csrf/token_manager.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedTokenManager = NewTokenManager()
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
for range ticker.C {
|
||||
sharedTokenManager.Clean()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type TokenManager struct {
|
||||
tokenMap map[string]int64 // token => timestamp
|
||||
|
||||
locker sync.Mutex
|
||||
}
|
||||
|
||||
func NewTokenManager() *TokenManager {
|
||||
return &TokenManager{
|
||||
tokenMap: map[string]int64{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TokenManager) Put(token string) {
|
||||
this.locker.Lock()
|
||||
this.tokenMap[token] = time.Now().Unix()
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *TokenManager) Exists(token string) bool {
|
||||
this.locker.Lock()
|
||||
_, ok := this.tokenMap[token]
|
||||
this.locker.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *TokenManager) Delete(token string) {
|
||||
this.locker.Lock()
|
||||
delete(this.tokenMap, token)
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *TokenManager) Clean() {
|
||||
this.locker.Lock()
|
||||
for token, timestamp := range this.tokenMap {
|
||||
if time.Now().Unix()-timestamp > 3600 { // 删除一个小时前的
|
||||
delete(this.tokenMap, token)
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
66
EdgeAdmin/internal/csrf/utils.go
Normal file
66
EdgeAdmin/internal/csrf/utils.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generate 生成Token
|
||||
func Generate() string {
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(configs.Secret))
|
||||
h.Write([]byte(timestamp))
|
||||
s := h.Sum(nil)
|
||||
token := base64.StdEncoding.EncodeToString([]byte(timestamp + fmt.Sprintf("%x", s)))
|
||||
sharedTokenManager.Put(token)
|
||||
return token
|
||||
}
|
||||
|
||||
// Validate 校验Token
|
||||
func Validate(token string) (b bool) {
|
||||
if len(token) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !sharedTokenManager.Exists(token) {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
sharedTokenManager.Delete(token)
|
||||
}()
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hashString := string(data)
|
||||
if len(hashString) < 10+32 {
|
||||
return
|
||||
}
|
||||
|
||||
timestampString := hashString[:10]
|
||||
hashString = hashString[10:]
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(configs.Secret))
|
||||
h.Write([]byte(timestampString))
|
||||
hashData := h.Sum(nil)
|
||||
if hashString != fmt.Sprintf("%x", hashData) {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := types.Int64(timestampString)
|
||||
if timestamp < time.Now().Unix()-1800 { // 有效期半个小时
|
||||
return
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
41
EdgeAdmin/internal/encrypt/magic_key.go
Normal file
41
EdgeAdmin/internal/encrypt/magic_key.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
MagicKey = "f1c8eafb543f03023e97b7be864a4e9b"
|
||||
)
|
||||
|
||||
// 加密特殊信息
|
||||
func MagicKeyEncode(data []byte) []byte {
|
||||
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
|
||||
dst, err := method.Encrypt(data)
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// 解密特殊信息
|
||||
func MagicKeyDecode(data []byte) []byte {
|
||||
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
|
||||
src, err := method.Decrypt(data)
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
return src
|
||||
}
|
||||
11
EdgeAdmin/internal/encrypt/magic_key_test.go
Normal file
11
EdgeAdmin/internal/encrypt/magic_key_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMagicKeyEncode(t *testing.T) {
|
||||
dst := MagicKeyEncode([]byte("Hello,World"))
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src := MagicKeyDecode(dst)
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
12
EdgeAdmin/internal/encrypt/method.go
Normal file
12
EdgeAdmin/internal/encrypt/method.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package encrypt
|
||||
|
||||
type MethodInterface interface {
|
||||
// 初始化
|
||||
Init(key []byte, iv []byte) error
|
||||
|
||||
// 加密
|
||||
Encrypt(src []byte) (dst []byte, err error)
|
||||
|
||||
// 解密
|
||||
Decrypt(dst []byte) (src []byte, err error)
|
||||
}
|
||||
73
EdgeAdmin/internal/encrypt/method_aes_128_cfb.go
Normal file
73
EdgeAdmin/internal/encrypt/method_aes_128_cfb.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES128CFBMethod struct {
|
||||
iv []byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
func (this *AES128CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为32长度
|
||||
l := len(key)
|
||||
if l > 16 {
|
||||
key = key[:16]
|
||||
} else if l < 16 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 16-l)...)
|
||||
}
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
|
||||
this.iv = iv
|
||||
|
||||
// block
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES128CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES128CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
encrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
90
EdgeAdmin/internal/encrypt/method_aes_128_cfb_test.go
Normal file
90
EdgeAdmin/internal/encrypt/method_aes_128_cfb_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAES128CFBMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
|
||||
func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sources := [][]byte{}
|
||||
|
||||
{
|
||||
a := []byte{1}
|
||||
_, err = method.Encrypt(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
src := []byte(strings.Repeat("Hello", 1))
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sources = append(sources, dst)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
a := []byte{1}
|
||||
_, err = method.Decrypt(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, dst := range sources {
|
||||
dst2 := append([]byte{}, dst...)
|
||||
src2, err := method.Decrypt(dst2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(src2))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAES128CFBMethod_Encrypt(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
src := []byte(strings.Repeat("Hello", 1024))
|
||||
for i := 0; i < b.N; i++ {
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = dst
|
||||
}
|
||||
}
|
||||
74
EdgeAdmin/internal/encrypt/method_aes_192_cfb.go
Normal file
74
EdgeAdmin/internal/encrypt/method_aes_192_cfb.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES192CFBMethod struct {
|
||||
block cipher.Block
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (this *AES192CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为24长度
|
||||
l := len(key)
|
||||
if l > 24 {
|
||||
key = key[:24]
|
||||
} else if l < 24 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 24-l)...)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
|
||||
this.iv = iv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES192CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES192CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
|
||||
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
decrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
45
EdgeAdmin/internal/encrypt/method_aes_192_cfb_test.go
Normal file
45
EdgeAdmin/internal/encrypt/method_aes_192_cfb_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAES192CFBMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
|
||||
func BenchmarkAES192CFBMethod_Encrypt(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
src := []byte(strings.Repeat("Hello", 1024))
|
||||
for i := 0; i < b.N; i++ {
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = dst
|
||||
}
|
||||
}
|
||||
72
EdgeAdmin/internal/encrypt/method_aes_256_cfb.go
Normal file
72
EdgeAdmin/internal/encrypt/method_aes_256_cfb.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES256CFBMethod struct {
|
||||
block cipher.Block
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为32长度
|
||||
l := len(key)
|
||||
if l > 32 {
|
||||
key = key[:32]
|
||||
} else if l < 32 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 32-l)...)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
this.iv = iv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
decrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
42
EdgeAdmin/internal/encrypt/method_aes_256_cfb_test.go
Normal file
42
EdgeAdmin/internal/encrypt/method_aes_256_cfb_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAES256CFBMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
|
||||
func TestAES256CFBMethod_Encrypt2(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
26
EdgeAdmin/internal/encrypt/method_raw.go
Normal file
26
EdgeAdmin/internal/encrypt/method_raw.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package encrypt
|
||||
|
||||
type RawMethod struct {
|
||||
}
|
||||
|
||||
func (this *RawMethod) Init(key, iv []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RawMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
dst = make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RawMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
src = make([]byte, len(dst))
|
||||
copy(src, dst)
|
||||
return
|
||||
}
|
||||
23
EdgeAdmin/internal/encrypt/method_raw_test.go
Normal file
23
EdgeAdmin/internal/encrypt/method_raw_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRawMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("raw", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
43
EdgeAdmin/internal/encrypt/method_utils.go
Normal file
43
EdgeAdmin/internal/encrypt/method_utils.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var methods = map[string]reflect.Type{
|
||||
"raw": reflect.TypeOf(new(RawMethod)).Elem(),
|
||||
"aes-128-cfb": reflect.TypeOf(new(AES128CFBMethod)).Elem(),
|
||||
"aes-192-cfb": reflect.TypeOf(new(AES192CFBMethod)).Elem(),
|
||||
"aes-256-cfb": reflect.TypeOf(new(AES256CFBMethod)).Elem(),
|
||||
}
|
||||
|
||||
func NewMethodInstance(method string, key string, iv string) (MethodInterface, error) {
|
||||
valueType, ok := methods[method]
|
||||
if !ok {
|
||||
return nil, errors.New("method '" + method + "' not found")
|
||||
}
|
||||
instance, ok := reflect.New(valueType).Interface().(MethodInterface)
|
||||
if !ok {
|
||||
return nil, errors.New("method '" + method + "' must implement MethodInterface")
|
||||
}
|
||||
err := instance.Init([]byte(key), []byte(iv))
|
||||
return instance, err
|
||||
}
|
||||
|
||||
func RecoverMethodPanic(err interface{}) error {
|
||||
if err != nil {
|
||||
s, ok := err.(string)
|
||||
if ok {
|
||||
return errors.New(s)
|
||||
}
|
||||
|
||||
e, ok := err.(error)
|
||||
if ok {
|
||||
return e
|
||||
}
|
||||
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
EdgeAdmin/internal/encrypt/method_utils_test.go
Normal file
8
EdgeAdmin/internal/encrypt/method_utils_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFindMethodInstance(t *testing.T) {
|
||||
t.Log(NewMethodInstance("a", "b", ""))
|
||||
t.Log(NewMethodInstance("aes-256-cfb", "123456", ""))
|
||||
}
|
||||
56
EdgeAdmin/internal/errors/error.go
Normal file
56
EdgeAdmin/internal/errors/error.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type errorObj struct {
|
||||
err error
|
||||
file string
|
||||
line int
|
||||
funcName string
|
||||
}
|
||||
|
||||
func (this *errorObj) Error() string {
|
||||
s := this.err.Error() + "\n " + this.file
|
||||
if len(this.funcName) > 0 {
|
||||
s += ":" + this.funcName + "()"
|
||||
}
|
||||
s += ":" + strconv.Itoa(this.line)
|
||||
return s
|
||||
}
|
||||
|
||||
// 新错误
|
||||
func New(errText string) error {
|
||||
ptr, file, line, ok := runtime.Caller(1)
|
||||
funcName := ""
|
||||
if ok {
|
||||
frame, _ := runtime.CallersFrames([]uintptr{ptr}).Next()
|
||||
funcName = filepath.Base(frame.Function)
|
||||
}
|
||||
return &errorObj{
|
||||
err: errors.New(errText),
|
||||
file: file,
|
||||
line: line,
|
||||
funcName: funcName,
|
||||
}
|
||||
}
|
||||
|
||||
// 包装已有错误
|
||||
func Wrap(err error) error {
|
||||
ptr, file, line, ok := runtime.Caller(1)
|
||||
funcName := ""
|
||||
if ok {
|
||||
frame, _ := runtime.CallersFrames([]uintptr{ptr}).Next()
|
||||
funcName = filepath.Base(frame.Function)
|
||||
}
|
||||
return &errorObj{
|
||||
err: err,
|
||||
file: file,
|
||||
line: line,
|
||||
funcName: funcName,
|
||||
}
|
||||
}
|
||||
22
EdgeAdmin/internal/errors/error_test.go
Normal file
22
EdgeAdmin/internal/errors/error_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Log(New("hello"))
|
||||
t.Log(Wrap(errors.New("hello")))
|
||||
t.Log(testError1())
|
||||
t.Log(Wrap(testError1()))
|
||||
t.Log(Wrap(testError2()))
|
||||
}
|
||||
|
||||
func testError1() error {
|
||||
return New("test error1")
|
||||
}
|
||||
|
||||
func testError2() error {
|
||||
return Wrap(testError1())
|
||||
}
|
||||
10
EdgeAdmin/internal/events/events.go
Normal file
10
EdgeAdmin/internal/events/events.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package events
|
||||
|
||||
type Event = string
|
||||
|
||||
const (
|
||||
EventStart Event = "start" // start loading
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
|
||||
EventSecurityConfigChanged Event = "securityConfigChanged" // 安全设置变更
|
||||
)
|
||||
27
EdgeAdmin/internal/events/utils.go
Normal file
27
EdgeAdmin/internal/events/utils.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package events
|
||||
|
||||
import "sync"
|
||||
|
||||
var eventsMap = map[string][]func(){} // event => []callbacks
|
||||
var locker = sync.Mutex{}
|
||||
|
||||
// On 增加事件回调
|
||||
func On(event string, callback func()) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var callbacks = eventsMap[event]
|
||||
callbacks = append(callbacks, callback)
|
||||
eventsMap[event] = callbacks
|
||||
}
|
||||
|
||||
// Notify 通知事件
|
||||
func Notify(event string) {
|
||||
locker.Lock()
|
||||
var callbacks = eventsMap[event]
|
||||
locker.Unlock()
|
||||
|
||||
for _, callback := range callbacks {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
16
EdgeAdmin/internal/events/utils_test.go
Normal file
16
EdgeAdmin/internal/events/utils_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package events
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOn(t *testing.T) {
|
||||
On("hello", func() {
|
||||
t.Log("world")
|
||||
})
|
||||
On("hello", func() {
|
||||
t.Log("world2")
|
||||
})
|
||||
On("hello2", func() {
|
||||
t.Log("world2")
|
||||
})
|
||||
Notify("hello")
|
||||
}
|
||||
170
EdgeAdmin/internal/gen/generate.go
Normal file
170
EdgeAdmin/internal/gen/generate.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Generate() error {
|
||||
err := generateComponentsJSFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate 'components.src.js' failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成Javascript文件
|
||||
func generateComponentsJSFile() error {
|
||||
var buffer = bytes.NewBuffer([]byte{})
|
||||
|
||||
var webRoot string
|
||||
if Tea.IsTesting() {
|
||||
webRoot = Tea.Root + "/../web/public/js/components/"
|
||||
} else {
|
||||
webRoot = Tea.Root + "/web/public/js/components/"
|
||||
}
|
||||
var f = files.NewFile(webRoot)
|
||||
|
||||
f.Range(func(file *files.File) {
|
||||
if !file.IsFile() {
|
||||
return
|
||||
}
|
||||
if file.Ext() != ".js" {
|
||||
return
|
||||
}
|
||||
data, err := file.ReadAll()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
buffer.Write(data)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
})
|
||||
|
||||
// 条件组件
|
||||
typesJSON, err := json.Marshal(condutils.ReadAllAvailableCondTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request cond types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
|
||||
buffer.Write(typesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 条件操作符
|
||||
requestOperatorsJSON, err := json.Marshal(shared.AllRequestOperators())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
|
||||
buffer.Write(requestOperatorsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 请求变量
|
||||
requestVariablesJSON, err := json.Marshal(shared.DefaultRequestVariables())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request variables failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_VARIABLES = ")
|
||||
buffer.Write(requestVariablesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 指标
|
||||
metricHTTPKeysJSON, err := json.Marshal(serverconfigs.FindAllMetricKeyDefinitions(serverconfigs.MetricItemCategoryHTTP))
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal metric http keys failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
|
||||
buffer.Write(metricHTTPKeysJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值项目
|
||||
ipAddrThresholdItemsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdItems())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold items failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
|
||||
buffer.Write(ipAddrThresholdItemsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值动作
|
||||
ipAddrThresholdActionsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdActions())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold actions failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
|
||||
buffer.Write(ipAddrThresholdActionsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF checkpoints
|
||||
var wafCheckpointsMaps = []maps.Map{}
|
||||
for _, checkpoint := range firewallconfigs.AllCheckpoints {
|
||||
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
|
||||
"name": checkpoint.Name,
|
||||
"prefix": checkpoint.Prefix,
|
||||
"description": checkpoint.Description,
|
||||
})
|
||||
}
|
||||
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
|
||||
buffer.Write(wafCheckpointsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF操作符
|
||||
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
|
||||
buffer.Write(wafOperatorsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF验证码类型
|
||||
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
|
||||
buffer.Write(captchaTypesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fp, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
13
EdgeAdmin/internal/gen/generate_test.go
Normal file
13
EdgeAdmin/internal/gen/generate_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
err := Generate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
12
EdgeAdmin/internal/goman/instance.go
Normal file
12
EdgeAdmin/internal/goman/instance.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import "time"
|
||||
|
||||
type Instance struct {
|
||||
Id uint64
|
||||
CreatedTime time.Time
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
81
EdgeAdmin/internal/goman/lib.go
Normal file
81
EdgeAdmin/internal/goman/lib.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var locker = &sync.Mutex{}
|
||||
var instanceMap = map[uint64]*Instance{} // id => *Instance
|
||||
var instanceId = uint64(0)
|
||||
|
||||
// New 新创建goroutine
|
||||
func New(f func()) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f()
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// NewWithArgs 创建带有参数的goroutine
|
||||
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f(args...)
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// List 列出所有正在运行goroutine
|
||||
func List() []*Instance {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var result = []*Instance{}
|
||||
for _, instance := range instanceMap {
|
||||
result = append(result, instance)
|
||||
}
|
||||
return result
|
||||
}
|
||||
28
EdgeAdmin/internal/goman/lib_test.go
Normal file
28
EdgeAdmin/internal/goman/lib_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
476
EdgeAdmin/internal/nodes/admin_node.go
Normal file
476
EdgeAdmin/internal/nodes/admin_node.go
Normal file
@@ -0,0 +1,476 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/iwind/TeaGo"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var SharedAdminNode *AdminNode = nil
|
||||
|
||||
type AdminNode struct {
|
||||
sock *gosock.Sock
|
||||
subPIDs []int
|
||||
}
|
||||
|
||||
func NewAdminNode() *AdminNode {
|
||||
return &AdminNode{}
|
||||
}
|
||||
|
||||
func (this *AdminNode) Run() {
|
||||
SharedAdminNode = this
|
||||
|
||||
// 启动管理界面
|
||||
var secret = this.genSecret()
|
||||
configs.Secret = secret
|
||||
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 检查server配置
|
||||
err = this.checkServer()
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
logs.Println("[NODE]" + err.Error())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
this.addPortsToFirewall()
|
||||
|
||||
// 监听信号
|
||||
var sigQueue = make(chan os.Signal, 8)
|
||||
signal.Notify(sigQueue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
|
||||
go func() {
|
||||
for range sigQueue {
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
|
||||
// 触发事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
// 启动API节点
|
||||
this.startAPINode()
|
||||
|
||||
// 设置DNS相关
|
||||
this.setupDNS()
|
||||
|
||||
// 启动IP库
|
||||
this.startIPLibrary()
|
||||
|
||||
// 启动Web服务
|
||||
sessionManager, err := NewSessionManager()
|
||||
if err != nil {
|
||||
log.Fatal("start session failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
TeaGo.NewServer(false).
|
||||
AccessLog(false).
|
||||
EndAll().
|
||||
Session(sessionManager, teaconst.CookieSID).
|
||||
ReadHeaderTimeout(3 * time.Second).
|
||||
ReadTimeout(1200 * time.Second).
|
||||
Start()
|
||||
}
|
||||
|
||||
// Daemon 实现守护进程
|
||||
func (this *AdminNode) Daemon() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
var isDebug = lists.ContainsString(os.Args, "debug")
|
||||
for {
|
||||
conn, err := sock.Dial()
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
}
|
||||
|
||||
// 尝试启动
|
||||
err = func() error {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(exe)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]", err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InstallSystemService 安装系统服务
|
||||
func (this *AdminNode) InstallSystemService() error {
|
||||
shortName := teaconst.SystemdServiceName
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
|
||||
err = manager.Install(exe, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSubPID 添加子PID
|
||||
func (this *AdminNode) AddSubPID(pid int) {
|
||||
this.subPIDs = append(this.subPIDs, pid)
|
||||
}
|
||||
|
||||
// 检查Server配置
|
||||
func (this *AdminNode) checkServer() error {
|
||||
configFile := Tea.ConfigFile("server.yaml")
|
||||
_, err := os.Stat(configFile)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
// 创建文件
|
||||
templateFile := Tea.ConfigFile("server.template.yaml")
|
||||
data, err := os.ReadFile(templateFile)
|
||||
if err == nil {
|
||||
err = os.WriteFile(configFile, data, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create config file failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
templateYAML := `# environment code
|
||||
env: prod
|
||||
|
||||
# http
|
||||
http:
|
||||
"on": true
|
||||
listen: [ "0.0.0.0:7788" ]
|
||||
|
||||
# https
|
||||
https:
|
||||
"on": false
|
||||
listen: [ "0.0.0.0:443"]
|
||||
cert: ""
|
||||
key: ""
|
||||
`
|
||||
err = os.WriteFile(configFile, []byte(templateYAML), 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create config file failed: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("can not read config from 'configs/server.yaml': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
func (this *AdminNode) addPortsToFirewall() {
|
||||
var configFile = Tea.ConfigFile("server.yaml")
|
||||
data, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var config = &TeaGo.ServerConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ports = []int{}
|
||||
if config.Http.On {
|
||||
for _, listen := range config.Http.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Https.On {
|
||||
for _, listen := range config.Https.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.AddPortsToFirewall(ports)
|
||||
}
|
||||
|
||||
// 设置DNS相关
|
||||
func (this *AdminNode) setupDNS() {
|
||||
config, loadErr := configloaders.LoadAdminUIConfig()
|
||||
if loadErr != nil {
|
||||
// 默认使用go原生
|
||||
err := os.Setenv("GODEBUG", "netdns=go")
|
||||
if err != nil {
|
||||
logs.Println("[DNS_RESOLVER]set env failed: " + err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
switch config.DNSResolver.Type {
|
||||
case nodeconfigs.DNSResolverTypeGoNative:
|
||||
err = os.Setenv("GODEBUG", "netdns=go")
|
||||
case nodeconfigs.DNSResolverTypeCGO:
|
||||
err = os.Setenv("GODEBUG", "netdns=cgo")
|
||||
default:
|
||||
// 默认使用go原生
|
||||
err = os.Setenv("GODEBUG", "netdns=go")
|
||||
}
|
||||
if err != nil {
|
||||
logs.Println("[DNS_RESOLVER]set env failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 启动API节点
|
||||
func (this *AdminNode) startAPINode() {
|
||||
var configPath = Tea.Root + "/edge-api/configs/api.yaml"
|
||||
_, err := os.Stat(configPath)
|
||||
canStart := false
|
||||
if err == nil {
|
||||
canStart = true
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
// 尝试恢复api.yaml
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
paths := []string{}
|
||||
if len(homeDir) > 0 {
|
||||
paths = append(paths, homeDir+"/.edge-api/api.yaml")
|
||||
}
|
||||
paths = append(paths, "/etc/edge-api/api.yaml")
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = os.WriteFile(configPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/api.yaml' from '" + path + "'")
|
||||
canStart = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbPath := Tea.Root + "/edge-api/configs/db.yaml"
|
||||
_, err = os.Stat(dbPath)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// 尝试恢复db.yaml
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
paths := []string{}
|
||||
if len(homeDir) > 0 {
|
||||
paths = append(paths, homeDir+"/.edge-api/db.yaml")
|
||||
}
|
||||
paths = append(paths, "/etc/edge-api/db.yaml")
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = os.WriteFile(dbPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/db.yaml' from '" + path + "'")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if canStart {
|
||||
logs.Println("start edge-api")
|
||||
cmd := exec.Command(Tea.Root + "/edge-api/bin/edge-api")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
logs.Println("[ERROR]start edge-api failed: " + err.Error())
|
||||
} else {
|
||||
this.subPIDs = append(this.subPIDs, cmd.Process.Pid)
|
||||
// 在后台 goroutine 中等待子进程退出,避免僵尸进程
|
||||
go func() {
|
||||
waitErr := cmd.Wait()
|
||||
if waitErr != nil {
|
||||
logs.Println("[NODE]edge-api process exited with error: " + waitErr.Error())
|
||||
} else {
|
||||
logs.Println("[NODE]edge-api process exited normally")
|
||||
}
|
||||
// 从 subPIDs 中移除已退出的进程
|
||||
for i, pid := range this.subPIDs {
|
||||
if pid == cmd.Process.Pid {
|
||||
this.subPIDs = append(this.subPIDs[:i], this.subPIDs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成Secret
|
||||
func (this *AdminNode) genSecret() string {
|
||||
tmpFile := os.TempDir() + "/edge-admin-secret.tmp"
|
||||
data, err := os.ReadFile(tmpFile)
|
||||
if err == nil && len(data) == 32 {
|
||||
return string(data)
|
||||
}
|
||||
secret := rands.String(32)
|
||||
_ = os.WriteFile(tmpFile, []byte(secret), 0666)
|
||||
return secret
|
||||
}
|
||||
|
||||
// 监听本地sock
|
||||
func (this *AdminNode) listenSock() error {
|
||||
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 关闭子进程
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// 停止当前目录下的API节点
|
||||
var apiSock = gosock.NewTmpSock("edge-api")
|
||||
apiReply, err := apiSock.Send(&gosock.Command{Code: "info"})
|
||||
if err == nil {
|
||||
adminExe, _ := os.Executable()
|
||||
if len(adminExe) > 0 && apiReply != nil && strings.HasPrefix(maps.NewMap(apiReply.Params).GetString("path"), filepath.Dir(filepath.Dir(adminExe))) {
|
||||
_, _ = apiSock.Send(&gosock.Command{Code: "stop"})
|
||||
}
|
||||
}
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
case "recover":
|
||||
teaconst.IsRecoverMode = true
|
||||
_ = cmd.ReplyOk()
|
||||
case "demo":
|
||||
teaconst.IsDemoMode = !teaconst.IsDemoMode
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode},
|
||||
})
|
||||
case "info":
|
||||
exePath, _ := os.Executable()
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "info",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
"version": teaconst.Version,
|
||||
"path": exePath,
|
||||
},
|
||||
})
|
||||
case "dev": // 切换到dev
|
||||
Tea.Env = Tea.EnvDev
|
||||
_ = cmd.ReplyOk()
|
||||
case "prod": // 切换到prod
|
||||
Tea.Env = Tea.EnvProd
|
||||
_ = cmd.ReplyOk()
|
||||
case "security.reset":
|
||||
var newConfig = configloaders.NewSecurityConfig()
|
||||
_ = configloaders.UpdateSecurityConfig(newConfig)
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
18
EdgeAdmin/internal/nodes/admin_node_ext.go
Normal file
18
EdgeAdmin/internal/nodes/admin_node_ext.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
// 启动IP库
|
||||
func (this *AdminNode) startIPLibrary() {
|
||||
logs.Println("[NODE]initializing ip library ...")
|
||||
err := iplibrary.InitDefault()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
18
EdgeAdmin/internal/nodes/admin_node_ext_plus.go
Normal file
18
EdgeAdmin/internal/nodes/admin_node_ext_plus.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
// 启动IP库
|
||||
func (this *AdminNode) startIPLibrary() {
|
||||
logs.Println("[NODE]initializing ip library ...")
|
||||
err := iplibrary.InitDefault()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
118
EdgeAdmin/internal/nodes/session_manager.go
Normal file
118
EdgeAdmin/internal/nodes/session_manager.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionManager SESSION管理
|
||||
type SessionManager struct {
|
||||
life uint
|
||||
}
|
||||
|
||||
func NewSessionManager() (*SessionManager, error) {
|
||||
return &SessionManager{}, nil
|
||||
}
|
||||
|
||||
func (this *SessionManager) Init(config *actions.SessionConfig) {
|
||||
this.life = config.Life
|
||||
}
|
||||
|
||||
func (this *SessionManager) Read(sid string) map[string]string {
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
var result = map[string]string{}
|
||||
|
||||
var cacheKey = "SESSION@" + sid
|
||||
var item = ttlcache.DefaultCache.Read(cacheKey)
|
||||
if item != nil && item.Value != nil {
|
||||
itemMap, ok := item.Value.(map[string]string)
|
||||
if ok {
|
||||
return itemMap
|
||||
}
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
|
||||
result["@error"] = err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
var session = resp.LoginSession
|
||||
if session == nil || len(session.ValuesJSON) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
err = json.Unmarshal(session.ValuesJSON, &result)
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "decode '"+sid+"' values failed: "+err.Error())
|
||||
}
|
||||
|
||||
// Write to cache
|
||||
ttlcache.DefaultCache.Write(cacheKey, result, time.Now().Unix()+300 /** must not be too long **/)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete("SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = rpcClient.LoginSessionRPC().WriteLoginSessionValue(rpcClient.Context(0), &pb.WriteLoginSessionValueRequest{
|
||||
Sid: sid,
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "write sid:'"+sid+"' key:'"+key+"' failed: "+err.Error())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *SessionManager) Delete(sid string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete("SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = rpcClient.LoginSessionRPC().DeleteLoginSession(rpcClient.Context(0), &pb.DeleteLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "delete '"+sid+"' failed: "+err.Error())
|
||||
}
|
||||
return true
|
||||
}
|
||||
10
EdgeAdmin/internal/oplogs/levels.go
Normal file
10
EdgeAdmin/internal/oplogs/levels.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package oplogs
|
||||
|
||||
const (
|
||||
LevelNone = "none"
|
||||
LevelInfo = "info"
|
||||
LevelDebug = "debug"
|
||||
LevelWarn = "warn"
|
||||
LevelError = "error"
|
||||
LevelFatal = "fatal"
|
||||
)
|
||||
89
EdgeAdmin/internal/plus/components.go
Normal file
89
EdgeAdmin/internal/plus/components.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
type ComponentCode = string
|
||||
|
||||
const (
|
||||
ComponentCodeUser ComponentCode = "user"
|
||||
ComponentCodeScheduling ComponentCode = "scheduling"
|
||||
ComponentCodeMonitor ComponentCode = "monitor"
|
||||
ComponentCodeLog ComponentCode = "log"
|
||||
ComponentCodeReporter ComponentCode = "reporter"
|
||||
ComponentCodePlan ComponentCode = "plan"
|
||||
ComponentCodeFinance ComponentCode = "finance"
|
||||
ComponentCodeNS ComponentCode = "ns"
|
||||
ComponentCodeL2Node ComponentCode = "l2node"
|
||||
ComponentCodeComputing ComponentCode = "computing"
|
||||
ComponentCodeTicket ComponentCode = "ticket"
|
||||
ComponentCodeAntiDDoS ComponentCode = "antiDDoS"
|
||||
)
|
||||
|
||||
type ComponentDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Code ComponentCode `json:"code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func FindAllComponents() []*ComponentDefinition {
|
||||
return []*ComponentDefinition{
|
||||
{
|
||||
Name: "多租户",
|
||||
Code: ComponentCodeUser,
|
||||
},
|
||||
{
|
||||
Name: "智能调度",
|
||||
Code: ComponentCodeScheduling,
|
||||
},
|
||||
{
|
||||
Name: "监控",
|
||||
Code: ComponentCodeMonitor,
|
||||
},
|
||||
{
|
||||
Name: "日志",
|
||||
Code: ComponentCodeLog,
|
||||
},
|
||||
{
|
||||
Name: "区域监控",
|
||||
Code: ComponentCodeReporter,
|
||||
},
|
||||
{
|
||||
Name: "套餐",
|
||||
Code: ComponentCodePlan,
|
||||
},
|
||||
{
|
||||
Name: "财务",
|
||||
Code: ComponentCodeFinance,
|
||||
},
|
||||
{
|
||||
Name: "智能DNS",
|
||||
Code: ComponentCodeNS,
|
||||
},
|
||||
{
|
||||
Name: "L2节点",
|
||||
Code: ComponentCodeL2Node,
|
||||
},
|
||||
{
|
||||
Name: "边缘计算",
|
||||
Code: ComponentCodeComputing,
|
||||
},
|
||||
{
|
||||
Name: "工单系统",
|
||||
Code: ComponentCodeTicket,
|
||||
},
|
||||
{
|
||||
Name: "高防IP",
|
||||
Code: ComponentCodeAntiDDoS,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CheckComponent(code string) bool {
|
||||
for _, c := range FindAllComponents() {
|
||||
if c.Code == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
6
EdgeAdmin/internal/plus/error.go
Normal file
6
EdgeAdmin/internal/plus/error.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
var ErrString = ""
|
||||
51
EdgeAdmin/internal/plus/helper.go
Normal file
51
EdgeAdmin/internal/plus/helper.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewHelper(component ComponentCode) *Helper {
|
||||
return &Helper{component: component}
|
||||
}
|
||||
|
||||
func NewBasicHelper() *Helper {
|
||||
return NewHelper("")
|
||||
}
|
||||
|
||||
type Helper struct {
|
||||
component string
|
||||
}
|
||||
|
||||
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
|
||||
if !teaconst.IsPlus || !AllowComponent(this.component) {
|
||||
var writer = actionPtr.Object().ResponseWriter
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = writer.Write([]byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>权限错误</title>
|
||||
<link rel="stylesheet" type="text/css" href="/css/semantic.min.css?v=bRafhK" media="all"/>
|
||||
<style type="text/css">
|
||||
div { max-width: 960px; margin: 4em auto; }
|
||||
h3 { font-weight: normal; }
|
||||
p { font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h3>权限错误</h3>
|
||||
<p>此操作需要商业版用户权限。如果你已经是商业用户,请重新访问 <a href="/settings/authority">商业版认证页</a> 以便激活权限。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`))
|
||||
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
98
EdgeAdmin/internal/plus/utils.go
Normal file
98
EdgeAdmin/internal/plus/utils.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 已经开通的组件
|
||||
var plusLocker = sync.Mutex{}
|
||||
var plusComponents = []string{}
|
||||
|
||||
var PlusEdition = ""
|
||||
|
||||
// UpdateComponents 修改开通的组件
|
||||
func UpdateComponents(edition string, components []string) {
|
||||
plusLocker.Lock()
|
||||
PlusEdition = edition
|
||||
plusComponents = components
|
||||
plusLocker.Unlock()
|
||||
}
|
||||
|
||||
// AllowComponent 检查是否允许使用某个组件
|
||||
func AllowComponent(component ComponentCode) bool {
|
||||
if !teaconst.IsPlus {
|
||||
return false
|
||||
}
|
||||
|
||||
// 允许不指定组件,表示只要是plus用户就可以
|
||||
if len(component) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
plusLocker.Lock()
|
||||
defer plusLocker.Unlock()
|
||||
|
||||
if len(plusComponents) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range plusComponents {
|
||||
if c == "*" || c == component {
|
||||
// 如果不是最新版本的授权,则忽略一些权限
|
||||
if c == "*" && (len(PlusEdition) == 0 || plusutils.CompareEdition(PlusEdition, plusutils.EditionPro) < 0) && component == ComponentCodeAntiDDoS {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateMac 校验MAC地址
|
||||
func ValidateMac(mac string) bool {
|
||||
if len(mac) == 0 || mac == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, addr := range FindAllMacAddresses() {
|
||||
if addr == mac {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FindAllMacAddresses 获取MAC地址
|
||||
func FindAllMacAddresses() []string {
|
||||
var macAddresses = []string{}
|
||||
interfaces, _ := net.Interfaces()
|
||||
for _, i := range interfaces {
|
||||
var addr = i.HardwareAddr.String()
|
||||
if len(addr) == 0 {
|
||||
continue
|
||||
}
|
||||
if lists.ContainsString(macAddresses, addr) {
|
||||
continue
|
||||
}
|
||||
if i.Flags&net.FlagLoopback == 1 {
|
||||
continue
|
||||
}
|
||||
if i.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
ipAddrs, _ := i.Addrs()
|
||||
if len(ipAddrs) == 0 {
|
||||
continue
|
||||
}
|
||||
macAddresses = append(macAddresses, addr)
|
||||
}
|
||||
return macAddresses
|
||||
}
|
||||
621
EdgeAdmin/internal/rpc/rpc_client.go
Normal file
621
EdgeAdmin/internal/rpc/rpc_client.go
Normal file
@@ -0,0 +1,621 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/encrypt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RPCClient RPC客户端
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRPCClient 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
}
|
||||
|
||||
var client = &RPCClient{
|
||||
apiConfig: apiConfig,
|
||||
}
|
||||
|
||||
err := client.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置RPC
|
||||
if isPrimary {
|
||||
dao.SetRPC(client)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
|
||||
return pb.NewAPITokenServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
|
||||
return pb.NewAdminServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeRPC() pb.NodeServiceClient {
|
||||
return pb.NewNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLogRPC() pb.NodeLogServiceClient {
|
||||
return pb.NewNodeLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
|
||||
return pb.NewNodeGrantServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLoginRPC() pb.NodeLoginServiceClient {
|
||||
return pb.NewNodeLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
|
||||
return pb.NewNodeClusterServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterFirewallActionRPC() pb.NodeClusterFirewallActionServiceClient {
|
||||
return pb.NewNodeClusterFirewallActionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeGroupRPC() pb.NodeGroupServiceClient {
|
||||
return pb.NewNodeGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient {
|
||||
return pb.NewNodeRegionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
|
||||
return pb.NewNodeIPAddressServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressLogRPC() pb.NodeIPAddressLogServiceClient {
|
||||
return pb.NewNodeIPAddressLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressThresholdRPC() pb.NodeIPAddressThresholdServiceClient {
|
||||
return pb.NewNodeIPAddressThresholdServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeValueRPC() pb.NodeValueServiceClient {
|
||||
return pb.NewNodeValueServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeThresholdRPC() pb.NodeThresholdServiceClient {
|
||||
return pb.NewNodeThresholdServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
|
||||
return pb.NewServerServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBandwidthStatRPC() pb.ServerBandwidthStatServiceClient {
|
||||
return pb.NewServerBandwidthStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
|
||||
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerClientBrowserMonthlyStatRPC() pb.ServerClientBrowserMonthlyStatServiceClient {
|
||||
return pb.NewServerClientBrowserMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionCountryMonthlyStatRPC() pb.ServerRegionCountryMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionCountryMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionProvinceMonthlyStatRPC() pb.ServerRegionProvinceMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionProvinceMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionCityMonthlyStatRPC() pb.ServerRegionCityMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionCityMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionProviderMonthlyStatRPC() pb.ServerRegionProviderMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionProviderMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerHTTPFirewallDailyStatRPC() pb.ServerHTTPFirewallDailyStatServiceClient {
|
||||
return pb.NewServerHTTPFirewallDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
|
||||
return pb.NewServerDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
|
||||
return pb.NewServerGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
|
||||
return pb.NewAPINodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
|
||||
return pb.NewAPIMethodStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
|
||||
return pb.NewDBNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
||||
return pb.NewDBServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) OriginRPC() pb.OriginServiceClient {
|
||||
return pb.NewOriginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPWebRPC() pb.HTTPWebServiceClient {
|
||||
return pb.NewHTTPWebServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReverseProxyRPC() pb.ReverseProxyServiceClient {
|
||||
return pb.NewReverseProxyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPGzipRPC() pb.HTTPGzipServiceClient {
|
||||
return pb.NewHTTPGzipServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPHeaderRPC() pb.HTTPHeaderServiceClient {
|
||||
return pb.NewHTTPHeaderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPHeaderPolicyRPC() pb.HTTPHeaderPolicyServiceClient {
|
||||
return pb.NewHTTPHeaderPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPPageRPC() pb.HTTPPageServiceClient {
|
||||
return pb.NewHTTPPageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPAccessLogPolicyRPC() pb.HTTPAccessLogPolicyServiceClient {
|
||||
return pb.NewHTTPAccessLogPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCachePolicyRPC() pb.HTTPCachePolicyServiceClient {
|
||||
return pb.NewHTTPCachePolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskRPC() pb.HTTPCacheTaskServiceClient {
|
||||
return pb.NewHTTPCacheTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskKeyRPC() pb.HTTPCacheTaskKeyServiceClient {
|
||||
return pb.NewHTTPCacheTaskKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallPolicyRPC() pb.HTTPFirewallPolicyServiceClient {
|
||||
return pb.NewHTTPFirewallPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallRuleGroupRPC() pb.HTTPFirewallRuleGroupServiceClient {
|
||||
return pb.NewHTTPFirewallRuleGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallRuleSetRPC() pb.HTTPFirewallRuleSetServiceClient {
|
||||
return pb.NewHTTPFirewallRuleSetServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FirewallRPC() pb.FirewallServiceClient {
|
||||
return pb.NewFirewallServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
|
||||
return pb.NewHTTPLocationServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPWebsocketRPC() pb.HTTPWebsocketServiceClient {
|
||||
return pb.NewHTTPWebsocketServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
|
||||
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// HTTPAccessLogRPC 访问日志
|
||||
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
|
||||
return pb.NewHTTPAccessLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFastcgiRPC() pb.HTTPFastcgiServiceClient {
|
||||
return pb.NewHTTPFastcgiServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPAuthPolicyRPC() pb.HTTPAuthPolicyServiceClient {
|
||||
return pb.NewHTTPAuthPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
|
||||
return pb.NewSSLCertServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SSLPolicyRPC() pb.SSLPolicyServiceClient {
|
||||
return pb.NewSSLPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SysSettingRPC() pb.SysSettingServiceClient {
|
||||
return pb.NewSysSettingServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
|
||||
return pb.NewMessageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
||||
return pb.NewIPLibraryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryFileRPC() pb.IPLibraryFileServiceClient {
|
||||
return pb.NewIPLibraryFileServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryArtifactRPC() pb.IPLibraryArtifactServiceClient {
|
||||
return pb.NewIPLibraryArtifactServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPListRPC() pb.IPListServiceClient {
|
||||
return pb.NewIPListServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPItemRPC() pb.IPItemServiceClient {
|
||||
return pb.NewIPItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FileRPC() pb.FileServiceClient {
|
||||
return pb.NewFileServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FileChunkRPC() pb.FileChunkServiceClient {
|
||||
return pb.NewFileChunkServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionCountryRPC() pb.RegionCountryServiceClient {
|
||||
return pb.NewRegionCountryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionProvinceRPC() pb.RegionProvinceServiceClient {
|
||||
return pb.NewRegionProvinceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionCityRPC() pb.RegionCityServiceClient {
|
||||
return pb.NewRegionCityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionTownRPC() pb.RegionTownServiceClient {
|
||||
return pb.NewRegionTownServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionProviderRPC() pb.RegionProviderServiceClient {
|
||||
return pb.NewRegionProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LogRPC() pb.LogServiceClient {
|
||||
return pb.NewLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSProviderRPC() pb.DNSProviderServiceClient {
|
||||
return pb.NewDNSProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSDomainRPC() pb.DNSDomainServiceClient {
|
||||
return pb.NewDNSDomainServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSRPC() pb.DNSServiceClient {
|
||||
return pb.NewDNSServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSTaskRPC() pb.DNSTaskServiceClient {
|
||||
return pb.NewDNSTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient {
|
||||
return pb.NewACMEUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
|
||||
return pb.NewACMETaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEProviderRPC() pb.ACMEProviderServiceClient {
|
||||
return pb.NewACMEProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEProviderAccountRPC() pb.ACMEProviderAccountServiceClient {
|
||||
return pb.NewACMEProviderAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserRPC() pb.UserServiceClient {
|
||||
return pb.NewUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
|
||||
return pb.NewUserAccessKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserIdentityRPC() pb.UserIdentityServiceClient {
|
||||
return pb.NewUserIdentityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
|
||||
return pb.NewLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginSessionRPC() pb.LoginSessionServiceClient {
|
||||
return pb.NewLoginSessionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginTicketRPC() pb.LoginTicketServiceClient {
|
||||
return pb.NewLoginTicketServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
|
||||
return pb.NewNodeTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
|
||||
return pb.NewLatestItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
|
||||
return pb.NewMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
|
||||
return pb.NewMetricStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
|
||||
return pb.NewMetricChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
|
||||
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
|
||||
return pb.NewServerStatBoardServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDomainHourlyStatRPC() pb.ServerDomainHourlyStatServiceClient {
|
||||
return pb.NewServerDomainHourlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
|
||||
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
|
||||
return pb.NewTrafficDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 构造Admin上下文
|
||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "admin",
|
||||
"userId": adminId,
|
||||
}
|
||||
method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, this.apiConfig.Secret, this.apiConfig.NodeId)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
data, err := method.Encrypt(m.AsJSON())
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// APIContext 构造API上下文
|
||||
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "api",
|
||||
"userId": apiNodeId,
|
||||
}
|
||||
method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, this.apiConfig.Secret, this.apiConfig.NodeId)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
data, err := method.Encrypt(m.AsJSON())
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// UpdateConfig 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
|
||||
this.locker.Lock()
|
||||
err := this.init()
|
||||
this.locker.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *RPCClient) init() error {
|
||||
// 当前的IP地址
|
||||
var localIPAddrs = this.localIPAddrs()
|
||||
|
||||
// 重新连接
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse endpoint failed: %w", err)
|
||||
}
|
||||
|
||||
var apiHost = u.Host
|
||||
|
||||
// 如果本机,则将地址修改为回路地址
|
||||
if lists.ContainsString(localIPAddrs, u.Hostname()) {
|
||||
if strings.Contains(apiHost, "[") { // IPv6 [host]:port
|
||||
apiHost = "[::1]"
|
||||
} else {
|
||||
apiHost = "127.0.0.1"
|
||||
}
|
||||
var port = u.Port()
|
||||
if len(port) > 0 {
|
||||
apiHost += ":" + port
|
||||
}
|
||||
}
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128<<20),
|
||||
grpc.MaxCallSendMsgSize(128<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 30 * time.Second,
|
||||
})
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), callOptions, keepaliveParams)
|
||||
} else {
|
||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
if len(conns) == 0 {
|
||||
return errors.New("[RPC]no available endpoints")
|
||||
}
|
||||
|
||||
// 这里不需要加锁,因为会和pickConn冲突
|
||||
this.conns = conns
|
||||
return nil
|
||||
}
|
||||
|
||||
// 随机选择一个连接
|
||||
func (this *RPCClient) pickConn() *grpc.ClientConn {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查连接状态
|
||||
var countConns = len(this.conns)
|
||||
if countConns > 0 {
|
||||
if countConns == 1 {
|
||||
return this.conns[0]
|
||||
}
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, conn := range this.conns {
|
||||
if conn.GetState() == state {
|
||||
availableConns = append(availableConns, conn)
|
||||
}
|
||||
}
|
||||
if len(availableConns) > 0 {
|
||||
return this.randConn(availableConns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.randConn(this.conns)
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
func (this *RPCClient) Close() error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var lastErr error
|
||||
for _, conn := range this.conns {
|
||||
var err = conn.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (this *RPCClient) localIPAddrs() []string {
|
||||
localInterfaceAddrs, err := net.InterfaceAddrs()
|
||||
var localIPAddrs = []string{}
|
||||
if err == nil {
|
||||
for _, addr := range localInterfaceAddrs {
|
||||
var addrString = addr.String()
|
||||
var index = strings.Index(addrString, "/")
|
||||
if index > 0 {
|
||||
localIPAddrs = append(localIPAddrs, addrString[:index])
|
||||
}
|
||||
}
|
||||
}
|
||||
return localIPAddrs
|
||||
}
|
||||
|
||||
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
var l = len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
250
EdgeAdmin/internal/rpc/rpc_client_plus.go
Normal file
250
EdgeAdmin/internal/rpc/rpc_client_plus.go
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package rpc
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
|
||||
func (this *RPCClient) ScriptRPC() pb.ScriptServiceClient {
|
||||
return pb.NewScriptServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserScriptRPC() pb.UserScriptServiceClient {
|
||||
return pb.NewUserScriptServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserOrderRPC() pb.UserOrderServiceClient {
|
||||
return pb.NewUserOrderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) OrderMethodRPC() pb.OrderMethodServiceClient {
|
||||
return pb.NewOrderMethodServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTicketRPC() pb.UserTicketServiceClient {
|
||||
return pb.NewUserTicketServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTicketLogRPC() pb.UserTicketLogServiceClient {
|
||||
return pb.NewUserTicketLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTicketCategoryRPC() pb.UserTicketCategoryServiceClient {
|
||||
return pb.NewUserTicketCategoryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSDomainRPC() pb.NSDomainServiceClient {
|
||||
return pb.NewNSDomainServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSDomainGroupRPC() pb.NSDomainGroupServiceClient {
|
||||
return pb.NewNSDomainGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRecordRPC() pb.NSRecordServiceClient {
|
||||
return pb.NewNSRecordServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSKeyRPC() pb.NSKeyServiceClient {
|
||||
return pb.NewNSKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRouteRPC() pb.NSRouteServiceClient {
|
||||
return pb.NewNSRouteServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRouteCategoryRPC() pb.NSRouteCategoryServiceClient {
|
||||
return pb.NewNSRouteCategoryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSAccessLogRPC() pb.NSAccessLogServiceClient {
|
||||
return pb.NewNSAccessLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRPC() pb.NSServiceClient {
|
||||
return pb.NewNSServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSQuestionOptionRPC() pb.NSQuestionOptionServiceClient {
|
||||
return pb.NewNSQuestionOptionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSClusterRPC() pb.NSClusterServiceClient {
|
||||
return pb.NewNSClusterServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSNodeRPC() pb.NSNodeServiceClient {
|
||||
return pb.NewNSNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRecordHourlyStatRPC() pb.NSRecordHourlyStatServiceClient {
|
||||
return pb.NewNSRecordHourlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSPlanRPC() pb.NSPlanServiceClient {
|
||||
return pb.NewNSPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSUserPlanRPC() pb.NSUserPlanServiceClient {
|
||||
return pb.NewNSUserPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportNodeRPC() pb.ReportNodeServiceClient {
|
||||
return pb.NewReportNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportNodeGroupRPC() pb.ReportNodeGroupServiceClient {
|
||||
return pb.NewReportNodeGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportResultRPC() pb.ReportResultServiceClient {
|
||||
return pb.NewReportResultServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
|
||||
return pb.NewUserNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
|
||||
return pb.NewUserBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBillRPC() pb.ServerBillServiceClient {
|
||||
return pb.NewServerBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTrafficBillRPC() pb.UserTrafficBillServiceClient {
|
||||
return pb.NewUserTrafficBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountRPC() pb.UserAccountServiceClient {
|
||||
return pb.NewUserAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountLogRPC() pb.UserAccountLogServiceClient {
|
||||
return pb.NewUserAccountLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountDailyStatRPC() pb.UserAccountDailyStatServiceClient {
|
||||
return pb.NewUserAccountDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient {
|
||||
return pb.NewNodePriceItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PriceRPC() pb.PriceServiceClient {
|
||||
return pb.NewPriceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PlanRPC() pb.PlanServiceClient {
|
||||
return pb.NewPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserPlanRPC() pb.UserPlanServiceClient {
|
||||
return pb.NewUserPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficPackageRPC() pb.TrafficPackageServiceClient {
|
||||
return pb.NewTrafficPackageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficPackagePeriodRPC() pb.TrafficPackagePeriodServiceClient {
|
||||
return pb.NewTrafficPackagePeriodServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficPackagePriceRPC() pb.TrafficPackagePriceServiceClient {
|
||||
return pb.NewTrafficPackagePriceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTrafficPackageRPC() pb.UserTrafficPackageServiceClient {
|
||||
return pb.NewUserTrafficPackageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FormalClientSystemRPC() pb.FormalClientSystemServiceClient {
|
||||
return pb.NewFormalClientSystemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FormalClientBrowserRPC() pb.FormalClientBrowserServiceClient {
|
||||
return pb.NewFormalClientBrowserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ClientAgentRPC() pb.ClientAgentServiceClient {
|
||||
return pb.NewClientAgentServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ClientAgentIPRPC() pb.ClientAgentIPServiceClient {
|
||||
return pb.NewClientAgentIPServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackageRPC() pb.ADPackageServiceClient {
|
||||
return pb.NewADPackageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackageInstanceRPC() pb.ADPackageInstanceServiceClient {
|
||||
return pb.NewADPackageInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackagePeriodRPC() pb.ADPackagePeriodServiceClient {
|
||||
return pb.NewADPackagePeriodServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackagePriceRPC() pb.ADPackagePriceServiceClient {
|
||||
return pb.NewADPackagePriceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADNetworkRPC() pb.ADNetworkServiceClient {
|
||||
return pb.NewADNetworkServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserADInstanceRPC() pb.UserADInstanceServiceClient {
|
||||
return pb.NewUserADInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeActionRPC() pb.NodeActionServiceClient {
|
||||
return pb.NewNodeActionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
|
||||
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
|
||||
return pb.NewMessageRecipientServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
|
||||
return pb.NewMessageMediaServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
|
||||
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
|
||||
return pb.NewMessageTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
|
||||
return pb.NewMessageTaskLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
|
||||
return pb.NewMessageReceiverServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
|
||||
return pb.NewAuthorityKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SMSSenderRPC() pb.SMSSenderServiceClient {
|
||||
return pb.NewSMSSenderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PostCategoryRPC() pb.PostCategoryServiceClient {
|
||||
return pb.NewPostCategoryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PostRPC() pb.PostServiceClient {
|
||||
return pb.NewPostServiceClient(this.pickConn())
|
||||
}
|
||||
134
EdgeAdmin/internal/rpc/rpc_client_test.go
Normal file
134
EdgeAdmin/internal/rpc/rpc_client_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
before := time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp)
|
||||
}
|
||||
|
||||
func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.NodeRPC().FindEnabledNode(client.Context(1), &pb.FindEnabledNodeRequest{NodeId: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.NodeRPC().FindEnabledNode(client.Context(1), &pb.FindEnabledNodeRequest{NodeId: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8004"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.NodeRPC().FindEnabledNode(client.Context(1), &pb.FindEnabledNodeRequest{NodeId: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient_2(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var conn = rpc.AdminRPC()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := conn.LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
69
EdgeAdmin/internal/rpc/rpc_utils.go
Normal file
69
EdgeAdmin/internal/rpc/rpc_utils.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var sharedRPC *RPCClient = nil
|
||||
var locker = &sync.Mutex{}
|
||||
|
||||
func SharedRPC() (*RPCClient, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
if sharedRPC != nil {
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sharedRPC = client
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
// IsConnError 是否为连接错误
|
||||
func IsConnError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否为连接错误
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
var errorCode = statusErr.Code()
|
||||
return errorCode == codes.Unavailable || errorCode == codes.Canceled
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "code = Canceled") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnimplementedError 检查是否为未实现错误
|
||||
func IsUnimplementedError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
if statusErr.Code() == codes.Unimplemented {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
23
EdgeAdmin/internal/setup/utils.go
Normal file
23
EdgeAdmin/internal/setup/utils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
)
|
||||
|
||||
var isConfigured bool
|
||||
|
||||
// IsConfigured 判断系统是否已经配置过
|
||||
func IsConfigured() bool {
|
||||
if isConfigured {
|
||||
return true
|
||||
}
|
||||
|
||||
_, err := configs.LoadAPIConfig()
|
||||
isConfigured = err == nil
|
||||
return isConfigured
|
||||
}
|
||||
|
||||
// IsNewInstalled IsNew 检查是否新安装
|
||||
func IsNewInstalled() bool {
|
||||
return configs.IsNewInstalled()
|
||||
}
|
||||
177
EdgeAdmin/internal/tasks/task_authority_plus.go
Normal file
177
EdgeAdmin/internal/tasks/task_authority_plus.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
var task = NewAuthorityTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var authorityTaskNotifyChange = make(chan bool, 1)
|
||||
|
||||
func NotifyAuthorityTask() {
|
||||
select {
|
||||
case authorityTaskNotifyChange <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
type AuthorityTask struct {
|
||||
oldIsPlus bool
|
||||
}
|
||||
|
||||
func NewAuthorityTask() *AuthorityTask {
|
||||
return &AuthorityTask{}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Start() {
|
||||
// 从缓存中读取
|
||||
var config = configs.ReadPlusConfig()
|
||||
if config != nil {
|
||||
teaconst.IsPlus = config.IsPlus
|
||||
this.notifyChange()
|
||||
plus.UpdateComponents(config.Edition, config.Components)
|
||||
}
|
||||
|
||||
// 开始计时器
|
||||
var ticker = time.NewTicker(10 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
|
||||
// 初始化的时候先获取一次
|
||||
var timeout = time.NewTimer(3 * time.Second)
|
||||
<-timeout.C
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
|
||||
// 定时获取
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-authorityTaskNotifyChange:
|
||||
}
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.AuthorityKeyRPC().ReadAuthorityKey(rpcClient.Context(0), &pb.ReadAuthorityKeyRequest{})
|
||||
if err != nil {
|
||||
teaconst.IsPlus = false
|
||||
|
||||
if !rpc.IsConnError(err) {
|
||||
this.notifyChange()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
defer this.notifyChange()
|
||||
|
||||
if resp.AuthorityKey == nil {
|
||||
teaconst.IsPlus = false
|
||||
return nil
|
||||
}
|
||||
|
||||
plus.ErrString = ""
|
||||
var plusEdition = ""
|
||||
if resp.AuthorityKey != nil && len(resp.AuthorityKey.Value) > 0 && resp.AuthorityKey.DayTo >= timeutil.Format("Y-m-d") {
|
||||
nativeKey, err := plusutils.DecodeKey([]byte(resp.AuthorityKey.Value))
|
||||
if err != nil {
|
||||
teaconst.IsPlus = false
|
||||
return nil
|
||||
}
|
||||
var isOk = true
|
||||
|
||||
if len(nativeKey.RequestCode) > 0 {
|
||||
isOk, _ = plusutils.ValidateRequestCode(nativeKey.RequestCode)
|
||||
} else if len(nativeKey.MacAddresses) > 0 {
|
||||
var macAddresses = resp.AuthorityKey.MacAddresses
|
||||
for _, addr := range macAddresses {
|
||||
if !plus.ValidateMac(addr) {
|
||||
isOk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isOk {
|
||||
plusEdition = resp.AuthorityKey.Edition
|
||||
teaconst.IsPlus = true
|
||||
plus.UpdateComponents(resp.AuthorityKey.Edition, resp.AuthorityKey.Components)
|
||||
} else {
|
||||
plus.ErrString = "当前管理平台所在服务器环境与商业版认证分发时发生了变化,因此无法使用商业版功能。如果你正在迁移管理平台,请联系开发者重新申请新的注册码。"
|
||||
teaconst.IsPlus = false
|
||||
}
|
||||
} else {
|
||||
teaconst.IsPlus = false
|
||||
}
|
||||
|
||||
_ = configs.WritePlusConfig(&configs.PlusConfig{
|
||||
Edition: plusEdition,
|
||||
IsPlus: teaconst.IsPlus,
|
||||
Components: resp.AuthorityKey.Components,
|
||||
DayTo: resp.AuthorityKey.DayTo,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) notifyChange() {
|
||||
if this.oldIsPlus == teaconst.IsPlus {
|
||||
return
|
||||
}
|
||||
this.oldIsPlus = teaconst.IsPlus
|
||||
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second) // 等待默认的加载程序完成
|
||||
|
||||
var err error
|
||||
if teaconst.IsPlus {
|
||||
err = iplibrary.InitPlus()
|
||||
} else {
|
||||
err = iplibrary.InitDefault()
|
||||
}
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: " + err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
133
EdgeAdmin/internal/tasks/task_check_updates.go
Normal file
133
EdgeAdmin/internal/tasks/task_check_updates.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
var task = NewCheckUpdatesTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type CheckUpdatesTask struct {
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
func NewCheckUpdatesTask() *CheckUpdatesTask {
|
||||
return &CheckUpdatesTask{}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Start() {
|
||||
this.ticker = time.NewTicker(12 * time.Hour)
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][CHECK_UPDATES_TASK]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Loop() error {
|
||||
// 检查是否开启
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeCheckUpdates})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var valueJSON = valueResp.ValueJSON
|
||||
var config = systemconfigs.NewCheckUpdatesConfig()
|
||||
if len(valueJSON) > 0 {
|
||||
err = json.Unmarshal(valueJSON, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode config failed: %w", err)
|
||||
}
|
||||
if !config.AutoCheck {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 开始检查
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 目前支持Linux
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var apiURL = teaconst.UpdatesURL
|
||||
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api failed: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api failed: %w", err)
|
||||
}
|
||||
|
||||
var apiResponse = &Response{}
|
||||
err = json.Unmarshal(data, apiResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode version data failed: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.Code != 200 {
|
||||
return errors.New("invalid response: " + apiResponse.Message)
|
||||
}
|
||||
|
||||
var m = maps.NewMap(apiResponse.Data)
|
||||
var dlHost = m.GetString("host")
|
||||
var versions = m.GetSlice("versions")
|
||||
if len(versions) > 0 {
|
||||
for _, version := range versions {
|
||||
var vMap = maps.NewMap(version)
|
||||
if vMap.GetString("code") == "admin" {
|
||||
var latestVersion = vMap.GetString("version")
|
||||
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 && (len(config.IgnoredVersion) == 0 || stringutil.VersionCompare(latestVersion, config.IgnoredVersion) > 0) {
|
||||
teaconst.NewVersionCode = latestVersion
|
||||
teaconst.NewVersionDownloadURL = dlHost + vMap.GetString("url")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
166
EdgeAdmin/internal/tasks/task_sync_api_nodes.go
Normal file
166
EdgeAdmin/internal/tasks/task_sync_api_nodes.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncAPINodesTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// SyncAPINodesTask API节点同步任务
|
||||
type SyncAPINodesTask struct {
|
||||
}
|
||||
|
||||
func NewSyncAPINodesTask() *SyncAPINodesTask {
|
||||
return &SyncAPINodesTask{}
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Start() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][SYNC_API_NODES]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPCDisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取所有可用的节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.APINodeRPC().FindAllEnabledAPINodes(rpcClient.Context(0), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var newEndpoints = []string{}
|
||||
for _, node := range resp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
newEndpoints = append(newEndpoints, node.AccessAddrs...)
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
if this.isSame(newEndpoints, config.RPCEndpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 测试是否有API节点可用
|
||||
hasOk := this.testEndpoints(newEndpoints)
|
||||
if !hasOk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPCEndpoints = newEndpoints
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) bool {
|
||||
sort.Strings(endpoints1)
|
||||
sort.Strings(endpoints2)
|
||||
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
|
||||
if len(endpoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
wg.Add(len(endpoints))
|
||||
|
||||
var ok = false
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
go func(endpoint string) {
|
||||
defer wg.Done()
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer func() {
|
||||
cancel()
|
||||
}()
|
||||
var conn *grpc.ClientConn
|
||||
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithBlock())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.Close()
|
||||
|
||||
ok = true
|
||||
}(endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return ok
|
||||
}
|
||||
15
EdgeAdmin/internal/tasks/task_sync_api_nodes_test.go
Normal file
15
EdgeAdmin/internal/tasks/task_sync_api_nodes_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncAPINodesTask_Loop(t *testing.T) {
|
||||
task := NewSyncAPINodesTask()
|
||||
err := task.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
84
EdgeAdmin/internal/tasks/task_sync_cluster.go
Normal file
84
EdgeAdmin/internal/tasks/task_sync_cluster.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncClusterTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// SyncClusterTask 自动同步集群任务
|
||||
type SyncClusterTask struct {
|
||||
}
|
||||
|
||||
func NewSyncClusterTask() *SyncClusterTask {
|
||||
return &SyncClusterTask{}
|
||||
}
|
||||
|
||||
func (this *SyncClusterTask) Start() {
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
for range ticker.C {
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][SYNC_CLUSTER]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SyncClusterTask) loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := rpcClient.Context(0)
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 500})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeIds := []int64{}
|
||||
taskIds := []int64{}
|
||||
for _, task := range tasksResp.NodeTasks {
|
||||
if !lists.ContainsInt64(nodeIds, task.Node.Id) {
|
||||
nodeIds = append(nodeIds, task.Node.Id)
|
||||
}
|
||||
taskIds = append(taskIds, task.Id)
|
||||
}
|
||||
if len(nodeIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err = nodeutils.SendMessageToNodeIds(ctx, nodeIds, messageconfigs.MessageCodeNewNodeTask, &messageconfigs.NewNodeTaskMessage{}, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置已通知
|
||||
_, err = rpcClient.NodeTaskRPC().UpdateNodeTasksNotified(ctx, &pb.UpdateNodeTasksNotifiedRequest{NodeTaskIds: taskIds})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
12
EdgeAdmin/internal/tasks/task_sync_cluster_test.go
Normal file
12
EdgeAdmin/internal/tasks/task_sync_cluster_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package tasks
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSyncClusterTask_loop(t *testing.T) {
|
||||
task := NewSyncClusterTask()
|
||||
err := task.loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
145
EdgeAdmin/internal/ttlcache/cache.go
Normal file
145
EdgeAdmin/internal/ttlcache/cache.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DefaultCache = NewCache()
|
||||
|
||||
// TTL缓存
|
||||
// 最大的缓存时间为30 * 86400
|
||||
// Piece数据结构:
|
||||
//
|
||||
// Piece1 | Piece2 | Piece3 | ...
|
||||
// [ Item1, Item2, ... | ...
|
||||
//
|
||||
// KeyMap列表数据结构
|
||||
// { timestamp1 => [key1, key2, ...] }, ...
|
||||
type Cache struct {
|
||||
isDestroyed bool
|
||||
pieces []*Piece
|
||||
countPieces uint64
|
||||
maxItems int
|
||||
|
||||
gcPieceIndex int
|
||||
ticker *utils.Ticker
|
||||
}
|
||||
|
||||
func NewCache(opt ...OptionInterface) *Cache {
|
||||
countPieces := 128
|
||||
maxItems := 1_000_000
|
||||
for _, option := range opt {
|
||||
if option == nil {
|
||||
continue
|
||||
}
|
||||
switch o := option.(type) {
|
||||
case *PiecesOption:
|
||||
if o.Count > 0 {
|
||||
countPieces = o.Count
|
||||
}
|
||||
case *MaxItemsOption:
|
||||
if o.Count > 0 {
|
||||
maxItems = o.Count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache := &Cache{
|
||||
countPieces: uint64(countPieces),
|
||||
maxItems: maxItems,
|
||||
}
|
||||
|
||||
for i := 0; i < countPieces; i++ {
|
||||
cache.pieces = append(cache.pieces, NewPiece(maxItems/countPieces))
|
||||
}
|
||||
|
||||
// start timer
|
||||
go func() {
|
||||
cache.ticker = utils.NewTicker(5 * time.Second)
|
||||
for cache.ticker.Next() {
|
||||
cache.GC()
|
||||
}
|
||||
}()
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
func (this *Cache) Write(key string, value interface{}, expiredAt int64) {
|
||||
if this.isDestroyed {
|
||||
return
|
||||
}
|
||||
|
||||
currentTimestamp := time.Now().Unix()
|
||||
if expiredAt <= currentTimestamp {
|
||||
return
|
||||
}
|
||||
|
||||
maxExpiredAt := currentTimestamp + 30*86400
|
||||
if expiredAt > maxExpiredAt {
|
||||
expiredAt = maxExpiredAt
|
||||
}
|
||||
uint64Key := HashKey([]byte(key))
|
||||
pieceIndex := uint64Key % this.countPieces
|
||||
this.pieces[pieceIndex].Add(uint64Key, &Item{
|
||||
Value: value,
|
||||
expiredAt: expiredAt,
|
||||
})
|
||||
}
|
||||
|
||||
func (this *Cache) IncreaseInt64(key string, delta int64, expiredAt int64) int64 {
|
||||
if this.isDestroyed {
|
||||
return 0
|
||||
}
|
||||
|
||||
currentTimestamp := time.Now().Unix()
|
||||
if expiredAt <= currentTimestamp {
|
||||
return 0
|
||||
}
|
||||
|
||||
maxExpiredAt := currentTimestamp + 30*86400
|
||||
if expiredAt > maxExpiredAt {
|
||||
expiredAt = maxExpiredAt
|
||||
}
|
||||
uint64Key := HashKey([]byte(key))
|
||||
pieceIndex := uint64Key % this.countPieces
|
||||
return this.pieces[pieceIndex].IncreaseInt64(uint64Key, delta, expiredAt)
|
||||
}
|
||||
|
||||
func (this *Cache) Read(key string) (item *Item) {
|
||||
uint64Key := HashKey([]byte(key))
|
||||
return this.pieces[uint64Key%this.countPieces].Read(uint64Key)
|
||||
}
|
||||
|
||||
func (this *Cache) Delete(key string) {
|
||||
uint64Key := HashKey([]byte(key))
|
||||
this.pieces[uint64Key%this.countPieces].Delete(uint64Key)
|
||||
}
|
||||
|
||||
func (this *Cache) Count() (count int) {
|
||||
for _, piece := range this.pieces {
|
||||
count += piece.Count()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Cache) GC() {
|
||||
this.pieces[this.gcPieceIndex].GC()
|
||||
newIndex := this.gcPieceIndex + 1
|
||||
if newIndex >= int(this.countPieces) {
|
||||
newIndex = 0
|
||||
}
|
||||
this.gcPieceIndex = newIndex
|
||||
}
|
||||
|
||||
func (this *Cache) Destroy() {
|
||||
this.isDestroyed = true
|
||||
|
||||
if this.ticker != nil {
|
||||
this.ticker.Stop()
|
||||
this.ticker = nil
|
||||
}
|
||||
for _, piece := range this.pieces {
|
||||
piece.Destroy()
|
||||
}
|
||||
}
|
||||
124
EdgeAdmin/internal/ttlcache/cache_test.go
Normal file
124
EdgeAdmin/internal/ttlcache/cache_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewCache(t *testing.T) {
|
||||
cache := NewCache()
|
||||
cache.Write("a", 1, time.Now().Unix()+3600)
|
||||
cache.Write("b", 2, time.Now().Unix()+3601)
|
||||
cache.Write("a", 1, time.Now().Unix()+3602)
|
||||
cache.Write("d", 1, time.Now().Unix()+1)
|
||||
|
||||
for _, piece := range cache.pieces {
|
||||
if len(piece.m) > 0 {
|
||||
for k, item := range piece.m {
|
||||
t.Log(k, "=>", item.Value, item.expiredAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Log(cache.Read("a"))
|
||||
time.Sleep(2 * time.Second)
|
||||
t.Log(cache.Read("d"))
|
||||
}
|
||||
|
||||
func BenchmarkCache_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
cache := NewCache()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(i%1024))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCache_IncreaseInt64(t *testing.T) {
|
||||
var cache = NewCache()
|
||||
|
||||
{
|
||||
cache.IncreaseInt64("a", 1, time.Now().Unix()+3600)
|
||||
t.Log(cache.Read("a"))
|
||||
}
|
||||
{
|
||||
cache.IncreaseInt64("a", 1, time.Now().Unix()+3600+1)
|
||||
t.Log(cache.Read("a"))
|
||||
}
|
||||
{
|
||||
cache.Write("b", 1, time.Now().Unix()+3600+2)
|
||||
t.Log(cache.Read("b"))
|
||||
}
|
||||
{
|
||||
cache.IncreaseInt64("b", 1, time.Now().Unix()+3600+3)
|
||||
t.Log(cache.Read("b"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCache_Read(t *testing.T) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var cache = NewCache(PiecesOption{Count: 32})
|
||||
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
cache.Write("HELLO_WORLD_"+strconv.Itoa(i), i, time.Now().Unix()+int64(i%10240)+1)
|
||||
}
|
||||
|
||||
total := 0
|
||||
for _, piece := range cache.pieces {
|
||||
//t.Log(len(piece.m), "keys")
|
||||
total += len(piece.m)
|
||||
}
|
||||
t.Log(total, "total keys")
|
||||
|
||||
before := time.Now()
|
||||
for i := 0; i < 10_240; i++ {
|
||||
_ = cache.Read("HELLO_WORLD_" + strconv.Itoa(i))
|
||||
}
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestCache_GC(t *testing.T) {
|
||||
var cache = NewCache(&PiecesOption{Count: 5})
|
||||
cache.Write("a", 1, time.Now().Unix()+1)
|
||||
cache.Write("b", 2, time.Now().Unix()+2)
|
||||
cache.Write("c", 3, time.Now().Unix()+3)
|
||||
cache.Write("d", 4, time.Now().Unix()+4)
|
||||
cache.Write("e", 5, time.Now().Unix()+10)
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 1000; i++ {
|
||||
cache.Write("f", 1, time.Now().Unix()+1)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
cache.GC()
|
||||
t.Log("items:", cache.Count())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
t.Log("now:", time.Now().Unix())
|
||||
for _, p := range cache.pieces {
|
||||
for k, v := range p.m {
|
||||
t.Log(k, v.Value, v.expiredAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCache_GC2(t *testing.T) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
cache := NewCache()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(rands.Int(0, 100)))
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Log(cache.Count(), "items")
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
6
EdgeAdmin/internal/ttlcache/item.go
Normal file
6
EdgeAdmin/internal/ttlcache/item.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package ttlcache
|
||||
|
||||
type Item struct {
|
||||
Value interface{}
|
||||
expiredAt int64
|
||||
}
|
||||
20
EdgeAdmin/internal/ttlcache/option.go
Normal file
20
EdgeAdmin/internal/ttlcache/option.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package ttlcache
|
||||
|
||||
type OptionInterface interface {
|
||||
}
|
||||
|
||||
type PiecesOption struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
func NewPiecesOption(count int) *PiecesOption {
|
||||
return &PiecesOption{Count: count}
|
||||
}
|
||||
|
||||
type MaxItemsOption struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
func NewMaxItemsOption(count int) *MaxItemsOption {
|
||||
return &MaxItemsOption{Count: count}
|
||||
}
|
||||
88
EdgeAdmin/internal/ttlcache/piece.go
Normal file
88
EdgeAdmin/internal/ttlcache/piece.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Piece struct {
|
||||
m map[uint64]*Item
|
||||
maxItems int
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewPiece(maxItems int) *Piece {
|
||||
return &Piece{m: map[uint64]*Item{}, maxItems: maxItems}
|
||||
}
|
||||
|
||||
func (this *Piece) Add(key uint64, item *Item) {
|
||||
this.locker.Lock()
|
||||
if len(this.m) >= this.maxItems {
|
||||
this.locker.Unlock()
|
||||
return
|
||||
}
|
||||
this.m[key] = item
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Piece) IncreaseInt64(key uint64, delta int64, expiredAt int64) (result int64) {
|
||||
this.locker.Lock()
|
||||
item, ok := this.m[key]
|
||||
if ok {
|
||||
result = types.Int64(item.Value) + delta
|
||||
item.Value = result
|
||||
item.expiredAt = expiredAt
|
||||
} else {
|
||||
if len(this.m) < this.maxItems {
|
||||
result = delta
|
||||
this.m[key] = &Item{
|
||||
Value: delta,
|
||||
expiredAt: expiredAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Piece) Delete(key uint64) {
|
||||
this.locker.Lock()
|
||||
delete(this.m, key)
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Piece) Read(key uint64) (item *Item) {
|
||||
this.locker.RLock()
|
||||
item = this.m[key]
|
||||
if item != nil && item.expiredAt < time.Now().Unix() {
|
||||
item = nil
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Piece) Count() (count int) {
|
||||
this.locker.RLock()
|
||||
count = len(this.m)
|
||||
this.locker.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Piece) GC() {
|
||||
this.locker.Lock()
|
||||
timestamp := time.Now().Unix()
|
||||
for k, item := range this.m {
|
||||
if item.expiredAt <= timestamp {
|
||||
delete(this.m, k)
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Piece) Destroy() {
|
||||
this.locker.Lock()
|
||||
this.m = nil
|
||||
this.locker.Unlock()
|
||||
}
|
||||
60
EdgeAdmin/internal/ttlcache/piece_test.go
Normal file
60
EdgeAdmin/internal/ttlcache/piece_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPiece_Add(t *testing.T) {
|
||||
piece := NewPiece(10)
|
||||
piece.Add(1, &Item{expiredAt: time.Now().Unix() + 3600})
|
||||
piece.Add(2, &Item{})
|
||||
piece.Add(3, &Item{})
|
||||
piece.Delete(3)
|
||||
for key, item := range piece.m {
|
||||
t.Log(key, item.Value)
|
||||
}
|
||||
t.Log(piece.Read(1))
|
||||
}
|
||||
|
||||
func TestPiece_MaxItems(t *testing.T) {
|
||||
piece := NewPiece(10)
|
||||
for i := 0; i < 1000; i++ {
|
||||
piece.Add(uint64(i), &Item{expiredAt: time.Now().Unix() + 3600})
|
||||
}
|
||||
t.Log(len(piece.m))
|
||||
}
|
||||
|
||||
func TestPiece_GC(t *testing.T) {
|
||||
piece := NewPiece(10)
|
||||
piece.Add(1, &Item{Value: 1, expiredAt: time.Now().Unix() + 1})
|
||||
piece.Add(2, &Item{Value: 2, expiredAt: time.Now().Unix() + 1})
|
||||
piece.Add(3, &Item{Value: 3, expiredAt: time.Now().Unix() + 1})
|
||||
t.Log("before gc ===")
|
||||
for key, item := range piece.m {
|
||||
t.Log(key, item.Value)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
piece.GC()
|
||||
|
||||
t.Log("after gc ===")
|
||||
for key, item := range piece.m {
|
||||
t.Log(key, item.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPiece_GC2(t *testing.T) {
|
||||
piece := NewPiece(10)
|
||||
for i := 0; i < 10_000; i++ {
|
||||
piece.Add(uint64(i), &Item{Value: 1, expiredAt: time.Now().Unix() + int64(rands.Int(1, 10))})
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
before := time.Now()
|
||||
piece.GC()
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
t.Log(piece.Count())
|
||||
}
|
||||
7
EdgeAdmin/internal/ttlcache/utils.go
Normal file
7
EdgeAdmin/internal/ttlcache/utils.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package ttlcache
|
||||
|
||||
import "github.com/cespare/xxhash/v2"
|
||||
|
||||
func HashKey(key []byte) uint64 {
|
||||
return xxhash.Sum64(key)
|
||||
}
|
||||
13
EdgeAdmin/internal/ttlcache/utils_test.go
Normal file
13
EdgeAdmin/internal/ttlcache/utils_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkHashKey(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
for i := 0; i < b.N; i++ {
|
||||
HashKey([]byte("HELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLD"))
|
||||
}
|
||||
}
|
||||
79
EdgeAdmin/internal/utils/apinodeutils/deploy_file.go
Normal file
79
EdgeAdmin/internal/utils/apinodeutils/deploy_file.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// DeployFile 部署文件描述
|
||||
type DeployFile struct {
|
||||
OS string
|
||||
Arch string
|
||||
Version string
|
||||
Path string
|
||||
}
|
||||
|
||||
// Sum 计算概要
|
||||
func (this *DeployFile) Sum() (string, error) {
|
||||
fp, err := os.Open(this.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
m := md5.New()
|
||||
buffer := make([]byte, 128*1024)
|
||||
for {
|
||||
n, err := fp.Read(buffer)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
_, err = m.Write(buffer[:n])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
sum := m.Sum(nil)
|
||||
return fmt.Sprintf("%x", sum), nil
|
||||
}
|
||||
|
||||
// Read 读取一个片段数据
|
||||
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
|
||||
fp, err := os.Open(this.Path)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
stat, err := fp.Stat()
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
if offset >= stat.Size() {
|
||||
return nil, offset, io.EOF
|
||||
}
|
||||
|
||||
_, err = fp.Seek(offset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
|
||||
buffer := make([]byte, 128*1024)
|
||||
n, err := fp.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, offset, err
|
||||
}
|
||||
|
||||
return buffer[:n], offset + int64(n), nil
|
||||
}
|
||||
96
EdgeAdmin/internal/utils/apinodeutils/deploy_manager.go
Normal file
96
EdgeAdmin/internal/utils/apinodeutils/deploy_manager.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// DeployManager 节点部署文件管理器
|
||||
// 如果节点部署文件有变化,需要重启API节点以便于生效
|
||||
type DeployManager struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
// NewDeployManager 获取新节点部署文件管理器
|
||||
func NewDeployManager() *DeployManager {
|
||||
var manager = &DeployManager{
|
||||
dir: Tea.Root + "/edge-api/deploy",
|
||||
}
|
||||
manager.LoadNodeFiles()
|
||||
manager.LoadNSNodeFiles()
|
||||
return manager
|
||||
}
|
||||
|
||||
// LoadNodeFiles 加载所有边缘节点文件
|
||||
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadNSNodeFiles 加载所有NS节点安装文件
|
||||
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
30
EdgeAdmin/internal/utils/apinodeutils/manager.go
Normal file
30
EdgeAdmin/internal/utils/apinodeutils/manager.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
var SharedManager = NewManager()
|
||||
|
||||
type Manager struct {
|
||||
upgraderMap map[int64]*Upgrader
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
upgraderMap: map[int64]*Upgrader{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Manager) AddUpgrader(upgrader *Upgrader) {
|
||||
this.upgraderMap[upgrader.apiNodeId] = upgrader
|
||||
}
|
||||
|
||||
func (this *Manager) FindUpgrader(apiNodeId int64) *Upgrader {
|
||||
return this.upgraderMap[apiNodeId]
|
||||
}
|
||||
|
||||
func (this *Manager) RemoveUpgrader(upgrader *Upgrader) {
|
||||
if upgrader == nil {
|
||||
return
|
||||
}
|
||||
delete(this.upgraderMap, upgrader.apiNodeId)
|
||||
}
|
||||
349
EdgeAdmin/internal/utils/apinodeutils/upgrader.go
Normal file
349
EdgeAdmin/internal/utils/apinodeutils/upgrader.go
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type Progress struct {
|
||||
Percent float64
|
||||
}
|
||||
|
||||
type Upgrader struct {
|
||||
progress *Progress
|
||||
apiExe string
|
||||
apiNodeId int64
|
||||
}
|
||||
|
||||
func NewUpgrader(apiNodeId int64) *Upgrader {
|
||||
return &Upgrader{
|
||||
apiExe: apiExe(),
|
||||
progress: &Progress{Percent: 0},
|
||||
apiNodeId: apiNodeId,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Upgrader) Upgrade() error {
|
||||
sharedClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiNodeResp, err := sharedClient.APINodeRPC().FindEnabledAPINode(sharedClient.Context(0), &pb.FindEnabledAPINodeRequest{ApiNodeId: this.apiNodeId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var apiNode = apiNodeResp.ApiNode
|
||||
if apiNode == nil {
|
||||
return errors.New("could not find api node with id '" + types.String(this.apiNodeId) + "'")
|
||||
}
|
||||
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var newAPIConfig = apiConfig.Clone()
|
||||
newAPIConfig.RPCEndpoints = apiNode.AccessAddrs
|
||||
|
||||
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级边缘节点
|
||||
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级NS节点
|
||||
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级API节点
|
||||
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upgrade api node failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress 查看升级进程
|
||||
func (this *Upgrader) Progress() *Progress {
|
||||
return this.progress
|
||||
}
|
||||
|
||||
// 升级API节点
|
||||
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !Tea.IsTesting() /** 开发环境下允许突破此限制方便测试 **/ &&
|
||||
(stringutil.VersionCompare(versionResp.Version, "0.6.4" /** 从0.6.4开始支持 **/) < 0 || versionResp.Os != runtime.GOOS || versionResp.Arch != runtime.GOARCH) {
|
||||
return errors.New("could not upgrade api node v" + versionResp.Version + "/" + versionResp.Os + "/" + versionResp.Arch)
|
||||
}
|
||||
|
||||
// 检查本地文件版本
|
||||
canUpgrade, reason := CanUpgrade(versionResp.Version, versionResp.Os, versionResp.Arch)
|
||||
if !canUpgrade {
|
||||
return errors.New(reason)
|
||||
}
|
||||
|
||||
localVersion, err := lookupLocalVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("lookup version failed: %w", err)
|
||||
}
|
||||
|
||||
// 检查要升级的文件
|
||||
var gzFile = this.apiExe + "." + localVersion + ".gz"
|
||||
|
||||
gzReader, err := os.Open(gzFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
err = func() error {
|
||||
// 压缩文件
|
||||
exeReader, err := os.Open(this.apiExe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = exeReader.Close()
|
||||
}()
|
||||
var tmpGzFile = gzFile + ".tmp"
|
||||
gzFileWriter, err := os.OpenFile(tmpGzFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var gzWriter = gzip.NewWriter(gzFileWriter)
|
||||
defer func() {
|
||||
_ = gzWriter.Close()
|
||||
_ = gzFileWriter.Close()
|
||||
|
||||
_ = os.Rename(tmpGzFile, gzFile)
|
||||
}()
|
||||
_, err = io.Copy(gzWriter, exeReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gzReader, err = os.Open(gzFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = gzReader.Close()
|
||||
}()
|
||||
|
||||
// 开始上传
|
||||
var hash = md5.New()
|
||||
var buf = make([]byte, 128*4096)
|
||||
var isFirst = true
|
||||
stat, err := gzReader.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var totalSize = stat.Size()
|
||||
if totalSize == 0 {
|
||||
_ = gzReader.Close()
|
||||
_ = os.Remove(gzFile)
|
||||
return errors.New("invalid gz file")
|
||||
}
|
||||
|
||||
var uploadedSize int64 = 0
|
||||
for {
|
||||
n, err := gzReader.Read(buf)
|
||||
if n > 0 {
|
||||
// 计算Hash
|
||||
hash.Write(buf[:n])
|
||||
|
||||
// 上传
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
|
||||
Filename: filepath.Base(this.apiExe),
|
||||
Sum: "",
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: false,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
// 进度
|
||||
uploadedSize += int64(n)
|
||||
this.progress = &Progress{Percent: float64(uploadedSize*100) / float64(totalSize)}
|
||||
}
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
if err == io.EOF {
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
|
||||
Filename: filepath.Base(this.apiExe),
|
||||
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: true,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 升级边缘节点
|
||||
func (this *Upgrader) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
// 本地的
|
||||
var manager = NewDeployManager()
|
||||
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||
for _, deployFile := range manager.LoadNodeFiles() {
|
||||
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||
}
|
||||
|
||||
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
|
||||
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||
}
|
||||
|
||||
// 对比版本
|
||||
for key, deployFile := range localFileMap {
|
||||
remoteDeployFile, ok := remoteFileMap[key]
|
||||
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload deploy file '%s' failed: %w", filepath.Base(deployFile.Path), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 升级NS节点
|
||||
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
|
||||
// 本地的
|
||||
var manager = NewDeployManager()
|
||||
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
|
||||
for _, deployFile := range manager.LoadNSNodeFiles() {
|
||||
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
|
||||
}
|
||||
|
||||
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
|
||||
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
|
||||
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
|
||||
}
|
||||
|
||||
// 对比版本
|
||||
for key, deployFile := range localFileMap {
|
||||
remoteDeployFile, ok := remoteFileMap[key]
|
||||
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
|
||||
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("upload deploy file '%s' failed: %w", filepath.Base(deployFile.Path), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 上传节点文件
|
||||
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
|
||||
var buf = make([]byte, 128*4096)
|
||||
var isFirst = true
|
||||
|
||||
var hash = md5.New()
|
||||
|
||||
for {
|
||||
n, err := fp.Read(buf)
|
||||
if n > 0 {
|
||||
hash.Write(buf[:n])
|
||||
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||
Filename: filepath.Base(path),
|
||||
Sum: "",
|
||||
ChunkData: buf[:n],
|
||||
IsFirstChunk: isFirst,
|
||||
IsLastChunk: false,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
isFirst = false
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
|
||||
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
|
||||
Filename: filepath.Base(path),
|
||||
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
|
||||
ChunkData: nil,
|
||||
IsFirstChunk: false,
|
||||
IsLastChunk: true,
|
||||
})
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
22
EdgeAdmin/internal/utils/apinodeutils/upgrader_test.go
Normal file
22
EdgeAdmin/internal/utils/apinodeutils/upgrader_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpgrader_CanUpgrade(t *testing.T) {
|
||||
t.Log(apinodeutils.CanUpgrade("0.6.3", runtime.GOOS, runtime.GOARCH))
|
||||
}
|
||||
|
||||
func TestUpgrader_Upgrade(t *testing.T) {
|
||||
var upgrader = apinodeutils.NewUpgrader(1)
|
||||
err := upgrader.Upgrade()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
77
EdgeAdmin/internal/utils/apinodeutils/utils.go
Normal file
77
EdgeAdmin/internal/utils/apinodeutils/utils.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package apinodeutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool, reason string) {
|
||||
if len(apiVersion) == 0 {
|
||||
return false, "current api version should not be empty"
|
||||
}
|
||||
|
||||
if stringutil.VersionCompare(apiVersion, "0.6.4") < 0 {
|
||||
return false, "api node version must greater than or equal to 0.6.4"
|
||||
}
|
||||
|
||||
if osName != runtime.GOOS {
|
||||
return false, "os not match: " + osName
|
||||
}
|
||||
if arch != runtime.GOARCH {
|
||||
return false, "arch not match: " + arch
|
||||
}
|
||||
|
||||
stat, err := os.Stat(apiExe())
|
||||
if err != nil {
|
||||
return false, "stat error: " + err.Error()
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return false, "is directory"
|
||||
}
|
||||
|
||||
localVersion, err := lookupLocalVersion()
|
||||
if err != nil {
|
||||
return false, "lookup version failed: " + err.Error()
|
||||
}
|
||||
if localVersion != teaconst.APINodeVersion {
|
||||
return false, "not newest api node"
|
||||
}
|
||||
if stringutil.VersionCompare(localVersion, apiVersion) <= 0 {
|
||||
return false, "need not upgrade, local '" + localVersion + "' vs remote '" + apiVersion + "'"
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func lookupLocalVersion() (string, error) {
|
||||
var cmd = exec.Command(apiExe(), "-V")
|
||||
var output = &bytes.Buffer{}
|
||||
cmd.Stdout = output
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var localVersion = strings.TrimSpace(output.String())
|
||||
|
||||
// 检查版本号
|
||||
var reg = regexp.MustCompile(`^[\d.]+$`)
|
||||
if !reg.MatchString(localVersion) {
|
||||
return "", errors.New("lookup version failed: " + localVersion)
|
||||
}
|
||||
|
||||
return localVersion, nil
|
||||
}
|
||||
|
||||
func apiExe() string {
|
||||
return Tea.Root + "/edge-api/bin/edge-api"
|
||||
}
|
||||
12
EdgeAdmin/internal/utils/dateutils/utils.go
Normal file
12
EdgeAdmin/internal/utils/dateutils/utils.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dateutils
|
||||
|
||||
// SplitYmd 分隔Ymd格式的日期
|
||||
// Ymd => Y-m-d
|
||||
func SplitYmd(day string) string {
|
||||
if len(day) != 8 {
|
||||
return day
|
||||
}
|
||||
return day[:4] + "-" + day[4:6] + "-" + day[6:]
|
||||
}
|
||||
12
EdgeAdmin/internal/utils/email.go
Normal file
12
EdgeAdmin/internal/utils/email.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
var emailReg = regexp.MustCompile(`(?i)^[a-z\d]+([._+-]*[a-z\d]+)*@([a-z\d]+[a-z\d-]*[a-z\d]+\.)+[a-z\d]+$`)
|
||||
|
||||
// ValidateEmail 校验电子邮箱格式
|
||||
func ValidateEmail(email string) bool {
|
||||
return emailReg.MatchString(email)
|
||||
}
|
||||
22
EdgeAdmin/internal/utils/email_test.go
Normal file
22
EdgeAdmin/internal/utils/email_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateEmail(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("a.b.c.d@gmail.com"))
|
||||
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com.cn"))
|
||||
a.IsTrue(utils.ValidateEmail("hello.world.123@gmail.123.com"))
|
||||
a.IsTrue(utils.ValidateEmail("10000@qq.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa.@gmail.com"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@gmail"))
|
||||
a.IsFalse(utils.ValidateEmail("aaaa@123"))
|
||||
}
|
||||
8
EdgeAdmin/internal/utils/errors.go
Normal file
8
EdgeAdmin/internal/utils/errors.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package utils
|
||||
|
||||
import "github.com/iwind/TeaGo/logs"
|
||||
|
||||
func PrintError(err error) {
|
||||
// TODO 记录调用的文件名、行数
|
||||
logs.Println("[ERROR]" + err.Error())
|
||||
}
|
||||
308
EdgeAdmin/internal/utils/exce/excelize.go
Normal file
308
EdgeAdmin/internal/utils/exce/excelize.go
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
@Author: 1usir
|
||||
@Description:
|
||||
@File: excelize
|
||||
@Version: 1.0.0
|
||||
@Date: 2024/2/21 14:10
|
||||
*/
|
||||
|
||||
package exce
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Name string `json:"name"` // 序号 - 规则集名字
|
||||
Type string `json:"type"` // 攻击类型 - 规则分组名称
|
||||
Regular string `json:"regular"` // 正则
|
||||
Regulars []string `json:"regulars"` // 正则集合
|
||||
Level string `json:"level"` // 威胁等级
|
||||
Position []string `json:"position"` // 参数位置 - 参数
|
||||
Description string `json:"description"` // 描述 - 备注
|
||||
CVE string `json:"cve"` // cve 编号
|
||||
Inbound bool `json:"inbound"` // 入站规则
|
||||
Outbound bool `json:"outbound"` // 出站规则
|
||||
IsAnd bool `json:"is_and"` // 多条件
|
||||
}
|
||||
|
||||
func saveErr(sheet string, nf *excelize.File, cols []string, sheetIndexs map[string]int) {
|
||||
index, ok := sheetIndexs[sheet]
|
||||
if !ok {
|
||||
nf.NewSheet(sheet)
|
||||
// 设置单元格的值
|
||||
nf.SetCellValue(sheet, "A1", "序号")
|
||||
nf.SetCellValue(sheet, "B1", "攻击类型")
|
||||
nf.SetCellValue(sheet, "C1", "关键词")
|
||||
nf.SetCellValue(sheet, "D1", "正则")
|
||||
nf.SetCellValue(sheet, "E1", "威胁等级")
|
||||
nf.SetCellValue(sheet, "F1", "攻击语句")
|
||||
nf.SetCellValue(sheet, "G1", "攻击语句解码后")
|
||||
nf.SetCellValue(sheet, "H1", "参数位置")
|
||||
nf.SetCellValue(sheet, "I1", "描述")
|
||||
nf.SetCellValue(sheet, "J1", "CVE编号")
|
||||
nf.SetCellValue(sheet, "K1", "备注")
|
||||
nf.SetCellValue(sheet, "L1", "错误原因")
|
||||
sheetIndexs[sheet] = 2
|
||||
index = 2
|
||||
}
|
||||
for i, col := range cols {
|
||||
nf.SetCellValue(sheet, fmt.Sprintf("%c%d", 'A'+i, index), col)
|
||||
}
|
||||
sheetIndexs[sheet]++
|
||||
}
|
||||
|
||||
func ParseRules(r io.Reader) (*bytes.Buffer, []*Rule, error) {
|
||||
nf := excelize.NewFile()
|
||||
nf.DeleteSheet("Sheet1")
|
||||
f, err := excelize.OpenReader(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
res := make([]*Rule, 0)
|
||||
sheets := f.GetSheetList()
|
||||
sheetIndexs := map[string]int{}
|
||||
for _, sheet := range sheets {
|
||||
rows, err := f.GetRows(sheet)
|
||||
if err != nil || len(rows) <= 1 {
|
||||
return nil, nil, err
|
||||
}
|
||||
/*
|
||||
1 2 3 4 5 6 7 8 9 10 11 12
|
||||
序号|攻击类型|关键字|正则|威胁等级|攻击语句|攻击语句解码后|参数位置|描述|CVE编号|备注|错误原因
|
||||
*/
|
||||
|
||||
for _, row := range rows[1:] {
|
||||
cols := make([]string, 12)
|
||||
copy(cols, row)
|
||||
if len(cols) < 8 || cols[0] == "" || cols[1] == "" || cols[3] == "" || cols[7] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
r := &Rule{
|
||||
Name: strings.TrimSpace(cols[0]),
|
||||
Type: strings.TrimSpace(cols[1]),
|
||||
Regular: strings.TrimSpace(cols[3]),
|
||||
Level: strings.TrimSpace(cols[4]),
|
||||
Position: strings.Split(cols[7], "\n"),
|
||||
}
|
||||
if strings.Contains(r.Regular, "\n") {
|
||||
//fmt.Println(fmt.Sprintf("无效规则1:Sheet[%s|%s] %s", sheet, r.Name, r.Regular))
|
||||
//return nil, errors.New(fmt.Sprintf("无效规则:Sheet[%s|%s] %s", sheet, r.Name, r.Regular))
|
||||
// 创建错误新表格
|
||||
cols[11] = "无效正则"
|
||||
saveErr(sheet, nf, cols, sheetIndexs)
|
||||
continue
|
||||
}
|
||||
if len(cols) > 8 {
|
||||
r.Description = cols[8]
|
||||
}
|
||||
if len(cols) > 9 {
|
||||
r.CVE = cols[9]
|
||||
}
|
||||
// 特殊处理
|
||||
if r.Type == "xss注入" {
|
||||
r.Type = "XSS"
|
||||
}
|
||||
// 支持多条件
|
||||
var regulars []string
|
||||
var positions []string
|
||||
|
||||
if strings.Contains(r.Regular, "且") {
|
||||
regulars, positions, err = parseRegulars(r.Regular)
|
||||
if err != nil {
|
||||
//fmt.Println(fmt.Sprintf("多规则解析失败:Sheet[%s|%s] %s %s", sheet, r.Name, r.Regular, err))
|
||||
cols[11] = "多规则解析失败"
|
||||
saveErr(sheet, nf, cols, sheetIndexs)
|
||||
continue
|
||||
}
|
||||
r.IsAnd = true
|
||||
} else {
|
||||
regulars = []string{r.Regular}
|
||||
}
|
||||
for _, regular := range regulars {
|
||||
// 校验正则参数是否合理
|
||||
rule := &firewallconfigs.HTTPFirewallRule{
|
||||
IsOn: true,
|
||||
Operator: "match",
|
||||
Value: regular,
|
||||
IsCaseInsensitive: true,
|
||||
}
|
||||
if err := rule.Init(); err != nil {
|
||||
//fmt.Println(fmt.Sprintf("无效正则规则:Sheet[%s|%s] %s", sheet, r.Name, r.Regular))
|
||||
// 创建错误新表格
|
||||
cols[11] = "正则解析失败"
|
||||
saveErr(sheet, nf, cols, sheetIndexs)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if r.IsAnd {
|
||||
r.Regulars = regulars
|
||||
r.Position = r.setString(positions)
|
||||
} else {
|
||||
// position 格式化去重
|
||||
r.Position = r.setString(r.Position)
|
||||
}
|
||||
res = append(res, r)
|
||||
}
|
||||
}
|
||||
//nf.SaveAs("/Users/1usir/works/waf/open-waf/waf/EdgeAdmin/internal/utils/exce/WAF ALL Error.xlsx")
|
||||
if len(sheetIndexs) > 0 {
|
||||
_ = nf.DeleteSheet("Sheet1")
|
||||
buff, err := nf.WriteToBuffer()
|
||||
return buff, res, err
|
||||
} else {
|
||||
return nil, res, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Rule) formatPosition(p string) string {
|
||||
switch p {
|
||||
case "REQUEST_FILENAME", "REQUEST_FILENAM":
|
||||
this.Inbound = true
|
||||
return "${requestUpload.name}"
|
||||
case "ARGS_NAMES", "ARGS":
|
||||
this.Inbound = true
|
||||
return "${args}"
|
||||
case "REQUEST_BODY", "body":
|
||||
this.Inbound = true
|
||||
return "${requestBody}"
|
||||
case "REQUEST_HEADERS", "head", "header", "headers":
|
||||
this.Inbound = true
|
||||
return "${headers}"
|
||||
case "REQUEST_HEADERS_NAMES":
|
||||
this.Inbound = true
|
||||
return "${headerNames}"
|
||||
case "REQUEST_COOKIES_NAMES", "REQUEST_COOKIES", "cookie":
|
||||
this.Inbound = true
|
||||
return "${cookies}"
|
||||
case "url", "uri", "REQUEST_RAW_URI", "REQUEST_URI":
|
||||
this.Inbound = true
|
||||
return "${requestURI}"
|
||||
case "RESPONSE_BODY":
|
||||
this.Outbound = true
|
||||
return "${responseBody}"
|
||||
case "CONTENT_TYPE":
|
||||
return "${contentType}"
|
||||
case "referer":
|
||||
return "${referer}"
|
||||
case "host":
|
||||
return "${host}"
|
||||
default:
|
||||
if strings.HasPrefix(p, "${") && strings.HasSuffix(p, "}") {
|
||||
return p
|
||||
}
|
||||
//fmt.Println("=========>?", p)
|
||||
//panic(p)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// 元素去重
|
||||
func (this *Rule) setString(slice []string) []string {
|
||||
|
||||
// 解析位置
|
||||
p := []string{}
|
||||
for _, v := range slice {
|
||||
if strings.Contains(v, "ARGS_NAMES_LIST") {
|
||||
p = append(p, "uri")
|
||||
} else if strings.Contains(v, "REQUEST_HEADER_FIELDS") {
|
||||
p = append(p, "header")
|
||||
} else if strings.Contains(v, "COOKIES_NAMES_LIST") {
|
||||
p = append(p, "REQUEST_COOKIES_NAMES")
|
||||
} else if strings.Contains(v, ",") {
|
||||
p = append(p, strings.Split(v, ",")...)
|
||||
} else if strings.Contains(v, ",") {
|
||||
p = append(p, strings.Split(v, ",")...)
|
||||
} else {
|
||||
p = append(p, v)
|
||||
}
|
||||
}
|
||||
slice = p
|
||||
res := make([]string, 0, len(slice))
|
||||
m := map[string]int{}
|
||||
for _, v := range slice {
|
||||
v = this.formatPosition(v)
|
||||
_, ok := m[v]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
if v == "${headers}" || v == "${headerNames}" { // headers 包含headersNames 如果存在headers时 headersName 可以忽略
|
||||
_, ok1 := m["${headers}"]
|
||||
idx2, ok2 := m["${headerNames}"]
|
||||
if ok2 {
|
||||
res[idx2] = "${headers}"
|
||||
delete(m, "${headerNames}")
|
||||
m["{headers}"] = idx2
|
||||
continue
|
||||
}
|
||||
if ok1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
m[v] = len(res)
|
||||
res = append(res, v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 支持多条件
|
||||
/*
|
||||
uri:\/wp-login\.php且body:.{256,}
|
||||
uri:\/goform\/_aslvl且body:SAPassword=W2402
|
||||
*/
|
||||
func parseRegulars(conditions string) ([]string, []string, error) {
|
||||
|
||||
getFieldFunc := func(s string) (func(string) (string, func(string) string), string) {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "uri", "body", "host", "header", "headers", "head", "cookie", "referer":
|
||||
return nil, s
|
||||
case "user-agent", "ua":
|
||||
return nil, "${userAgent}"
|
||||
case "authorization":
|
||||
return func(s string) (string, func(string) string) {
|
||||
return "${headers}", func(s string) string {
|
||||
return "authorization:" + s
|
||||
}
|
||||
}, ""
|
||||
default:
|
||||
return nil, ""
|
||||
}
|
||||
}
|
||||
cdts := strings.Split(conditions, "且")
|
||||
var regulars []string
|
||||
var positions []string
|
||||
for _, cdt := range cdts {
|
||||
i := strings.Index(cdt, ":")
|
||||
if i == -1 { // 错误
|
||||
return nil, nil, errors.New("invalid " + cdt)
|
||||
}
|
||||
// 提取position
|
||||
nextFc, field := getFieldFunc(cdt[:i])
|
||||
var position, regular string
|
||||
if nextFc == nil && field == "" { // 无法识别
|
||||
return nil, nil, errors.New("invalid " + cdt)
|
||||
}
|
||||
if nextFc != nil {
|
||||
field, getRegularFc := nextFc(cdt[i+1:])
|
||||
if field == "" || getRegularFc == nil { // 无效正则
|
||||
return nil, nil, errors.New("invalid " + cdt)
|
||||
}
|
||||
position = field
|
||||
regular = getRegularFc(cdt[i+1:])
|
||||
} else {
|
||||
position = field
|
||||
regular = cdt[i+1:]
|
||||
}
|
||||
regulars = append(regulars, regular)
|
||||
positions = append(positions, position)
|
||||
}
|
||||
return regulars, positions, nil
|
||||
}
|
||||
34
EdgeAdmin/internal/utils/exce/excelize_test.go
Normal file
34
EdgeAdmin/internal/utils/exce/excelize_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
@Author: 1usir
|
||||
@Description:
|
||||
@File: excelize_test
|
||||
@Version: 1.0.0
|
||||
@Date: 2024/2/21 17:37
|
||||
*/
|
||||
|
||||
package exce
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseRule(t *testing.T) {
|
||||
f, err := os.Open("/Users/1usir/works/waf/open-waf/waf/EdgeAdmin/internal/utils/exce/WAF ALL.xlsx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, rs, err := ParseRules(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, v := range rs {
|
||||
for _, arg := range v.Position {
|
||||
if arg == "{responseBody}" && v.Outbound {
|
||||
fmt.Println(v.Name, v.Type, v.Regular, v.Position, v.Inbound, v.Outbound)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
162
EdgeAdmin/internal/utils/exec/cmd.go
Normal file
162
EdgeAdmin/internal/utils/exec/cmd.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package executils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cmd struct {
|
||||
name string
|
||||
args []string
|
||||
env []string
|
||||
dir string
|
||||
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
cancelFunc func()
|
||||
|
||||
captureStdout bool
|
||||
captureStderr bool
|
||||
|
||||
stdout *bytes.Buffer
|
||||
stderr *bytes.Buffer
|
||||
|
||||
rawCmd *exec.Cmd
|
||||
}
|
||||
|
||||
func NewCmd(name string, args ...string) *Cmd {
|
||||
return &Cmd{
|
||||
name: name,
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
|
||||
return (&Cmd{
|
||||
name: name,
|
||||
args: args,
|
||||
}).WithTimeout(timeout)
|
||||
}
|
||||
|
||||
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
|
||||
this.timeout = timeout
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
this.ctx = ctx
|
||||
this.cancelFunc = cancelFunc
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithStdout() *Cmd {
|
||||
this.captureStdout = true
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithStderr() *Cmd {
|
||||
this.captureStderr = true
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithEnv(env []string) *Cmd {
|
||||
this.env = env
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithDir(dir string) *Cmd {
|
||||
this.dir = dir
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) Start() error {
|
||||
var cmd = this.compose()
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func (this *Cmd) Wait() error {
|
||||
var cmd = this.compose()
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
||||
func (this *Cmd) Run() error {
|
||||
if this.cancelFunc != nil {
|
||||
defer this.cancelFunc()
|
||||
}
|
||||
|
||||
var cmd = this.compose()
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (this *Cmd) RawStdout() string {
|
||||
if this.stdout != nil {
|
||||
return this.stdout.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *Cmd) Stdout() string {
|
||||
return strings.TrimSpace(this.RawStdout())
|
||||
}
|
||||
|
||||
func (this *Cmd) RawStderr() string {
|
||||
if this.stderr != nil {
|
||||
return this.stderr.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *Cmd) Stderr() string {
|
||||
return strings.TrimSpace(this.RawStderr())
|
||||
}
|
||||
|
||||
func (this *Cmd) String() string {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd.String()
|
||||
}
|
||||
var newCmd = exec.Command(this.name, this.args...)
|
||||
return newCmd.String()
|
||||
}
|
||||
|
||||
func (this *Cmd) Process() *os.Process {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd.Process
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Cmd) compose() *exec.Cmd {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd
|
||||
}
|
||||
|
||||
if this.ctx != nil {
|
||||
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
|
||||
} else {
|
||||
this.rawCmd = exec.Command(this.name, this.args...)
|
||||
}
|
||||
|
||||
if this.env != nil {
|
||||
this.rawCmd.Env = this.env
|
||||
}
|
||||
|
||||
if len(this.dir) > 0 {
|
||||
this.rawCmd.Dir = this.dir
|
||||
}
|
||||
|
||||
if this.captureStdout {
|
||||
this.stdout = &bytes.Buffer{}
|
||||
this.rawCmd.Stdout = this.stdout
|
||||
}
|
||||
if this.captureStderr {
|
||||
this.stderr = &bytes.Buffer{}
|
||||
this.rawCmd.Stderr = this.stderr
|
||||
}
|
||||
|
||||
return this.rawCmd
|
||||
}
|
||||
61
EdgeAdmin/internal/utils/exec/cmd_test.go
Normal file
61
EdgeAdmin/internal/utils/exec/cmd_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package executils_test
|
||||
|
||||
import (
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTimeoutCmd_Sleep(t *testing.T) {
|
||||
var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo(t *testing.T) {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo2(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "hello")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("raw stdout:", cmd.RawStdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
t.Log("raw stderr:", cmd.RawStderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo3(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestCmd_Process(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log(cmd.Process())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_String(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
t.Log("stdout:", cmd.String())
|
||||
}
|
||||
58
EdgeAdmin/internal/utils/exec/look_linux.go
Normal file
58
EdgeAdmin/internal/utils/exec/look_linux.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build linux
|
||||
|
||||
package executils
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// LookPath customize our LookPath() function, to work in broken $PATH environment variable
|
||||
func LookPath(file string) (string, error) {
|
||||
result, err := exec.LookPath(file)
|
||||
if err == nil && len(result) > 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// add common dirs contains executable files these may be excluded in $PATH environment variable
|
||||
var binPaths = []string{
|
||||
"/usr/sbin",
|
||||
"/usr/bin",
|
||||
"/usr/local/sbin",
|
||||
"/usr/local/bin",
|
||||
}
|
||||
|
||||
for _, binPath := range binPaths {
|
||||
var fullPath = binPath + string(os.PathSeparator) + file
|
||||
|
||||
stat, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return "", syscall.EISDIR
|
||||
}
|
||||
|
||||
var mode = stat.Mode()
|
||||
if mode.IsDir() {
|
||||
return "", syscall.EISDIR
|
||||
}
|
||||
err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS)
|
||||
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
|
||||
return fullPath, err
|
||||
}
|
||||
if mode&0111 != 0 {
|
||||
return fullPath, nil
|
||||
}
|
||||
return "", fs.ErrPermission
|
||||
}
|
||||
|
||||
return "", &exec.Error{
|
||||
Name: file,
|
||||
Err: exec.ErrNotFound,
|
||||
}
|
||||
}
|
||||
10
EdgeAdmin/internal/utils/exec/look_others.go
Normal file
10
EdgeAdmin/internal/utils/exec/look_others.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !linux
|
||||
|
||||
package executils
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func LookPath(file string) (string, error) {
|
||||
return exec.LookPath(file)
|
||||
}
|
||||
29
EdgeAdmin/internal/utils/firewall.go
Normal file
29
EdgeAdmin/internal/utils/firewall.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func AddPortsToFirewall(ports []int) {
|
||||
for _, port := range ports {
|
||||
// Linux
|
||||
if runtime.GOOS == "linux" {
|
||||
// firewalld
|
||||
firewallCmd, _ := executils.LookPath("firewall-cmd")
|
||||
if len(firewallCmd) > 0 {
|
||||
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
|
||||
if err == nil {
|
||||
logs.Println("ADMIN_NODE", "add port '"+types.String(port)+"' to firewalld")
|
||||
|
||||
_ = exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp", "--permanent").Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
174
EdgeAdmin/internal/utils/ip_utils.go
Normal file
174
EdgeAdmin/internal/utils/ip_utils.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseIPValue 解析IP值
|
||||
func ParseIPValue(value string) (newValue string, ipFrom string, ipTo string, ok bool) {
|
||||
if len(value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newValue = value
|
||||
|
||||
// ip1-ip2
|
||||
if strings.Contains(value, "-") {
|
||||
var pieces = strings.Split(value, "-")
|
||||
if len(pieces) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
ipFrom = strings.TrimSpace(pieces[0])
|
||||
ipTo = strings.TrimSpace(pieces[1])
|
||||
|
||||
if !iputils.IsValid(ipFrom) || !iputils.IsValid(ipTo) {
|
||||
return
|
||||
}
|
||||
|
||||
if !iputils.IsSameVersion(ipFrom, ipTo) {
|
||||
return
|
||||
}
|
||||
|
||||
if iputils.CompareIP(ipFrom, ipTo) > 0 {
|
||||
ipFrom, ipTo = ipTo, ipFrom
|
||||
newValue = ipFrom + "-" + ipTo
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// ip/mask
|
||||
if strings.Contains(value, "/") {
|
||||
cidr, err := iputils.ParseCIDR(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return newValue, cidr.From().String(), cidr.To().String(), true
|
||||
}
|
||||
|
||||
// single value
|
||||
if iputils.IsValid(value) {
|
||||
ipFrom = value
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ExtractIP 分解IP
|
||||
// 只支持D段掩码的CIDR
|
||||
// 最多只记录255个值
|
||||
func ExtractIP(ipStrings string) ([]string, error) {
|
||||
ipStrings = strings.ReplaceAll(ipStrings, " ", "")
|
||||
|
||||
// CIDR
|
||||
if strings.Contains(ipStrings, "/") {
|
||||
_, cidrNet, err := net.ParseCIDR(ipStrings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var index = strings.Index(ipStrings, "/")
|
||||
var ipFrom = ipStrings[:index]
|
||||
var bits = types.Int(ipStrings[index+1:])
|
||||
if bits < 24 {
|
||||
return nil, errors.New("CIDR bits should be greater than 24")
|
||||
}
|
||||
|
||||
var ipv4 = net.ParseIP(ipFrom).To4()
|
||||
if len(ipv4) == 0 {
|
||||
return nil, errors.New("support IPv4 only")
|
||||
}
|
||||
|
||||
var result = []string{}
|
||||
ipv4[3] = 0 // 从0开始
|
||||
for i := 0; i <= 255; i++ {
|
||||
if cidrNet.Contains(ipv4) {
|
||||
result = append(result, ipv4.String())
|
||||
}
|
||||
ipv4 = NextIP(ipv4)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IP Range
|
||||
if strings.Contains(ipStrings, "-") {
|
||||
var index = strings.Index(ipStrings, "-")
|
||||
var ipFromString = ipStrings[:index]
|
||||
var ipToString = ipStrings[index+1:]
|
||||
|
||||
var ipFrom = net.ParseIP(ipFromString).To4()
|
||||
if len(ipFrom) == 0 {
|
||||
return nil, errors.New("invalid ip '" + ipFromString + "'")
|
||||
}
|
||||
|
||||
var ipTo = net.ParseIP(ipToString).To4()
|
||||
if len(ipTo) == 0 {
|
||||
return nil, errors.New("invalid ip '" + ipToString + "'")
|
||||
}
|
||||
|
||||
if bytes.Compare(ipFrom, ipTo) > 0 {
|
||||
ipFrom, ipTo = ipTo, ipFrom
|
||||
}
|
||||
|
||||
var result = []string{}
|
||||
for i := 0; i < 255; i++ {
|
||||
if bytes.Compare(ipFrom, ipTo) > 0 {
|
||||
break
|
||||
}
|
||||
result = append(result, ipFrom.String())
|
||||
ipFrom = NextIP(ipFrom)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return []string{ipStrings}, nil
|
||||
}
|
||||
|
||||
// NextIP IP增加1
|
||||
func NextIP(prevIP net.IP) net.IP {
|
||||
var ip = make(net.IP, len(prevIP))
|
||||
copy(ip, prevIP)
|
||||
var index = len(ip) - 1
|
||||
for {
|
||||
if ip[index] == 255 {
|
||||
ip[index] = 0
|
||||
index--
|
||||
if index < 0 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
ip[index]++
|
||||
break
|
||||
}
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// IsLocalIP 判断是否为本地IP
|
||||
// ip 是To4()或者To16()的结果
|
||||
func IsLocalIP(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ip[0] == 127 ||
|
||||
ip[0] == 10 ||
|
||||
(ip[0] == 172 && ip[1]&0xf0 == 16) ||
|
||||
(ip[0] == 192 && ip[1] == 168) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ip.String() == "::1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
30
EdgeAdmin/internal/utils/ip_utils_test.go
Normal file
30
EdgeAdmin/internal/utils/ip_utils_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractIP(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.1.100"))
|
||||
}
|
||||
|
||||
func TestExtractIP_CIDR(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.2.100/24"))
|
||||
}
|
||||
|
||||
func TestExtractIP_Range(t *testing.T) {
|
||||
t.Log(ExtractIP("192.168.2.100 - 192.168.4.2"))
|
||||
}
|
||||
|
||||
func TestNextIP(t *testing.T) {
|
||||
for _, ip := range []string{"192.168.1.1", "0.0.0.0", "255.255.255.255", "192.168.2.255", "192.168.255.255"} {
|
||||
t.Log(ip+":", NextIP(net.ParseIP(ip).To4()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextIP_Copy(t *testing.T) {
|
||||
var ip = net.ParseIP("192.168.1.100")
|
||||
var nextIP = NextIP(ip)
|
||||
t.Log(ip, nextIP)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user