This repository has been archived on 2026-07-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
GoEdgeAdmin/internal/web/actions/default/setup/install.go
T

344 lines
10 KiB
Go

package setup
import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"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"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
"io/ioutil"
"os"
"os/exec"
"time"
)
type InstallAction struct {
actionutils.ParentAction
}
func (this *InstallAction) RunPost(params struct {
ApiNodeJSON []byte
DbJSON []byte
AdminJSON []byte
Must *actions.Must
}) {
currentStatusText = ""
defer func() {
currentStatusText = ""
}()
// API节点配置
currentStatusText = "正在检查API节点配置"
apiNodeMap := maps.Map{}
err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap)
if err != nil {
this.Fail("API节点配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
}
// 数据库
currentStatusText = "正在检查数据库配置"
dbMap := maps.Map{}
err = json.Unmarshal(params.DbJSON, &dbMap)
if err != nil {
this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
}
// 管理员
currentStatusText = "正在检查管理员配置"
adminMap := maps.Map{}
err = json.Unmarshal(params.AdminJSON, &adminMap)
if err != nil {
this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
}
// 安装API节点
mode := apiNodeMap.GetString("mode")
if mode == "new" {
currentStatusText = "准备启动新API节点"
// 整个系统目录结构为:
// edge-admin/
// edge-api/
// bin/
// ...
// 检查环境
apiNodeDir := Tea.Root + "/edge-api"
for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} {
apiNodeDir := Tea.Root + "/" + dir
_, err = os.Stat(apiNodeDir)
if err != nil {
if os.IsNotExist(err) {
this.Fail("在当前目录(" + Tea.Root + ")下找不到" + dir + "目录,请将" + dir + "目录上传或者重新下载解压")
}
this.Fail("无法检查" + dir + "目录,发生错误:" + err.Error())
}
}
// 保存数据库配置
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": {
Driver: "mysql",
Dsn: dsn,
Prefix: "edge",
}},
}
dbConfig.Default.DB = "prod"
dbConfigData, err := yaml.Marshal(dbConfig)
if err != nil {
this.Fail("生成数据库配置失败:" + err.Error())
}
err = ioutil.WriteFile(apiNodeDir+"/configs/db.yaml", dbConfigData, 0666)
if err != nil {
this.Fail("保存数据库配置失败:" + err.Error())
}
// 生成备份文件
homeDir, _ := os.UserHomeDir()
backupDirs := []string{"/etc/edge-api"}
if len(homeDir) > 0 {
backupDirs = append(backupDirs, homeDir+"/.edge-api")
}
for _, backupDir := range backupDirs {
stat, err := os.Stat(backupDir)
if err == nil && stat.IsDir() {
_ = ioutil.WriteFile(backupDir+"/db.yaml", dbConfigData, 0666)
} else if err != nil && os.IsNotExist(err) {
err = os.Mkdir(backupDir, 0777)
if err == nil {
_ = ioutil.WriteFile(backupDir+"/db.yaml", dbConfigData, 0666)
}
}
}
err = ioutil.WriteFile(Tea.ConfigFile("/api_db.yaml"), dbConfigData, 0666)
if err != nil {
this.Fail("保存数据库配置失败:" + err.Error())
}
// 生成备份文件
backupDirs = []string{"/etc/edge-admin"}
if len(homeDir) > 0 {
backupDirs = append(backupDirs, homeDir+"/.edge-admin")
}
for _, backupDir := range backupDirs {
stat, err := os.Stat(backupDir)
if err == nil && stat.IsDir() {
_ = ioutil.WriteFile(backupDir+"/api_db.yaml", dbConfigData, 0666)
} else if err != nil && os.IsNotExist(err) {
err = os.Mkdir(backupDir, 0777)
if err == nil {
_ = ioutil.WriteFile(backupDir+"/api_db.yaml", dbConfigData, 0666)
}
}
}
// 开始安装
currentStatusText = "正在安装数据库表结构并写入数据"
var resultMap = maps.Map{}
logs.Println("[INSTALL]setup edge-api")
{
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"")
output := bytes.NewBuffer([]byte{})
cmd.Stdout = output
err = cmd.Run()
if err != nil {
this.Fail("安装失败:" + err.Error())
}
resultData := output.Bytes()
err = json.Unmarshal(resultData, &resultMap)
if err != nil {
this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")")
}
if !resultMap.GetBool("isOk") {
this.Fail("节点安装错误:" + resultMap.GetString("error"))
}
// 等数据完全写入
time.Sleep(1 * time.Second)
}
// 关闭正在运行的API节点,防止冲突
logs.Println("[INSTALL]stop edge-api")
{
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "stop")
_ = cmd.Run()
}
// 启动API节点
currentStatusText = "正在启动API节点"
logs.Println("[INSTALL]start edge-api")
{
cmd := exec.Command(apiNodeDir + "/bin/edge-api")
err = cmd.Start()
if err != nil {
this.Fail("API节点启动失败:" + err.Error())
}
// 记录子PID方便退出的时候一起退出
nodes.SharedAdminNode.AddSubPID(cmd.Process.Pid)
// 等待API节点初始化完成
currentStatusText = "正在等待API节点启动完毕"
var apiNodeSock = gosock.NewTmpSock("edge-api")
var maxRetries = 5
for {
reply, err := apiNodeSock.SendTimeout(&gosock.Command{
Code: "starting",
}, 3*time.Second)
if err != nil {
if maxRetries < 0 {
this.Fail("API节点启动失败,请查看运行日志检查是否正常")
} else {
time.Sleep(3 * time.Second)
maxRetries--
}
} else {
if !maps.NewMap(reply.Params).GetBool("isStarting") {
currentStatusText = "API节点启动完毕"
break
}
// 继续等待完成
time.Sleep(3 * time.Second)
}
}
}
// 写入API节点配置,完成安装
apiConfig := &configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{"http://" + configutils.QuoteIP(apiNodeMap.GetString("newHost")) + ":" + apiNodeMap.GetString("newPort")},
},
NodeId: resultMap.GetString("adminNodeId"),
Secret: resultMap.GetString("adminNodeSecret"),
}
// 设置管理员
currentStatusText = "正在设置管理员"
client, err := rpc.NewRPCClient(apiConfig, false)
if err != nil {
this.FailField("oldHost", "测试API节点时出错,请检查配置,错误信息:"+err.Error())
}
ctx := client.Context(0)
for i := 0; i < 3; i++ {
_, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{
Username: adminMap.GetString("username"),
Password: adminMap.GetString("password"),
})
// 这里我们尝试多次是为了等待API节点启动完毕
if err != nil {
time.Sleep(1 * time.Second)
}
}
if err != nil {
this.Fail("设置管理员账号出错:" + err.Error())
}
// 设置访问日志保留天数
currentStatusText = "正在配置访问日志保留天数"
var accessLogKeepDays = dbMap.GetInt("accessLogKeepDays")
if accessLogKeepDays > 0 {
var config = &systemconfigs.DatabaseConfig{}
config.ServerAccessLog.Clean.Days = accessLogKeepDays
configJSON, err := json.Marshal(config)
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
_, err = client.SysSettingRPC().UpdateSysSetting(ctx, &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeDatabaseConfigSetting,
ValueJSON: configJSON,
})
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
}
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil {
this.Fail("保存配置失败,原因:" + err.Error())
}
this.Success()
} else if mode == "old" {
// 构造RPC
apiConfig := &configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{
Endpoints: []string{apiNodeMap.GetString("oldProtocol") + "://" + configutils.QuoteIP(apiNodeMap.GetString("oldHost")) + ":" + apiNodeMap.GetString("oldPort")},
},
NodeId: apiNodeMap.GetString("oldNodeId"),
Secret: apiNodeMap.GetString("oldNodeSecret"),
}
client, err := rpc.NewRPCClient(apiConfig, false)
if err != nil {
this.FailField("oldHost", "测试API节点时出错,请检查配置,错误信息:"+err.Error())
}
defer func() {
_ = client.Close()
}()
// 设置管理员
ctx := client.APIContext(0)
_, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{
Username: adminMap.GetString("username"),
Password: adminMap.GetString("password"),
})
if err != nil {
this.Fail("设置管理员账号出错:" + err.Error())
}
// 设置访问日志保留天数
var accessLogKeepDays = dbMap.GetInt("accessLogKeepDays")
if accessLogKeepDays > 0 {
var config = &systemconfigs.DatabaseConfig{}
config.ServerAccessLog.Clean.Days = accessLogKeepDays
configJSON, err := json.Marshal(config)
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
_, err = client.SysSettingRPC().UpdateSysSetting(ctx, &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeDatabaseConfigSetting,
ValueJSON: configJSON,
})
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
}
// 写入API节点配置,完成安装
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil {
this.Fail("保存配置失败,原因:" + err.Error())
}
// 成功
this.Success()
} else {
this.Fail("错误的API节点模式:'" + mode + "'")
}
}