package nodes import ( "context" "encoding/json" "errors" "fmt" "github.com/TeaOSLab/EdgeCommon/pkg/iplibrary" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeUser/internal/configloaders" "github.com/TeaOSLab/EdgeUser/internal/configs" teaconst "github.com/TeaOSLab/EdgeUser/internal/const" "github.com/TeaOSLab/EdgeUser/internal/events" "github.com/TeaOSLab/EdgeUser/internal/rpc" _ "github.com/TeaOSLab/EdgeUser/internal/tasks" "github.com/TeaOSLab/EdgeUser/internal/utils" _ "github.com/TeaOSLab/EdgeUser/internal/web" "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" "time" ) type UserNode struct { sock *gosock.Sock } func NewUserNode() *UserNode { return &UserNode{ sock: gosock.NewTmpSock(teaconst.ProcessName), } } func (this *UserNode) Run() { // 启动用户界面 var secret = this.genSecret() configs.Secret = secret // 本地Sock err := this.listenSock() if err != nil { logs.Println("[USER_NODE]" + err.Error()) return } // 检查server配置 err = this.checkServer() if err != nil { logs.Println("[USER_NODE]" + err.Error()) return } // 触发事件 events.Notify(events.EventStart) // 拉取配置 err = this.pullConfig() if err != nil { logs.Println("[USER_NODE]pull config failed: " + err.Error()) return } // 设置DNS this.setupDNS() // 监控状态 go NewNodeStatusExecutor().Listen() logs.Println("[USER_NODE]initializing ip library ...") err = iplibrary.InitPlus() if err != nil { logs.Println("[USER_NODE]initialize ip library failed: " + err.Error()) } // 启动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(600*time.Second). Static("/www", Tea.Root+"/www"). Start() } // Daemon 实现守护进程 func (this *UserNode) Daemon() { var isDebug = lists.ContainsString(os.Args, "debug") for { conn, err := this.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 *UserNode) InstallSystemService() error { var 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 } // 检查Server配置 func (this *UserNode) checkServer() error { var configFile = Tea.ConfigFile("server.yaml") _, err := os.Stat(configFile) if err == nil { return nil } if os.IsNotExist(err) { // 创建文件 var 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:7789" ] # 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 } // 生成Secret func (this *UserNode) genSecret() string { var tmpFile = os.TempDir() + "/edge-user-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 } // 拉取配置 func (this *UserNode) pullConfig() error { rpcClient, err := rpc.SharedRPC() if err != nil { return err } var nodeResp *pb.FindCurrentUserNodeResponse for i := 0; i < 10; i++ { // may retry many times nodeResp, err = rpcClient.UserNodeRPC().FindCurrentUserNode(rpcClient.Context(0), &pb.FindCurrentUserNodeRequest{}) if err != nil { time.Sleep(1 * time.Second) continue } break } if err != nil { return err } var node = nodeResp.UserNode if node == nil { return errors.New("invalid 'nodeId' or 'secret'") } if configs.SharedAPIConfig != nil { configs.SharedAPIConfig.NumberId = node.Id } // 读取Web服务配置 var serverConfig = &TeaGo.ServerConfig{ Env: Tea.EnvProd, } if Tea.IsTesting() { serverConfig.Env = Tea.EnvDev } // HTTP httpConfig, err := this.decodeHTTP(node) if err != nil { return fmt.Errorf("decode http config failed: %w", err) } if httpConfig != nil && httpConfig.IsOn && len(httpConfig.Listen) > 0 { serverConfig.Http.On = true var listens = []string{} for _, listen := range httpConfig.Listen { listens = append(listens, listen.Addresses()...) } serverConfig.Http.Listen = listens } // HTTPS httpsConfig, err := this.DecodeHTTPS(node) if err != nil { return fmt.Errorf("decode https config failed: %w", err) } if httpsConfig != nil && httpsConfig.IsOn && len(httpsConfig.Listen) > 0 { serverConfig.Https.On = true serverConfig.Https.Cert = "configs/https.cert.pem" serverConfig.Https.Key = "configs/https.key.pem" var listens = []string{} for _, listen := range httpsConfig.Listen { listens = append(listens, listen.Addresses()...) } serverConfig.Https.Listen = listens } // 保存到文件 serverYAML, err := yaml.Marshal(serverConfig) if err != nil { return err } err = os.WriteFile(Tea.ConfigFile("server.yaml"), serverYAML, 0666) if err != nil { return err } // add to local firewall var ports = []int{} for _, listens := range [][]string{serverConfig.Http.Listen, serverConfig.Https.Listen} { for _, listen := range listens { _, portString, err := net.SplitHostPort(listen) if err == nil { var port = types.Int(portString) if port > 0 && !lists.ContainsInt(ports, port) { ports = append(ports, port) } } } } if len(ports) > 0 { go utils.AddPortsToFirewall(ports) } return nil } // 解析HTTP配置 func (this *UserNode) decodeHTTP(node *pb.UserNode) (*serverconfigs.HTTPProtocolConfig, error) { if len(node.HttpJSON) == 0 { return nil, nil } config := &serverconfigs.HTTPProtocolConfig{} err := json.Unmarshal(node.HttpJSON, config) if err != nil { return nil, err } err = config.Init() if err != nil { return nil, err } return config, nil } // DecodeHTTPS 解析HTTPS配置 func (this *UserNode) DecodeHTTPS(node *pb.UserNode) (*serverconfigs.HTTPSProtocolConfig, error) { if len(node.HttpsJSON) == 0 { return nil, nil } var config = &serverconfigs.HTTPSProtocolConfig{} err := json.Unmarshal(node.HttpsJSON, config) if err != nil { return nil, err } err = config.Init(context.TODO()) if err != nil { return nil, err } if config.SSLPolicyRef != nil { policyId := config.SSLPolicyRef.SSLPolicyId if policyId > 0 { rpcClient, err := rpc.SharedRPC() if err != nil { return nil, err } policyConfigResp, err := rpcClient.SSLPolicyRPC().FindEnabledSSLPolicyConfig(rpcClient.Context(0), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: policyId}) if err != nil { return nil, err } if len(policyConfigResp.SslPolicyJSON) > 0 { policyConfig := &sslconfigs.SSLPolicy{} err = json.Unmarshal(policyConfigResp.SslPolicyJSON, policyConfig) if err != nil { return nil, err } if len(policyConfig.Certs) > 0 { err = os.WriteFile(Tea.ConfigFile("https.cert.pem"), policyConfig.Certs[0].CertData, 0666) if err != nil { return nil, err } err = os.WriteFile(Tea.ConfigFile("https.key.pem"), policyConfig.Certs[0].KeyData, 0666) if err != nil { return nil, err } } } } } err = config.Init(context.TODO()) if err != nil { return nil, err } return config, nil } // 监听本地sock func (this *UserNode) listenSock() error { // 检查是否在运行 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 "info": exePath, _ := os.Executable() _ = cmd.Reply(&gosock.Command{ Code: "info", Params: map[string]interface{}{ "pid": os.Getpid(), "version": teaconst.Version, "path": exePath, }, }) case "stop": _ = cmd.ReplyOk() // 退出主进程 events.Notify(events.EventQuit) os.Exit(0) case "dev": // 切换到dev Tea.Env = Tea.EnvDev _ = cmd.ReplyOk() case "prod": // 切换到prod Tea.Env = Tea.EnvProd _ = cmd.ReplyOk() case "demo": teaconst.IsDemoMode = !teaconst.IsDemoMode _ = cmd.Reply(&gosock.Command{ Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode}, }) } }) 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 } // 设置DNS相关 func (this *UserNode) setupDNS() { config, loadErr := configloaders.LoadUIConfig() 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()) } }