github.com/TeaOSLab/EdgeNode@v1.3.8/cmd/edge-node/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"github.com/TeaOSLab/EdgeNode/internal/apps"
     8  	"github.com/TeaOSLab/EdgeNode/internal/configs"
     9  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
    10  	"github.com/TeaOSLab/EdgeNode/internal/nodes"
    11  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    12  	fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
    13  	"github.com/iwind/TeaGo/Tea"
    14  	_ "github.com/iwind/TeaGo/bootstrap"
    15  	"github.com/iwind/TeaGo/logs"
    16  	"github.com/iwind/TeaGo/maps"
    17  	"github.com/iwind/TeaGo/types"
    18  	"github.com/iwind/gosock/pkg/gosock"
    19  	"gopkg.in/yaml.v3"
    20  	"net"
    21  	"net/http"
    22  	_ "net/http/pprof"
    23  	"os"
    24  	"path/filepath"
    25  	"sort"
    26  	"strings"
    27  	"time"
    28  )
    29  
    30  func main() {
    31  	var app = apps.NewAppCmd().
    32  		Version(teaconst.Version).
    33  		Product(teaconst.ProductName).
    34  		Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|config|pprof|accesslog|uninstall]").
    35  		Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk|cache.garbage]").
    36  		Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
    37  
    38  	app.On("start:before", func() {
    39  		// validate config
    40  		_, err := configs.LoadAPIConfig()
    41  		if err != nil {
    42  			// validate cluster config
    43  			_, clusterErr := configs.LoadClusterConfig()
    44  			if clusterErr != nil { // fail again
    45  				fmt.Println("[ERROR]start failed: load api config from '" + Tea.ConfigFile(configs.ConfigFileName) + "' failed: " + err.Error())
    46  				os.Exit(0)
    47  			}
    48  		}
    49  	})
    50  	app.On("uninstall", func() {
    51  		// service
    52  		fmt.Println("Uninstall service ...")
    53  		var manager = utils.NewServiceManager(teaconst.ProcessName, teaconst.ProductName)
    54  		go func() {
    55  			_ = manager.Uninstall()
    56  		}()
    57  
    58  		// stop
    59  		fmt.Println("Stopping ...")
    60  		_, _ = gosock.NewTmpSock(teaconst.ProcessName).SendTimeout(&gosock.Command{Code: "stop"}, 1*time.Second)
    61  
    62  		// delete files
    63  		var exe, _ = os.Executable()
    64  		if len(exe) == 0 {
    65  			return
    66  		}
    67  
    68  		var dir = filepath.Dir(filepath.Dir(exe)) // ROOT / bin / exe
    69  
    70  		// verify dir
    71  		{
    72  			fmt.Println("Checking '" + dir + "' ...")
    73  			for _, subDir := range []string{"bin/" + filepath.Base(exe), "configs", "logs"} {
    74  				_, err := os.Stat(dir + "/" + subDir)
    75  				if err != nil {
    76  					fmt.Println("[ERROR]program directory structure has been broken, please remove it manually.")
    77  					return
    78  				}
    79  			}
    80  
    81  			fmt.Println("Removing '" + dir + "' ...")
    82  			err := os.RemoveAll(dir)
    83  			if err != nil {
    84  				fmt.Println("[ERROR]remove failed: " + err.Error())
    85  			}
    86  		}
    87  
    88  		// delete symbolic links
    89  		fmt.Println("Removing symbolic links ...")
    90  		_ = os.Remove("/usr/bin/" + teaconst.ProcessName)
    91  		_ = os.Remove("/var/log/" + teaconst.ProcessName)
    92  
    93  		// delete configs
    94  		// nothing to delete for EdgeNode
    95  
    96  		// delete sock
    97  		fmt.Println("Removing temporary files ...")
    98  		var tempDir = os.TempDir()
    99  		_ = os.Remove(tempDir + "/" + teaconst.ProcessName + ".sock")
   100  		_ = os.Remove(tempDir + "/" + teaconst.AccessLogSockName)
   101  
   102  		// cache ...
   103  		fmt.Println("Please delete cache directories by yourself.")
   104  
   105  		// done
   106  		fmt.Println("[DONE]")
   107  	})
   108  	app.On("test", func() {
   109  		err := nodes.NewNode().Test()
   110  		if err != nil {
   111  			_, _ = os.Stderr.WriteString(err.Error())
   112  		}
   113  	})
   114  	app.On("reload", func() {
   115  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   116  		reply, err := sock.Send(&gosock.Command{Code: "reload"})
   117  		if err != nil {
   118  			fmt.Println("[ERROR]" + err.Error())
   119  		} else {
   120  			var params = maps.NewMap(reply.Params)
   121  			if params.Has("error") {
   122  				fmt.Println("[ERROR]" + params.GetString("error"))
   123  			} else {
   124  				fmt.Println("ok")
   125  			}
   126  		}
   127  	})
   128  	app.On("daemon", func() {
   129  		nodes.NewNode().Daemon()
   130  	})
   131  	app.On("service", func() {
   132  		err := nodes.NewNode().InstallSystemService()
   133  		if err != nil {
   134  			fmt.Println("[ERROR]install failed: " + err.Error())
   135  			return
   136  		}
   137  		fmt.Println("done")
   138  	})
   139  	app.On("quit", func() {
   140  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   141  		_, err := sock.Send(&gosock.Command{Code: "quit"})
   142  		if err != nil {
   143  			fmt.Println("[ERROR]quit failed: " + err.Error())
   144  			return
   145  		}
   146  		fmt.Println("done")
   147  	})
   148  	app.On("pprof", func() {
   149  		var flagSet = flag.NewFlagSet("pprof", flag.ExitOnError)
   150  		var addr string
   151  		flagSet.StringVar(&addr, "addr", "", "")
   152  		_ = flagSet.Parse(os.Args[2:])
   153  
   154  		if len(addr) == 0 {
   155  			addr = "127.0.0.1:6060"
   156  		}
   157  		logs.Println("starting with pprof '" + addr + "'...")
   158  
   159  		go func() {
   160  			err := http.ListenAndServe(addr, nil)
   161  			if err != nil {
   162  				logs.Println("[ERROR]" + err.Error())
   163  			}
   164  		}()
   165  
   166  		var node = nodes.NewNode()
   167  		node.Start()
   168  	})
   169  	app.On("dbstat", func() {
   170  		teaconst.EnableDBStat = true
   171  
   172  		var node = nodes.NewNode()
   173  		node.Start()
   174  	})
   175  	app.On("trackers", func() {
   176  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   177  		reply, err := sock.Send(&gosock.Command{Code: "trackers"})
   178  		if err != nil {
   179  			fmt.Println("[ERROR]" + err.Error())
   180  		} else {
   181  			labelsMap, ok := reply.Params["labels"]
   182  			if ok {
   183  				labels, ok := labelsMap.(map[string]interface{})
   184  				if ok {
   185  					if len(labels) == 0 {
   186  						fmt.Println("no labels yet")
   187  					} else {
   188  						var labelNames = []string{}
   189  						for label := range labels {
   190  							labelNames = append(labelNames, label)
   191  						}
   192  						sort.Strings(labelNames)
   193  
   194  						for _, labelName := range labelNames {
   195  							fmt.Println(labelName + ": " + fmt.Sprintf("%.6f", labels[labelName]))
   196  						}
   197  					}
   198  				}
   199  			}
   200  		}
   201  	})
   202  	app.On("goman", func() {
   203  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   204  		reply, err := sock.Send(&gosock.Command{Code: "goman"})
   205  		if err != nil {
   206  			fmt.Println("[ERROR]" + err.Error())
   207  		} else {
   208  			instancesJSON, err := json.MarshalIndent(reply.Params, "", "  ")
   209  			if err != nil {
   210  				fmt.Println("[ERROR]" + err.Error())
   211  			} else {
   212  				fmt.Println(string(instancesJSON))
   213  			}
   214  		}
   215  	})
   216  	app.On("conns", func() {
   217  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   218  		reply, err := sock.Send(&gosock.Command{Code: "conns"})
   219  		if err != nil {
   220  			fmt.Println("[ERROR]" + err.Error())
   221  		} else {
   222  			resultJSON, err := json.MarshalIndent(reply.Params, "", "  ")
   223  			if err != nil {
   224  				fmt.Println("[ERROR]" + err.Error())
   225  			} else {
   226  				fmt.Println(string(resultJSON))
   227  			}
   228  		}
   229  	})
   230  	app.On("gc", func() {
   231  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   232  		reply, err := sock.Send(&gosock.Command{Code: "gc"})
   233  		if err != nil {
   234  			fmt.Println("[ERROR]" + err.Error())
   235  		} else {
   236  			if reply == nil {
   237  				fmt.Println("ok")
   238  			} else {
   239  				var paramMap = maps.NewMap(reply.Params)
   240  				var pauseMS = paramMap.GetFloat64("pauseMS")
   241  				var costMS = paramMap.GetFloat64("costMS")
   242  				fmt.Printf("ok, cost: %.4fms, pause: %.4fms", costMS, pauseMS)
   243  			}
   244  		}
   245  	})
   246  	app.On("ip.drop", func() {
   247  		var args = os.Args[2:]
   248  		if len(args) == 0 {
   249  			fmt.Println("Usage: edge-node ip.drop IP [--timeout=SECONDS] [--async]")
   250  			return
   251  		}
   252  		var ip = args[0]
   253  		if len(net.ParseIP(ip)) == 0 {
   254  			fmt.Println("IP '" + ip + "' is invalid")
   255  			return
   256  		}
   257  		var timeoutSeconds = 0
   258  		var options = app.ParseOptions(args[1:])
   259  		timeout, ok := options["timeout"]
   260  		if ok {
   261  			timeoutSeconds = types.Int(timeout[0])
   262  		}
   263  		var async = false
   264  		_, ok = options["async"]
   265  		if ok {
   266  			async = true
   267  		}
   268  
   269  		fmt.Println("drop ip '" + ip + "' for '" + types.String(timeoutSeconds) + "' seconds")
   270  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   271  		reply, err := sock.Send(&gosock.Command{
   272  			Code: "dropIP",
   273  			Params: map[string]interface{}{
   274  				"ip":             ip,
   275  				"timeoutSeconds": timeoutSeconds,
   276  				"async":          async,
   277  			},
   278  		})
   279  		if err != nil {
   280  			fmt.Println("[ERROR]" + err.Error())
   281  		} else {
   282  			var errString = maps.NewMap(reply.Params).GetString("error")
   283  			if len(errString) > 0 {
   284  				fmt.Println("[ERROR]" + errString)
   285  			} else {
   286  				fmt.Println("ok")
   287  			}
   288  		}
   289  	})
   290  	app.On("ip.reject", func() {
   291  		var args = os.Args[2:]
   292  		if len(args) == 0 {
   293  			fmt.Println("Usage: edge-node ip.reject IP [--timeout=SECONDS]")
   294  			return
   295  		}
   296  		var ip = args[0]
   297  		if len(net.ParseIP(ip)) == 0 {
   298  			fmt.Println("IP '" + ip + "' is invalid")
   299  			return
   300  		}
   301  		var timeoutSeconds = 0
   302  		var options = app.ParseOptions(args[1:])
   303  		timeout, ok := options["timeout"]
   304  		if ok {
   305  			timeoutSeconds = types.Int(timeout[0])
   306  		}
   307  
   308  		fmt.Println("reject ip '" + ip + "' for '" + types.String(timeoutSeconds) + "' seconds")
   309  
   310  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   311  		reply, err := sock.Send(&gosock.Command{
   312  			Code: "rejectIP",
   313  			Params: map[string]interface{}{
   314  				"ip":             ip,
   315  				"timeoutSeconds": timeoutSeconds,
   316  			},
   317  		})
   318  		if err != nil {
   319  			fmt.Println("[ERROR]" + err.Error())
   320  		} else {
   321  			var errString = maps.NewMap(reply.Params).GetString("error")
   322  			if len(errString) > 0 {
   323  				fmt.Println("[ERROR]" + errString)
   324  			} else {
   325  				fmt.Println("ok")
   326  			}
   327  		}
   328  	})
   329  	app.On("ip.close", func() {
   330  		var args = os.Args[2:]
   331  		if len(args) == 0 {
   332  			fmt.Println("Usage: edge-node ip.close IP")
   333  			return
   334  		}
   335  		var ip = args[0]
   336  		if len(net.ParseIP(ip)) == 0 {
   337  			fmt.Println("IP '" + ip + "' is invalid")
   338  			return
   339  		}
   340  
   341  		fmt.Println("close ip '" + ip)
   342  
   343  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   344  		reply, err := sock.Send(&gosock.Command{
   345  			Code: "closeIP",
   346  			Params: map[string]any{
   347  				"ip": ip,
   348  			},
   349  		})
   350  		if err != nil {
   351  			fmt.Println("[ERROR]" + err.Error())
   352  		} else {
   353  			var errString = maps.NewMap(reply.Params).GetString("error")
   354  			if len(errString) > 0 {
   355  				fmt.Println("[ERROR]" + errString)
   356  			} else {
   357  				fmt.Println("ok")
   358  			}
   359  		}
   360  	})
   361  	app.On("ip.remove", func() {
   362  		var args = os.Args[2:]
   363  		if len(args) == 0 {
   364  			fmt.Println("Usage: edge-node ip.remove IP")
   365  			return
   366  		}
   367  		var ip = args[0]
   368  		if len(net.ParseIP(ip)) == 0 {
   369  			fmt.Println("IP '" + ip + "' is invalid")
   370  			return
   371  		}
   372  
   373  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   374  		reply, err := sock.Send(&gosock.Command{
   375  			Code: "removeIP",
   376  			Params: map[string]interface{}{
   377  				"ip": ip,
   378  			},
   379  		})
   380  		if err != nil {
   381  			fmt.Println("[ERROR]" + err.Error())
   382  		} else {
   383  			var errString = maps.NewMap(reply.Params).GetString("error")
   384  			if len(errString) > 0 {
   385  				fmt.Println("[ERROR]" + errString)
   386  			} else {
   387  				fmt.Println("ok")
   388  			}
   389  		}
   390  	})
   391  	app.On("accesslog", func() {
   392  		// local sock
   393  		var tmpDir = os.TempDir()
   394  		var sockFile = tmpDir + "/" + teaconst.AccessLogSockName
   395  		_, err := os.Stat(sockFile)
   396  		if err != nil {
   397  			if !os.IsNotExist(err) {
   398  				fmt.Println("[ERROR]" + err.Error())
   399  				return
   400  			}
   401  		}
   402  
   403  		var processSock = gosock.NewTmpSock(teaconst.ProcessName)
   404  		reply, err := processSock.Send(&gosock.Command{
   405  			Code: "accesslog",
   406  		})
   407  		if err != nil {
   408  			fmt.Println("[ERROR]" + err.Error())
   409  			return
   410  		}
   411  		if reply.Code == "error" {
   412  			var errString = maps.NewMap(reply.Params).GetString("error")
   413  			if len(errString) > 0 {
   414  				fmt.Println("[ERROR]" + errString)
   415  				return
   416  			}
   417  		}
   418  
   419  		conn, err := net.Dial("unix", sockFile)
   420  		if err != nil {
   421  			fmt.Println("[ERROR]start reading access log failed: " + err.Error())
   422  			return
   423  		}
   424  		defer func() {
   425  			_ = conn.Close()
   426  		}()
   427  		var buf = make([]byte, 1024)
   428  		for {
   429  			n, err := conn.Read(buf)
   430  			if n > 0 {
   431  				fmt.Print(string(buf[:n]))
   432  			}
   433  			if err != nil {
   434  				break
   435  			}
   436  		}
   437  	})
   438  	app.On("bandwidth", func() {
   439  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   440  		reply, err := sock.Send(&gosock.Command{Code: "bandwidth"})
   441  		if err != nil {
   442  			fmt.Println("[ERROR]" + err.Error())
   443  			return
   444  		}
   445  		var statsMap = maps.NewMap(reply.Params).Get("stats")
   446  		statsJSON, err := json.MarshalIndent(statsMap, "", "  ")
   447  		if err != nil {
   448  			fmt.Println("[ERROR]" + err.Error())
   449  			return
   450  		}
   451  		fmt.Println(string(statsJSON))
   452  	})
   453  	app.On("disk", func() {
   454  		var args = os.Args[2:]
   455  		if len(args) > 0 {
   456  			switch args[0] {
   457  			case "speed":
   458  				speedMB, isFast, err := fsutils.CheckDiskIsFast()
   459  				if err != nil {
   460  					fmt.Println("[ERROR]" + err.Error())
   461  				} else {
   462  					fmt.Printf("Speed: %.0fMB/s\n", speedMB)
   463  					if isFast {
   464  						fmt.Println("IsFast: true")
   465  					} else {
   466  						fmt.Println("IsFast: false")
   467  					}
   468  				}
   469  			default:
   470  				fmt.Println("Usage: edge-node disk [speed]")
   471  			}
   472  		} else {
   473  			fmt.Println("Usage: edge-node disk [speed]")
   474  		}
   475  	})
   476  	app.On("cache.garbage", func() {
   477  		fmt.Println("scanning ...")
   478  
   479  		var shouldDelete bool
   480  		for _, arg := range os.Args {
   481  			if strings.TrimLeft(arg, "-") == "delete" {
   482  				shouldDelete = true
   483  			}
   484  		}
   485  
   486  		var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
   487  		progressSock.OnCommand(func(cmd *gosock.Command) {
   488  			var params = maps.NewMap(cmd.Params)
   489  			if cmd.Code == "progress" {
   490  				fmt.Printf("%.2f%% %d\n", params.GetFloat64("progress")*100, params.GetInt("count"))
   491  				_ = cmd.ReplyOk()
   492  			}
   493  		})
   494  		go func() {
   495  			_ = progressSock.Listen()
   496  		}()
   497  		time.Sleep(1 * time.Second)
   498  
   499  		var sock = gosock.NewTmpSock(teaconst.ProcessName)
   500  		reply, err := sock.Send(&gosock.Command{
   501  			Code:   "cache.garbage",
   502  			Params: map[string]any{"delete": shouldDelete},
   503  		})
   504  		if err != nil {
   505  			fmt.Println("[ERROR]" + err.Error())
   506  			return
   507  		}
   508  
   509  		var params = maps.NewMap(reply.Params)
   510  		if params.GetBool("isOk") {
   511  			var count = params.GetInt("count")
   512  			fmt.Println("found", count, "bad caches")
   513  
   514  			if count > 0 {
   515  				fmt.Println("======")
   516  				var sampleFiles = params.GetSlice("sampleFiles")
   517  				for _, file := range sampleFiles {
   518  					fmt.Println(types.String(file))
   519  				}
   520  				if count > len(sampleFiles) {
   521  					fmt.Println("... more files")
   522  				}
   523  			}
   524  		} else {
   525  			fmt.Println("[ERROR]" + params.GetString("error"))
   526  		}
   527  	})
   528  	app.On("config", func() {
   529  		var configString = os.Args[len(os.Args)-1]
   530  		if configString == "config" {
   531  			fmt.Println("Usage: edge-node config '\nrpc.endpoints: [\"...\"]\nnodeId: \"...\"\nsecret: \"...\"\n'")
   532  			return
   533  		}
   534  
   535  		var config = &configs.APIConfig{}
   536  		err := yaml.Unmarshal([]byte(configString), config)
   537  		if err != nil {
   538  			fmt.Println("[ERROR]decode config failed: " + err.Error())
   539  			return
   540  		}
   541  
   542  		err = config.Init()
   543  		if err != nil {
   544  			fmt.Println("[ERROR]validate config failed: " + err.Error())
   545  			return
   546  		}
   547  
   548  		// marshal again
   549  		configYAML, err := yaml.Marshal(config)
   550  		if err != nil {
   551  			fmt.Println("[ERROR]encode config failed: " + err.Error())
   552  			return
   553  		}
   554  
   555  		err = os.WriteFile(Tea.Root + "/configs/api_node.yaml", configYAML, 0666)
   556  		if err != nil {
   557  			fmt.Println("[ERROR]write config failed: " + err.Error())
   558  			return
   559  		}
   560  
   561  		fmt.Println("success")
   562  	})
   563  	app.Run(func() {
   564  		var node = nodes.NewNode()
   565  		node.Start()
   566  	})
   567  }