mirror of
				https://github.com/TeaOSLab/EdgeAPI.git
				synced 2025-11-04 16:00:24 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1048 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1048 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
 | 
						|
 | 
						|
package instances
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
 | 
						|
	"github.com/TeaOSLab/EdgeAPI/internal/db/models"
 | 
						|
	"github.com/TeaOSLab/EdgeAPI/internal/installers/helpers"
 | 
						|
	"github.com/TeaOSLab/EdgeAPI/internal/setup"
 | 
						|
	executils "github.com/TeaOSLab/EdgeAPI/internal/utils/exec"
 | 
						|
	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
 | 
						|
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
						|
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
 | 
						|
	"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
 | 
						|
	"github.com/iwind/TeaGo"
 | 
						|
	"github.com/iwind/TeaGo/Tea"
 | 
						|
	"github.com/iwind/TeaGo/dbs"
 | 
						|
	"github.com/iwind/TeaGo/lists"
 | 
						|
	"github.com/iwind/TeaGo/rands"
 | 
						|
	"github.com/iwind/TeaGo/types"
 | 
						|
	"gopkg.in/yaml.v3"
 | 
						|
	"log"
 | 
						|
	"net"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"regexp"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
type Instance struct {
 | 
						|
	options Options
 | 
						|
}
 | 
						|
 | 
						|
func NewInstance(options Options) *Instance {
 | 
						|
	return &Instance{
 | 
						|
		options: options,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) SetupAll() error {
 | 
						|
	type TaskFn func() error
 | 
						|
	for _, taskFn := range []TaskFn{
 | 
						|
		this.SetupDB,
 | 
						|
		this.SetupAdminNode,
 | 
						|
		this.SetupAPINode,
 | 
						|
		this.SetupNode,
 | 
						|
		this.SetupUserNode,
 | 
						|
		this.Clean,
 | 
						|
		this.Startup,
 | 
						|
	} {
 | 
						|
		err := taskFn()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) SetupDB() error {
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("setup db " + this.options.DB.Host + ":" + types.String(this.options.DB.Port) + "/" + this.options.DB.Name + " ...")
 | 
						|
	}
 | 
						|
 | 
						|
	// 检查数据库名
 | 
						|
	{
 | 
						|
		db, dbErr := this.dbInstanceWithoutDBName()
 | 
						|
		if dbErr != nil {
 | 
						|
			return dbErr
 | 
						|
		}
 | 
						|
 | 
						|
		defer func() {
 | 
						|
			_ = db.Close()
 | 
						|
		}()
 | 
						|
 | 
						|
		// 等待连接成功
 | 
						|
		for i := 0; i < 30; i++ {
 | 
						|
			err := db.Raw().PingContext(context.Background())
 | 
						|
			if err == nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
		}
 | 
						|
 | 
						|
		_, err := db.Exec("USE `" + this.options.DB.Name + "`")
 | 
						|
		if err != nil {
 | 
						|
			if models.CheckSQLErrCode(err, 1049) {
 | 
						|
				_, err = db.Exec("CREATE DATABASE `" + this.options.DB.Name + "` DEFAULT CHARSET utf8mb4")
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("create new database failed: %w", err)
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				return fmt.Errorf("check database failed: %w", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 检查版本
 | 
						|
	{
 | 
						|
		db, instanceErr := this.dbInstance(false)
 | 
						|
		if instanceErr != nil {
 | 
						|
			return instanceErr
 | 
						|
		}
 | 
						|
 | 
						|
		defer func() {
 | 
						|
			_ = db.Close()
 | 
						|
		}()
 | 
						|
 | 
						|
		dbConfig, configErr := db.Config()
 | 
						|
		if configErr != nil {
 | 
						|
			return configErr
 | 
						|
		}
 | 
						|
 | 
						|
		var shouldExecute bool
 | 
						|
		version, err := db.FindCol(0, "SELECT version FROM edgeVersions")
 | 
						|
		if err != nil {
 | 
						|
			shouldExecute = true
 | 
						|
		} else {
 | 
						|
			if !strings.HasPrefix(types.String(version), teaconst.Version+".") {
 | 
						|
				shouldExecute = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if shouldExecute {
 | 
						|
			var executor = setup.NewSQLExecutor(dbConfig)
 | 
						|
			err = executor.Run(false)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("execute sql failed: %w", err)
 | 
						|
			}
 | 
						|
 | 
						|
			// wait to commit
 | 
						|
			time.Sleep(1 * time.Second)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 启用数据库
 | 
						|
	db, instanceErr := this.dbInstance(true)
 | 
						|
	if instanceErr != nil {
 | 
						|
		return instanceErr
 | 
						|
	}
 | 
						|
 | 
						|
	defer func() {
 | 
						|
		_ = db.Close()
 | 
						|
	}()
 | 
						|
 | 
						|
	// 创建Admin Token
 | 
						|
	var tx *dbs.Tx
 | 
						|
	{
 | 
						|
		one, err := db.FindOne("SELECT * FROM edgeAPITokens WHERE role='admin'")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
			var nodeId = rands.HexString(32)
 | 
						|
			var secret = rands.String(32)
 | 
						|
			err = models.SharedApiTokenDAO.CreateAPIToken(tx, nodeId, secret, "admin")
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("create admin node token failed: %w", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 创建Admin
 | 
						|
	{
 | 
						|
		one, err := db.FindOne("SELECT * FROM edgeAdmins")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
			var password = rands.String(16)
 | 
						|
			// 保存密码到文件
 | 
						|
			var dir = this.options.WorkDir + "/usr/local/goedge"
 | 
						|
			_, err = os.Stat(dir)
 | 
						|
			if err != nil {
 | 
						|
				err = os.MkdirAll(dir, 0777)
 | 
						|
				if err != nil {
 | 
						|
					return fmt.Errorf("create directory '"+dir+"' failed: %w", err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			err = os.WriteFile(dir+"/password.txt", []byte("Admin Password: "+password+"\n"), 0666)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("write 'password.txt' failed: %w", err)
 | 
						|
			}
 | 
						|
 | 
						|
			_, err = models.SharedAdminDAO.CreateAdmin(tx, "admin", true, password, "Admin", true, []byte("[]"))
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("create admin failed: %w", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 创建API Node
 | 
						|
	{
 | 
						|
		one, findErr := db.FindOne("SELECT * FROM edgeAPINodes")
 | 
						|
		if findErr != nil {
 | 
						|
			return findErr
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
			// http
 | 
						|
			var httpConfig = &serverconfigs.HTTPProtocolConfig{}
 | 
						|
			httpConfig.IsOn = true
 | 
						|
			httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
 | 
						|
				{
 | 
						|
					Protocol:  "http",
 | 
						|
					PortRange: types.String(this.options.APINode.HTTPPort),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			httpJSON, encodeErr := json.Marshal(httpConfig)
 | 
						|
			if encodeErr != nil {
 | 
						|
				return fmt.Errorf("encode api http config failed: %w", encodeErr)
 | 
						|
			}
 | 
						|
 | 
						|
			// rest
 | 
						|
			var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
 | 
						|
			restHTTPConfig.IsOn = true
 | 
						|
			restHTTPConfig.Listen = []*serverconfigs.NetworkAddressConfig{
 | 
						|
				{
 | 
						|
					Protocol:  "http",
 | 
						|
					PortRange: types.String(this.options.APINode.RestHTTPPort),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			restHTTPJSON, encodeErr := json.Marshal(restHTTPConfig)
 | 
						|
			if encodeErr != nil {
 | 
						|
				return fmt.Errorf("encode api rest http config failed: %w", encodeErr)
 | 
						|
			}
 | 
						|
 | 
						|
			// access addrs
 | 
						|
			var accessAddrs = []*serverconfigs.NetworkAddressConfig{
 | 
						|
				{
 | 
						|
					Protocol:  "http",
 | 
						|
					Host:      "127.0.0.1",
 | 
						|
					PortRange: types.String(this.options.APINode.HTTPPort),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			accessAddrsJSON, encodeErr := json.Marshal(accessAddrs)
 | 
						|
			if encodeErr != nil {
 | 
						|
				return fmt.Errorf("encode access addresses failed: %w", encodeErr)
 | 
						|
			}
 | 
						|
 | 
						|
			_, err := models.SharedAPINodeDAO.CreateAPINode(tx, "API Node", "Primary API Node", httpJSON, nil, true, restHTTPJSON, nil, accessAddrsJSON, true)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		{
 | 
						|
			// check token
 | 
						|
			nodes, nodesErr := models.SharedAPINodeDAO.FindAllEnabledAPINodes(tx)
 | 
						|
			if nodesErr != nil {
 | 
						|
				return nodesErr
 | 
						|
			}
 | 
						|
			for _, node := range nodes {
 | 
						|
				token, err := models.SharedApiTokenDAO.FindEnabledTokenWithNode(tx, node.UniqueId)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				if token == nil {
 | 
						|
					err = models.SharedApiTokenDAO.CreateAPIToken(tx, node.UniqueId, node.Secret, nodeconfigs.NodeRoleAPI)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 创建Node
 | 
						|
	var clusterId int64
 | 
						|
	{
 | 
						|
		{
 | 
						|
			// check cluster
 | 
						|
			clusterIdCol, err := db.FindCol(0, "SELECT id FROM edgeNodeClusters WHERE state=1")
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			clusterId = types.Int64(clusterIdCol)
 | 
						|
			if clusterId == 0 {
 | 
						|
				return errors.New("invalid cluster id '" + types.String(clusterId) + "'")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		one, findErr := db.FindOne("SELECT * FROM edgeNodes")
 | 
						|
		if findErr != nil {
 | 
						|
			return findErr
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
			_, err := models.SharedNodeDAO.CreateNode(tx, 0, "Local Node", clusterId, 0, 0)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("create node failed: %w", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 检查User
 | 
						|
	var userId int64
 | 
						|
	{
 | 
						|
		one, findErr := db.FindOne("SELECT id FROM edgeUsers WHERE state=1")
 | 
						|
		if findErr != nil {
 | 
						|
			return findErr
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
			return errors.New("user must not be created yet")
 | 
						|
		}
 | 
						|
		userId = one.GetInt64("id")
 | 
						|
	}
 | 
						|
 | 
						|
	// 创建用户节点
 | 
						|
	{
 | 
						|
		one, findErr := db.FindOne("SELECT * FROM edgeUserNodes")
 | 
						|
		if findErr != nil {
 | 
						|
			return findErr
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
			// http
 | 
						|
			var httpConfig = &serverconfigs.HTTPProtocolConfig{}
 | 
						|
			httpConfig.IsOn = true
 | 
						|
			httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
 | 
						|
				{
 | 
						|
					Protocol:  "http",
 | 
						|
					PortRange: types.String(this.options.UserNode.HTTPPort),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			httpJSON, encodeErr := json.Marshal(httpConfig)
 | 
						|
			if encodeErr != nil {
 | 
						|
				return fmt.Errorf("encode api http config failed: %w", encodeErr)
 | 
						|
			}
 | 
						|
 | 
						|
			// access addrs
 | 
						|
			var accessAddrs = []*serverconfigs.NetworkAddressConfig{
 | 
						|
				{
 | 
						|
					Protocol:  "http",
 | 
						|
					Host:      "127.0.0.1",
 | 
						|
					PortRange: types.String(this.options.UserNode.HTTPPort),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			accessAddrsJSON, encodeErr := json.Marshal(accessAddrs)
 | 
						|
			if encodeErr != nil {
 | 
						|
				return fmt.Errorf("encode access addresses failed: %w", encodeErr)
 | 
						|
			}
 | 
						|
 | 
						|
			_, err := models.SharedUserNodeDAO.CreateUserNode(tx, "User Platform", "Created by system", httpJSON, nil, accessAddrsJSON, true)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 创建网站
 | 
						|
	{
 | 
						|
		one, findErr := db.FindOne("SELECT id FROM edgeServers WHERE state=1")
 | 
						|
		if findErr != nil {
 | 
						|
			return findErr
 | 
						|
		}
 | 
						|
		if len(one) == 0 {
 | 
						|
 | 
						|
			var webId int64
 | 
						|
 | 
						|
			{
 | 
						|
				{
 | 
						|
					var err error
 | 
						|
					webId, err = models.SharedHTTPWebDAO.CreateWeb(tx, 0, userId, nil)
 | 
						|
					if err != nil {
 | 
						|
						return fmt.Errorf("create web failed: %w", err)
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// 访问日志
 | 
						|
				{
 | 
						|
					err := models.SharedHTTPWebDAO.UpdateWebAccessLogConfig(tx, webId, []byte(`{
 | 
						|
			"isPrior": false,
 | 
						|
			"isOn": true,
 | 
						|
			"fields": [1, 2, 6, 7],
 | 
						|
			"status1": true,
 | 
						|
			"status2": true,
 | 
						|
			"status3": true,
 | 
						|
			"status4": true,
 | 
						|
			"status5": true,
 | 
						|
 | 
						|
			"storageOnly": false,
 | 
						|
			"storagePolicies": [],
 | 
						|
 | 
						|
            "firewallOnly": false
 | 
						|
		}`))
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// websocket
 | 
						|
				{
 | 
						|
					websocketId, err := models.SharedHTTPWebsocketDAO.CreateWebsocket(tx, []byte(`{
 | 
						|
					"count": 30,
 | 
						|
					"unit": "second"
 | 
						|
				}`), true, nil, true, "")
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					err = models.SharedHTTPWebDAO.UpdateWebsocket(tx, webId, []byte(`{
 | 
						|
				"isPrior": false,
 | 
						|
				"isOn": true,
 | 
						|
				"websocketId": `+types.String(websocketId)+`
 | 
						|
			}`))
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// cache
 | 
						|
				{
 | 
						|
					var cacheConfig = &serverconfigs.HTTPCacheConfig{
 | 
						|
						IsPrior:         false,
 | 
						|
						IsOn:            true,
 | 
						|
						AddStatusHeader: true,
 | 
						|
						PurgeIsOn:       false,
 | 
						|
						PurgeKey:        "",
 | 
						|
						CacheRefs:       []*serverconfigs.HTTPCacheRef{},
 | 
						|
					}
 | 
						|
					cacheConfigJSON, err := json.Marshal(cacheConfig)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					err = models.SharedHTTPWebDAO.UpdateWebCache(tx, webId, cacheConfigJSON)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// waf
 | 
						|
				{
 | 
						|
					var firewallRef = &firewallconfigs.HTTPFirewallRef{
 | 
						|
						IsPrior:          false,
 | 
						|
						IsOn:             true,
 | 
						|
						FirewallPolicyId: 0,
 | 
						|
					}
 | 
						|
					firewallRefJSON, err := json.Marshal(firewallRef)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					err = models.SharedHTTPWebDAO.UpdateWebFirewall(tx, webId, firewallRefJSON)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// stat
 | 
						|
				{
 | 
						|
					var statConfig = &serverconfigs.HTTPStatRef{
 | 
						|
						IsPrior: false,
 | 
						|
						IsOn:    true,
 | 
						|
					}
 | 
						|
					statJSON, err := json.Marshal(statConfig)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					err = models.SharedHTTPWebDAO.UpdateWebStat(tx, webId, statJSON)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			var httpConfig = &serverconfigs.HTTPProtocolConfig{}
 | 
						|
			httpConfig.IsOn = true
 | 
						|
			httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
 | 
						|
				{
 | 
						|
					Protocol:  "http",
 | 
						|
					PortRange: types.String(this.options.Node.HTTPPort),
 | 
						|
				},
 | 
						|
			}
 | 
						|
			httpConfigJSON, err := json.Marshal(httpConfig)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			// reverse proxy
 | 
						|
			reverseProxyId, err := models.SharedReverseProxyDAO.CreateReverseProxy(tx, 0, userId, nil, nil, nil)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			var reverseProxyRef = &serverconfigs.ReverseProxyRef{
 | 
						|
				IsOn:           true,
 | 
						|
				ReverseProxyId: reverseProxyId,
 | 
						|
			}
 | 
						|
			reverseProxyRefJSON, err := json.Marshal(reverseProxyRef)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			_, err = models.SharedServerDAO.CreateServer(tx, 0, userId, serverconfigs.ServerTypeHTTPProxy, "First Site", "Created by system", []byte(`[{"name": "example.org", "type":"full"}]`), false, nil, httpConfigJSON, nil, nil, nil, nil, nil, webId, reverseProxyRefJSON, clusterId, nil, nil, nil, 0)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("create server failed: %w", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 数据库清理
 | 
						|
	{
 | 
						|
		var config = systemconfigs.NewDatabaseConfig()
 | 
						|
		configJSON, err := json.Marshal(config)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("encode database config failed: %w", err)
 | 
						|
		}
 | 
						|
 | 
						|
		err = models.SharedSysSettingDAO.UpdateSetting(tx, systemconfigs.SettingCodeDatabaseConfigSetting, configJSON)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 删除任务
 | 
						|
	{
 | 
						|
		err := models.SharedNodeTaskDAO.DeleteAllNodeTasks(tx)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 设置未初始化
 | 
						|
	{
 | 
						|
		err := models.SharedSysSettingDAO.UpdateSetting(tx, systemconfigs.SettingCodeStandaloneInstanceInitialized, []byte("0"))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) SetupAdminNode() error {
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("setup admin node ...")
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		err := this.unzip("admin", teaconst.Version)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// create api_node.yaml
 | 
						|
	db, findErr := this.dbInstance(true)
 | 
						|
	if findErr != nil {
 | 
						|
		return findErr
 | 
						|
	}
 | 
						|
 | 
						|
	var apiYAMLData []byte
 | 
						|
	{
 | 
						|
		node, err := db.FindOne("SELECT nodeId,secret FROM edgeAPITokens WHERE state=1 AND role='admin'")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if node == nil {
 | 
						|
			return errors.New("can not find admin api token in database")
 | 
						|
		}
 | 
						|
 | 
						|
		var apiConfig = &APIConfig{
 | 
						|
			RPCEndpoints:     []string{"http://127.0.0.1:" + types.String(this.options.APINode.HTTPPort)},
 | 
						|
			RPCDisableUpdate: true,
 | 
						|
			NodeId:           node.GetString("nodeId"),
 | 
						|
			Secret:           node.GetString("secret"),
 | 
						|
		}
 | 
						|
		data, err := apiConfig.AsYAML()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		apiYAMLData = data
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-admin/configs/api_admin.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// backup
 | 
						|
	// ignore errors
 | 
						|
	{
 | 
						|
		homeDir, err := os.UserHomeDir()
 | 
						|
		if err == nil {
 | 
						|
			var dir = homeDir + "/.edge-admin/"
 | 
						|
			_, err = os.Stat(dir)
 | 
						|
			if err != nil {
 | 
						|
				_ = os.MkdirAll(dir, 0777)
 | 
						|
			}
 | 
						|
			_ = os.WriteFile(dir+"/api_admin.yaml", apiYAMLData, 0666)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// create database config
 | 
						|
	{
 | 
						|
		dbConfig, err := db.Config()
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("retrieve database config failed: %w", err)
 | 
						|
		}
 | 
						|
		var fullConfig = dbs.Config{}
 | 
						|
		fullConfig.Default.DB = "prod"
 | 
						|
		fullConfig.DBs = map[string]*dbs.DBConfig{
 | 
						|
			"prod": dbConfig,
 | 
						|
		}
 | 
						|
 | 
						|
		data, err := yaml.Marshal(fullConfig)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-admin/configs/api_db.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// server.yaml
 | 
						|
	{
 | 
						|
		var serverConfig = &TeaGo.ServerConfig{}
 | 
						|
		serverConfig.Env = "prod"
 | 
						|
		serverConfig.Http.On = true
 | 
						|
		serverConfig.Http.Listen = []string{":" + types.String(this.options.AdminNode.Port)}
 | 
						|
		data, err := yaml.Marshal(serverConfig)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-admin/configs/server.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) SetupAPINode() error {
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("setup api node ...")
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		err := this.unzip("api", teaconst.NodeVersion)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// create api_node.yaml
 | 
						|
	db, findErr := this.dbInstance(true)
 | 
						|
	if findErr != nil {
 | 
						|
		return findErr
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		node, err := db.FindOne("SELECT uniqueId,secret FROM edgeAPINodes WHERE state=1")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if node == nil {
 | 
						|
			return errors.New("can not find node in database")
 | 
						|
		}
 | 
						|
 | 
						|
		var apiConfig = &APIConfig{
 | 
						|
			NodeId: node.GetString("uniqueId"),
 | 
						|
			Secret: node.GetString("secret"),
 | 
						|
		}
 | 
						|
		data, err := apiConfig.AsYAML()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-api/configs/api.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// create database config
 | 
						|
	{
 | 
						|
		dbConfig, err := db.Config()
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("retrieve database config failed: %w", err)
 | 
						|
		}
 | 
						|
		var fullConfig = dbs.Config{}
 | 
						|
		fullConfig.Default.DB = "prod"
 | 
						|
		fullConfig.DBs = map[string]*dbs.DBConfig{
 | 
						|
			"prod": dbConfig,
 | 
						|
		}
 | 
						|
 | 
						|
		data, err := yaml.Marshal(fullConfig)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-api/configs/db.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) SetupNode() error {
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("setup node ...")
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		err := this.unzip("node", teaconst.NodeVersion)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// create api_node.yaml
 | 
						|
	{
 | 
						|
		db, err := this.dbInstance(true)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		node, err := db.FindOne("SELECT uniqueId,secret FROM edgeNodes WHERE state=1")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if node == nil {
 | 
						|
			return errors.New("can not find node in database")
 | 
						|
		}
 | 
						|
 | 
						|
		var apiConfig = &APIConfig{
 | 
						|
			RPCEndpoints:     []string{"http://127.0.0.1:" + types.String(this.options.APINode.HTTPPort)},
 | 
						|
			RPCDisableUpdate: true,
 | 
						|
			NodeId:           node.GetString("uniqueId"),
 | 
						|
			Secret:           node.GetString("secret"),
 | 
						|
		}
 | 
						|
		data, err := apiConfig.AsYAML()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-node/configs/api_node.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) SetupUserNode() error {
 | 
						|
	if !this.isPlus() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("setup user node ...")
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		err := this.unzip("user", teaconst.NodeVersion)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// create api_node.yaml
 | 
						|
	{
 | 
						|
		db, err := this.dbInstance(true)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		node, err := db.FindOne("SELECT uniqueId,secret FROM edgeUserNodes WHERE state=1")
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if node == nil {
 | 
						|
			return errors.New("can not find node in database")
 | 
						|
		}
 | 
						|
 | 
						|
		var apiConfig = &APIConfig{
 | 
						|
			RPCEndpoints:     []string{"http://127.0.0.1:" + types.String(this.options.APINode.HTTPPort)},
 | 
						|
			RPCDisableUpdate: true,
 | 
						|
			NodeId:           node.GetString("uniqueId"),
 | 
						|
			Secret:           node.GetString("secret"),
 | 
						|
		}
 | 
						|
		data, err := apiConfig.AsYAML()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = os.WriteFile(this.targetPrefix()+"/edge-user/configs/api_user.yaml", data, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) Clean() error {
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("cleaning ...")
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		var dir = this.targetPrefix() + "/edge-admin/edge-api"
 | 
						|
		_, err := os.Stat(dir)
 | 
						|
		if err == nil {
 | 
						|
			err = os.RemoveAll(dir)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("remove %s failed: %w", dir, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !this.options.IsTesting {
 | 
						|
		matches, _ := filepath.Glob(this.options.SrcDir + "/*.zip")
 | 
						|
		for _, filePath := range matches {
 | 
						|
			err := os.Remove(filePath)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("remove %s failed: %w", filePath, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) Startup() error {
 | 
						|
	if this.options.Verbose {
 | 
						|
		log.Println("startup ...")
 | 
						|
	}
 | 
						|
 | 
						|
	type NodeInfo struct {
 | 
						|
		Role  string
 | 
						|
		Ports []int
 | 
						|
	}
 | 
						|
 | 
						|
	for _, node := range []NodeInfo{
 | 
						|
		{"api", []int{this.options.APINode.HTTPPort, this.options.APINode.RestHTTPPort}},
 | 
						|
		{"admin", []int{this.options.AdminNode.Port}},
 | 
						|
		{"node", []int{this.options.Node.HTTPPort}},
 | 
						|
		{"user", []int{this.options.UserNode.HTTPPort}},
 | 
						|
	} {
 | 
						|
		var exe = this.targetPrefix() + "/edge-" + node.Role + "/bin/edge-" + node.Role
 | 
						|
		_, err := os.Stat(exe)
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		var cmd = executils.NewTimeoutCmd(30*time.Second, exe, "start")
 | 
						|
		err = cmd.Run()
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("start '"+node.Role+"' failed: %w", err)
 | 
						|
		}
 | 
						|
 | 
						|
		for _, port := range node.Ports {
 | 
						|
			var isOk bool
 | 
						|
			for i := 0; i < 30; /** seconds **/ i++ {
 | 
						|
				conn, connErr := net.DialTimeout("tcp", ":"+types.String(port), 5*time.Second)
 | 
						|
				if connErr != nil {
 | 
						|
					time.Sleep(1 * time.Second)
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				_ = conn.Close()
 | 
						|
				isOk = true
 | 
						|
			}
 | 
						|
			if !isOk {
 | 
						|
				return fmt.Errorf("waiting '%s' port '%d' timeout", node.Role, port)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) dbInstance(enableDAO bool) (*dbs.DB, error) {
 | 
						|
	Tea.Env = "prod"
 | 
						|
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
 | 
						|
		Driver: "mysql",
 | 
						|
		Dsn:    this.options.DB.Username + ":" + this.options.DB.Password + "@tcp(" + this.options.DB.Host + ":" + types.String(this.options.DB.Port) + ")/" + this.options.DB.Name + "?charset=utf8mb4&timeout=30s",
 | 
						|
		Prefix: "edge",
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("create database instance failed: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if enableDAO {
 | 
						|
		this.setupDAO(db)
 | 
						|
	}
 | 
						|
 | 
						|
	return db, nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) dbInstanceWithoutDBName() (*dbs.DB, error) {
 | 
						|
	Tea.Env = "prod"
 | 
						|
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
 | 
						|
		Driver: "mysql",
 | 
						|
		Dsn:    this.options.DB.Username + ":" + this.options.DB.Password + "@tcp(" + this.options.DB.Host + ":" + types.String(this.options.DB.Port) + ")/?charset=utf8mb4&timeout=30s",
 | 
						|
		Prefix: "edge",
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("create database instance failed: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return db, nil
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) setupDAO(db *dbs.DB) {
 | 
						|
	dbConfig, err := db.Config()
 | 
						|
	if err == nil {
 | 
						|
		dbs.GlobalConfig().DBs = map[string]*dbs.DBConfig{
 | 
						|
			"dev":  dbConfig,
 | 
						|
			"prod": dbConfig,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedNodeClusterDAO = models.NewNodeClusterDAO()
 | 
						|
		models.SharedNodeClusterDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedNodeDAO = models.NewNodeDAO()
 | 
						|
		models.SharedNodeDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedNodeTaskDAO = models.NewNodeTaskDAO()
 | 
						|
		models.SharedNodeTaskDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedSysLockerDAO = models.NewSysLockerDAO()
 | 
						|
		models.SharedSysLockerDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedAdminDAO = models.NewAdminDAO()
 | 
						|
		models.SharedAdminDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedAPINodeDAO = models.NewAPINodeDAO()
 | 
						|
		models.SharedAPINodeDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedApiTokenDAO = models.NewApiTokenDAO()
 | 
						|
		models.SharedApiTokenDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedUserDAO = models.NewUserDAO()
 | 
						|
		models.SharedUserDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedUserNodeDAO = models.NewUserNodeDAO()
 | 
						|
		models.SharedUserNodeDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedServerDAO = models.NewServerDAO()
 | 
						|
		models.SharedServerDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedHTTPWebDAO = models.NewHTTPWebDAO()
 | 
						|
		models.SharedHTTPWebDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedHTTPWebsocketDAO = models.NewHTTPWebsocketDAO()
 | 
						|
		models.SharedHTTPWebsocketDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedHTTPLocationDAO = models.NewHTTPLocationDAO()
 | 
						|
		models.SharedHTTPLocationDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedServerGroupDAO = models.NewServerGroupDAO()
 | 
						|
		models.SharedServerGroupDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedReverseProxyDAO = models.NewReverseProxyDAO()
 | 
						|
		models.SharedReverseProxyDAO.Instance = db
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		models.SharedSysSettingDAO = models.NewSysSettingDAO()
 | 
						|
		models.SharedSysSettingDAO.Instance = db
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) unzip(role string, version string) error {
 | 
						|
	if !regexp.MustCompile(`^\w+$`).MatchString(role) {
 | 
						|
		return errors.New("invalid role '" + role + "'")
 | 
						|
	}
 | 
						|
 | 
						|
	var arch = runtime.GOARCH
 | 
						|
	if runtime.GOOS != "linux" {
 | 
						|
		arch = "amd64"
 | 
						|
	}
 | 
						|
	var plusTag string
 | 
						|
	if this.isPlus() && lists.ContainsString([]string{nodeconfigs.NodeRoleAdmin, nodeconfigs.NodeRoleAPI, nodeconfigs.NodeRoleNode}, role) {
 | 
						|
		plusTag = "-plus"
 | 
						|
	}
 | 
						|
 | 
						|
	var zipFile = this.options.SrcDir + "/edge-" + role + "-linux-" + arch + plusTag + "-v" + version + ".zip"
 | 
						|
	{
 | 
						|
		stat, err := os.Stat(zipFile)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("stat '"+zipFile+"' failed: %w", err)
 | 
						|
		}
 | 
						|
		if stat.IsDir() {
 | 
						|
			return errors.New("'" + zipFile + "' should be a file instead of directory")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var targetPrefix = this.targetPrefix()
 | 
						|
 | 
						|
	{
 | 
						|
		stat, err := os.Stat(targetPrefix)
 | 
						|
		if err != nil || !stat.IsDir() {
 | 
						|
			err = os.MkdirAll(targetPrefix, 0777)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("create directory '"+targetPrefix+"' failed: %w", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if this.options.Cacheable {
 | 
						|
		var targetDir = targetPrefix + "/edge-" + role
 | 
						|
		_, err := os.Stat(targetDir)
 | 
						|
		if err == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var unzip = helpers.NewUnzip(zipFile, targetPrefix)
 | 
						|
	return unzip.Run()
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) targetPrefix() string {
 | 
						|
	return this.options.WorkDir + "/usr/local/goedge"
 | 
						|
}
 | 
						|
 | 
						|
func (this *Instance) isPlus() bool {
 | 
						|
	return teaconst.Tag == "plus"
 | 
						|
}
 |