增加恢复模式

This commit is contained in:
GoEdgeLab
2021-07-20 17:15:17 +08:00
parent 320b325496
commit 064bf2016b
34 changed files with 948 additions and 285 deletions

View File

@@ -2,17 +2,19 @@ package apps
import (
"fmt"
"github.com/iwind/TeaGo/Tea"
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"
"runtime"
"strconv"
"syscall"
"time"
)
// App命令帮助
// AppCmd App命令帮助
type AppCmd struct {
product string
version string
@@ -21,10 +23,14 @@ type AppCmd struct {
appendStrings []string
directives []*Directive
sock *gosock.Sock
}
func NewAppCmd() *AppCmd {
return &AppCmd{}
return &AppCmd{
sock: gosock.NewTmpSock(teaconst.ProcessName),
}
}
type CommandHelpOption struct {
@@ -32,25 +38,25 @@ type CommandHelpOption struct {
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.usage = usage
return this
}
// 选项
// Option 选项
func (this *AppCmd) Option(code string, description string) *AppCmd {
this.options = append(this.options, &CommandHelpOption{
Code: code,
@@ -59,13 +65,13 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
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)
@@ -104,7 +110,7 @@ func (this *AppCmd) Print() {
}
}
// 添加指令
// On 添加指令
func (this *AppCmd) On(arg string, callback func()) {
this.directives = append(this.directives, &Directive{
Arg: arg,
@@ -112,7 +118,7 @@ func (this *AppCmd) On(arg string, callback func()) {
})
}
// 运行
// Run 运行
func (this *AppCmd) Run(main func()) {
// 获取参数
args := os.Args[1:]
@@ -151,9 +157,6 @@ func (this *AppCmd) Run(main func()) {
return
}
// 记录PID
_ = this.writePid()
// 日志
writer := new(LogWriter)
writer.Init()
@@ -175,9 +178,9 @@ func (this *AppCmd) runHelp() {
// 启动
func (this *AppCmd) runStart() {
proc := this.checkPid()
if proc != nil {
fmt.Println(this.product+" already started, pid:", proc.Pid)
var pid = this.getPID()
if pid > 0 {
fmt.Println(this.product+" already started, pid:", pid)
return
}
@@ -193,18 +196,15 @@ func (this *AppCmd) runStart() {
// 停止
func (this *AppCmd) runStop() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
return
}
// 停止进程
_ = proc.Signal(syscall.SIGQUIT)
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
// 在Windows上经常不能及时释放资源
_ = DeletePid(Tea.Root + "/bin/pid")
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
}
// 重启
@@ -216,20 +216,24 @@ func (this *AppCmd) runRestart() {
// 状态
func (this *AppCmd) runStatus() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
} else {
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
return
}
fmt.Println(this.product + " is running, pid: " + types.String(pid))
}
// 检查PID
func (this *AppCmd) checkPid() *os.Process {
return CheckPid(Tea.Root + "/bin/pid")
}
// 获取当前的PID
func (this *AppCmd) getPID() int {
if !this.sock.IsListening() {
return 0
}
// 写入PID
func (this *AppCmd) writePid() error {
return WritePid(Tea.Root + "/bin/pid")
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
if err != nil {
return 0
}
return maps.NewMap(reply.Params).GetInt("pid")
}

View File

@@ -1,17 +0,0 @@
// +build !windows
package apps
import (
"os"
"syscall"
)
// lock file
func LockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
}
func UnlockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
}

View File

@@ -1,17 +0,0 @@
// +build windows
package apps
import (
"errors"
"os"
)
// lock file
func LockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}
func UnlockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}

View File

@@ -1,113 +0,0 @@
package apps
import (
"fmt"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"runtime"
)
var pidFileList = []*os.File{}
// 检查Pid
func CheckPid(path string) *os.Process {
// windows上打开的文件是不能删除的
if runtime.GOOS == "windows" {
if os.Remove(path) == nil {
return nil
}
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
// 是否能取得Lock
err = LockFile(file)
if err == nil {
_ = UnlockFile(file)
return nil
}
pidBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil
}
pid := types.Int(string(pidBytes))
if pid <= 0 {
return nil
}
proc, _ := os.FindProcess(pid)
return proc
}
// 写入Pid
func WritePid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
if err != nil {
return err
}
return nil
}
// 写入Ppid
func WritePpid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
if err != nil {
return err
}
return nil
}
// 删除Pid
func DeletePid(path string) error {
_, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return nil
}
return err
}
for _, fp := range pidFileList {
_ = UnlockFile(fp)
_ = fp.Close()
}
return os.Remove(path)
}

View File

@@ -108,7 +108,6 @@ func (this *APIConfig) WriteFile(path string) error {
if err != nil {
return err
}
err = ioutil.WriteFile(path, data, 0666)
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
// 写入 ~/.edge-admin/
@@ -141,5 +140,10 @@ func (this *APIConfig) WriteFile(path string) error {
}
}
return err
err = ioutil.WriteFile(path, data, 0666)
if err != nil {
return err
}
return nil
}

7
internal/const/vars.go Normal file
View File

@@ -0,0 +1,7 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package teaconst
var (
IsRecoverMode = false
)

View File

@@ -10,11 +10,12 @@ import (
"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/sessions"
"github.com/iwind/gosock/pkg/gosock"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/signal"
@@ -25,6 +26,7 @@ import (
var SharedAdminNode *AdminNode = nil
type AdminNode struct {
sock *gosock.Sock
subPIDs []int
}
@@ -42,7 +44,7 @@ func (this *AdminNode) Run() {
// 本地Sock
err := this.listenSock()
if err != nil {
logs.Println("NODE" + err.Error())
logs.Println("[NODE]", err.Error())
return
}
@@ -89,11 +91,11 @@ func (this *AdminNode) Run() {
// Daemon 实现守护进程
func (this *AdminNode) Daemon() {
path := os.TempDir() + "/edge-admin.sock"
var sock = gosock.NewTmpSock(teaconst.ProcessName)
isDebug := lists.ContainsString(os.Args, "debug")
isDebug = true
for {
conn, err := net.DialTimeout("unix", path, 1*time.Second)
conn, err := sock.Dial()
if err != nil {
if isDebug {
log.Println("[DAEMON]starting ...")
@@ -281,37 +283,59 @@ func (this *AdminNode) genSecret() string {
// 监听本地sock
func (this *AdminNode) listenSock() error {
path := os.TempDir() + "/edge-admin.sock"
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
// 检查是否已经存
_, err := os.Stat(path)
if err == nil {
conn, err := net.Dial("unix", path)
if err != nil {
_ = os.Remove(path)
// 检查是否在运行
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 {
_ = conn.Close()
return errors.New("error: the process is already running")
}
}
// 新的监听任务
listener, err := net.Listen("unix", path)
if err != nil {
return err
}
events.On(events.EventQuit, func() {
logs.Println("NODE", "quit unix sock")
_ = listener.Close()
})
// 启动监听
go func() {
for {
_, err := listener.Accept()
if err != nil {
return
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()
}
}
// 退出主进程
events.Notify(events.EventQuit)
os.Exit(0)
case "recover":
teaconst.IsRecoverMode = true
_ = 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
}

View File

@@ -51,6 +51,10 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
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())
}

View File

@@ -2,6 +2,7 @@ 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/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
@@ -44,7 +45,7 @@ func (this *SyncAPINodesTask) Start() {
func (this *SyncAPINodesTask) Loop() error {
// 如果还没有安装直接返回
if !setup.IsConfigured() {
if !setup.IsConfigured() || teaconst.IsRecoverMode {
return nil
}

View File

@@ -1,6 +1,7 @@
package tasks
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
@@ -40,7 +41,7 @@ func (this *SyncClusterTask) Start() {
func (this *SyncClusterTask) loop() error {
// 如果还没有安装直接返回
if !setup.IsConfigured() {
if !setup.IsConfigured() || teaconst.IsRecoverMode {
return nil
}

View File

@@ -0,0 +1,17 @@
package recover
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
if !teaconst.IsRecoverMode {
actionPtr.Object().RedirectURL("/")
return false
}
return true
}

View File

@@ -0,0 +1,17 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package recover
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
this.Show()
}

View File

@@ -0,0 +1,15 @@
package recover
import "github.com/iwind/TeaGo"
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(new(Helper)).
Prefix("/recover").
Get("", new(IndexAction)).
Post("/validateApi", new(ValidateApiAction)).
Post("/updateHosts", new(UpdateHostsAction)).
EndAll()
})
}

View File

@@ -0,0 +1,192 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package recover
import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
)
type UpdateHostsAction struct {
actionutils.ParentAction
}
func (this *UpdateHostsAction) RunPost(params struct {
Protocol string
Host string
Port string
NodeId string
NodeSecret string
OldHosts []string
NewHosts []string
}) {
if len(params.OldHosts) != len(params.NewHosts) {
this.Fail("参数配置错误,请刷新页面后重试")
}
client, err := rpc.NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
},
NodeId: params.NodeId,
Secret: params.NodeSecret,
})
if err != nil {
this.FailField("host", "测试API节点时出错请检查配置错误信息"+err.Error())
}
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.FailField("host", "无法连接此API节点错误信息"+err.Error())
}
// 获取管理员节点信息
apiTokensResp, err := client.APITokenRPC().FindAllEnabledAPITokens(client.APIContext(0), &pb.FindAllEnabledAPITokensRequest{Role: "admin"})
if err != nil {
this.Fail("读取管理员令牌失败:" + err.Error())
}
var apiTokens = apiTokensResp.ApiTokens
if len(apiTokens) == 0 {
this.Fail("数据库中没有管理员令牌信息,请确认数据是否完整")
}
var adminAPIToken = apiTokens[0]
// API节点列表
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.Fail("获取API节点列表失败错误信息" + err.Error())
}
var endpoints = []string{}
for _, node := range nodesResp.Nodes {
if !node.IsOn {
continue
}
// http
if len(node.HttpJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.HttpJSON = bytes.ReplaceAll(node.HttpJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// https
if len(node.HttpsJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.HttpsJSON = bytes.ReplaceAll(node.HttpsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// restHTTP
if len(node.RestHTTPJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.RestHTTPJSON = bytes.ReplaceAll(node.RestHTTPJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// restHTTPS
if len(node.RestHTTPSJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.RestHTTPSJSON = bytes.ReplaceAll(node.RestHTTPSJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
}
// access addrs
if len(node.AccessAddrsJSON) > 0 {
for index, oldHost := range params.OldHosts {
if len(params.NewHosts[index]) == 0 {
continue
}
node.AccessAddrsJSON = bytes.ReplaceAll(node.AccessAddrsJSON, []byte("\""+oldHost+"\""), []byte("\""+params.NewHosts[index]+"\""))
}
var addrs []*serverconfigs.NetworkAddressConfig
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
if err != nil {
this.Fail("读取节点访问地址失败:" + err.Error())
}
for _, addr := range addrs {
err = addr.Init()
if err != nil {
// 暂时不提示错误
continue
}
for _, a := range addr.FullAddresses() {
if !lists.ContainsString(endpoints, a) {
endpoints = append(endpoints, a)
}
}
}
}
// 保存
_, err = client.APINodeRPC().UpdateAPINode(client.Context(0), &pb.UpdateAPINodeRequest{
NodeId: node.Id,
Name: node.Name,
Description: node.Description,
HttpJSON: node.HttpJSON,
HttpsJSON: node.HttpsJSON,
AccessAddrsJSON: node.AccessAddrsJSON,
IsOn: node.IsOn,
RestIsOn: node.RestIsOn,
RestHTTPJSON: node.RestHTTPJSON,
RestHTTPSJSON: node.RestHTTPSJSON,
})
if err != nil {
this.Fail("保存API节点信息失败" + err.Error())
}
}
// 修改api.yaml
var apiConfig = &configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: endpoints,
},
NodeId: adminAPIToken.NodeId,
Secret: adminAPIToken.Secret,
}
err = apiConfig.WriteFile(Tea.Root + "/configs/api.yaml")
if err != nil {
this.Fail("保存configs/api.yaml失败" + err.Error())
}
// 加载api.yaml
rpcClient, err := rpc.SharedRPC()
if err != nil {
this.Fail("初始化RPC失败" + err.Error())
}
err = rpcClient.UpdateConfig(apiConfig)
if err != nil {
this.Fail("修改API配置失败" + err.Error())
}
// 退出恢复模式
teaconst.IsRecoverMode = false
this.Success()
}

View File

@@ -0,0 +1,153 @@
package recover
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"strings"
)
type ValidateApiAction struct {
actionutils.ParentAction
}
func (this *ValidateApiAction) RunPost(params struct {
Protocol string
Host string
Port string
NodeId string
NodeSecret string
Must *actions.Must
}) {
params.NodeId = strings.Trim(params.NodeId, "\"' ")
params.NodeSecret = strings.Trim(params.NodeSecret, "\"' ")
// 使用已有的API节点
params.Must.
Field("host", params.Host).
Require("请输入主机地址").
Field("port", params.Port).
Require("请输入服务端口").
Match(`^\d+$`, "服务端口只能是数字").
Field("nodeId", params.NodeId).
Require("请输入节点nodeId").
Field("nodeSecret", params.NodeSecret).
Require("请输入节点secret")
client, err := rpc.NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{params.Protocol + "://" + configutils.QuoteIP(params.Host) + ":" + params.Port},
},
NodeId: params.NodeId,
Secret: params.NodeSecret,
})
if err != nil {
this.FailField("host", "测试API节点时出错请检查配置错误信息"+err.Error())
}
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.FailField("host", "无法连接此API节点错误信息"+err.Error())
}
// API节点列表
nodesResp, err := client.APINodeRPC().FindAllEnabledAPINodes(client.Context(0), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.Fail("获取API节点列表失败错误信息" + err.Error())
}
var hosts = []string{}
for _, node := range nodesResp.Nodes {
if !node.IsOn {
continue
}
// http
if len(node.HttpJSON) > 0 {
var config = &serverconfigs.HTTPProtocolConfig{}
err = json.Unmarshal(node.HttpJSON, config)
if err != nil {
this.Fail("读取节点HTTP信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// https
if len(node.HttpsJSON) > 0 {
var config = &serverconfigs.HTTPSProtocolConfig{}
err = json.Unmarshal(node.HttpsJSON, config)
if err != nil {
this.Fail("读取节点HTTPS信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// restHTTP
if len(node.RestHTTPJSON) > 0 {
var config = &serverconfigs.HTTPProtocolConfig{}
err = json.Unmarshal(node.RestHTTPJSON, config)
if err != nil {
this.Fail("读取节点REST HTTP信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// restHTTPS
if len(node.RestHTTPSJSON) > 0 {
var config = &serverconfigs.HTTPSProtocolConfig{}
err = json.Unmarshal(node.RestHTTPSJSON, config)
if err != nil {
this.Fail("读取节点REST HTTPS信息失败" + err.Error())
}
for _, listen := range config.Listen {
if len(listen.Host) > 0 && !lists.ContainsString(hosts, listen.Host) {
hosts = append(hosts, listen.Host)
}
}
}
// access addrs
if len(node.AccessAddrsJSON) > 0 {
var addrs []*serverconfigs.NetworkAddressConfig
err = json.Unmarshal(node.AccessAddrsJSON, &addrs)
if err != nil {
this.Fail("读取节点访问地址失败:" + err.Error())
}
for _, addr := range addrs {
if len(addr.Host) > 0 && !lists.ContainsString(hosts, addr.Host) {
hosts = append(hosts, addr.Host)
}
}
}
}
this.Data["apiNode"] = maps.Map{
"protocol": params.Protocol,
"host": params.Host,
"port": params.Port,
"nodeId": params.NodeId,
"nodeSecret": params.NodeSecret,
"hosts": hosts,
}
this.Success()
}

View File

@@ -3,8 +3,7 @@ package setup
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"net"
"regexp"
"sort"
"strings"
)
type IndexAction struct {
@@ -16,31 +15,18 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct{}) {
// 当前服务器的IP
serverIPs := []string{}
addrs, _ := net.InterfaceAddrs()
for _, addr := range addrs {
netAddr, ok := addr.(*net.IPNet)
if !ok {
continue
var currentHost = this.Request.Host
if strings.Contains(this.Request.Host, ":") {
host, _, err := net.SplitHostPort(this.Request.Host)
if err == nil {
currentHost = host
}
serverIPs = append(serverIPs, netAddr.IP.String())
}
// 对IP进行排序我们希望IPv4排在前面而且希望127.0.0.1排在IPv4里的最后
sort.Slice(serverIPs, func(i, j int) bool {
ip1 := serverIPs[i]
if ip1 == "127.0.0.1" {
return false
}
if regexp.MustCompile(`^\d+\.\d+\.\d+.\d+$`).MatchString(ip1) {
return true
}
return false
})
this.Data["serverIPs"] = serverIPs
if net.ParseIP(currentHost) != nil {
this.Data["currentHost"] = currentHost
} else {
this.Data["currentHost"] = ""
}
this.Show()
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/go-yaml/yaml"
@@ -68,14 +69,14 @@ func (this *InstallAction) RunPost(params struct {
_, err = os.Stat(apiNodeDir)
if err != nil {
if os.IsNotExist(err) {
this.Fail("在当前目录下找不到" + dir + "目录,请将" + dir + "目录上传或者重新下载解压")
this.Fail("在当前目录" + Tea.Root + "下找不到" + dir + "目录,请将" + dir + "目录上传或者重新下载解压")
}
this.Fail("无法检查" + dir + "目录,发生错误:" + err.Error())
}
}
// 保存数据库配置
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + dbMap.GetString("host") + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
dbConfig := &dbs.Config{
DBs: map[string]*dbs.DBConfig{
"prod": {
@@ -175,7 +176,7 @@ func (this *InstallAction) RunPost(params struct {
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{"http://" + apiNodeMap.GetString("newHost") + ":" + apiNodeMap.GetString("newPort")},
Endpoints: []string{"http://" + configutils.QuoteIP(apiNodeMap.GetString("newHost")) + ":" + apiNodeMap.GetString("newPort")},
},
NodeId: resultMap.GetString("adminNodeId"),
Secret: resultMap.GetString("adminNodeSecret"),
@@ -233,7 +234,7 @@ func (this *InstallAction) RunPost(params struct {
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + apiNodeMap.GetString("oldHost") + ":" + apiNodeMap.GetString("oldPort")},
Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + configutils.QuoteIP(apiNodeMap.GetString("oldHost")) + ":" + apiNodeMap.GetString("oldPort")},
},
NodeId: apiNodeMap.GetString("oldNodeId"),
Secret: apiNodeMap.GetString("oldNodeSecret"),

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
@@ -62,7 +63,7 @@ func (this *ValidateApiAction) RunPost(params struct {
if net.ParseIP(params.NewHost) == nil {
this.FailField("newHost", "请输入正确的节点主机地址")
}
params.Must.
Field("newHost", params.NewHost).
Require("请输入节点主机地址")
@@ -86,7 +87,7 @@ func (this *ValidateApiAction) RunPost(params struct {
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{params.OldProtocol + "://" + params.OldHost + ":" + params.OldPort},
Endpoints: []string{params.OldProtocol + "://" + configutils.QuoteIP(params.OldHost) + ":" + params.OldPort},
},
NodeId: params.OldNodeId,
Secret: params.OldNodeSecret,

View File

@@ -2,11 +2,14 @@ package setup
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
"regexp"
"strings"
)
@@ -27,7 +30,20 @@ func (this *ValidateDbAction) RunPost(params struct {
params.Must.
Field("host", params.Host).
Require("请输入主机地址").
Match(`^[\w\.-]+$`, "主机地址中不能包含特殊字符").
Expect(func() (message string, success bool) {
// 是否为IP
if net.ParseIP(params.Host) != nil {
success = true
return
}
if !regexp.MustCompile(`^[\w.-]+$`).MatchString(params.Host) {
message = "主机地址中不能包含特殊字符"
success = false
return
}
success = true
return
}).
Field("port", params.Port).
Require("请输入端口").
Match(`^\d+$`, "端口中只能包含数字").
@@ -41,7 +57,7 @@ func (this *ValidateDbAction) RunPost(params struct {
// 测试连接
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
Driver: "mysql",
Dsn: params.Username + ":" + params.Password + "@tcp(" + params.Host + ":" + params.Port + ")/" + params.Database,
Dsn: params.Username + ":" + params.Password + "@tcp(" + configutils.QuoteIP(params.Host) + ":" + params.Port + ")/" + params.Database,
Prefix: "",
})
if err != nil {
@@ -58,7 +74,7 @@ func (this *ValidateDbAction) RunPost(params struct {
if strings.Contains(err.Error(), "Error 1049") {
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
Driver: "mysql",
Dsn: params.Username + ":" + params.Password + "@tcp(" + params.Host + ":" + params.Port + ")/",
Dsn: params.Username + ":" + params.Password + "@tcp(" + configutils.QuoteIP(params.Host) + ":" + params.Port + ")/",
Prefix: "",
})

View File

@@ -21,6 +21,11 @@ func NewUserMustAuth(module string) *userMustAuth {
}
func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
if teaconst.IsRecoverMode {
actionPtr.Object().RedirectURL("/recover")
return false
}
var action = actionPtr.Object()
// 安全相关

View File

@@ -13,6 +13,11 @@ type UserShouldAuth struct {
}
func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
if teaconst.IsRecoverMode {
actionPtr.Object().RedirectURL("/recover")
return false
}
this.action = actionPtr.Object()
// 安全相关
@@ -34,7 +39,7 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
return true
}
// 存储用户名到SESSION
// StoreAdmin 存储用户名到SESSION
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
// 修改sid的时间
if remember {

View File

@@ -131,6 +131,9 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-ui"
// 恢复
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/recover"
// 安装
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup"