github.com/TeaOSLab/EdgeNode@v1.3.8/internal/apps/app_cmd.go (about)

     1  package apps
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
     7  	executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
     8  	"github.com/iwind/TeaGo/logs"
     9  	"github.com/iwind/TeaGo/maps"
    10  	"github.com/iwind/TeaGo/types"
    11  	"github.com/iwind/gosock/pkg/gosock"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  	"syscall"
    19  	"time"
    20  )
    21  
    22  // AppCmd App命令帮助
    23  type AppCmd struct {
    24  	product       string
    25  	version       string
    26  	usages        []string
    27  	options       []*CommandHelpOption
    28  	appendStrings []string
    29  
    30  	directives []*Directive
    31  
    32  	sock *gosock.Sock
    33  }
    34  
    35  func NewAppCmd() *AppCmd {
    36  	return &AppCmd{
    37  		sock: gosock.NewTmpSock(teaconst.ProcessName),
    38  	}
    39  }
    40  
    41  type CommandHelpOption struct {
    42  	Code        string
    43  	Description string
    44  }
    45  
    46  // Product 产品
    47  func (this *AppCmd) Product(product string) *AppCmd {
    48  	this.product = product
    49  	return this
    50  }
    51  
    52  // Version 版本
    53  func (this *AppCmd) Version(version string) *AppCmd {
    54  	this.version = version
    55  	return this
    56  }
    57  
    58  // Usage 使用方法
    59  func (this *AppCmd) Usage(usage string) *AppCmd {
    60  	this.usages = append(this.usages, usage)
    61  	return this
    62  }
    63  
    64  // Option 选项
    65  func (this *AppCmd) Option(code string, description string) *AppCmd {
    66  	this.options = append(this.options, &CommandHelpOption{
    67  		Code:        code,
    68  		Description: description,
    69  	})
    70  	return this
    71  }
    72  
    73  // Append 附加内容
    74  func (this *AppCmd) Append(appendString string) *AppCmd {
    75  	this.appendStrings = append(this.appendStrings, appendString)
    76  	return this
    77  }
    78  
    79  // Print 打印
    80  func (this *AppCmd) Print() {
    81  	fmt.Println(this.product + " v" + this.version)
    82  
    83  	fmt.Println("Usage:")
    84  	for _, usage := range this.usages {
    85  		fmt.Println("   " + usage)
    86  	}
    87  
    88  	if len(this.options) > 0 {
    89  		fmt.Println("")
    90  		fmt.Println("Options:")
    91  
    92  		var spaces = 20
    93  		var max = 40
    94  		for _, option := range this.options {
    95  			l := len(option.Code)
    96  			if l < max && l > spaces {
    97  				spaces = l + 4
    98  			}
    99  		}
   100  
   101  		for _, option := range this.options {
   102  			if len(option.Code) > max {
   103  				fmt.Println("")
   104  				fmt.Println("  " + option.Code)
   105  				option.Code = ""
   106  			}
   107  
   108  			fmt.Printf("  %-"+strconv.Itoa(spaces)+"s%s\n", option.Code, ": "+option.Description)
   109  		}
   110  	}
   111  
   112  	if len(this.appendStrings) > 0 {
   113  		fmt.Println("")
   114  		for _, s := range this.appendStrings {
   115  			fmt.Println(s)
   116  		}
   117  	}
   118  }
   119  
   120  // On 添加指令
   121  func (this *AppCmd) On(arg string, callback func()) {
   122  	this.directives = append(this.directives, &Directive{
   123  		Arg:      arg,
   124  		Callback: callback,
   125  	})
   126  }
   127  
   128  // Run 运行
   129  func (this *AppCmd) Run(main func()) {
   130  	// 获取参数
   131  	var args = os.Args[1:]
   132  	if len(args) > 0 {
   133  		var mainArg = args[0]
   134  		this.callDirective(mainArg + ":before")
   135  
   136  		switch mainArg {
   137  		case "-v", "version", "-version", "--version":
   138  			this.runVersion()
   139  			return
   140  		case "?", "help", "-help", "h", "-h":
   141  			this.runHelp()
   142  			return
   143  		case "start":
   144  			this.runStart()
   145  			return
   146  		case "stop":
   147  			this.runStop()
   148  			return
   149  		case "restart":
   150  			this.runRestart()
   151  			return
   152  		case "status":
   153  			this.runStatus()
   154  			return
   155  		}
   156  
   157  		// 查找指令
   158  		for _, directive := range this.directives {
   159  			if directive.Arg == mainArg {
   160  				directive.Callback()
   161  				return
   162  			}
   163  		}
   164  
   165  		fmt.Println("unknown command '" + mainArg + "'")
   166  
   167  		return
   168  	}
   169  
   170  	// 日志
   171  	var writer = new(LogWriter)
   172  	writer.Init()
   173  	logs.SetWriter(writer)
   174  
   175  	// 运行主函数
   176  	main()
   177  }
   178  
   179  // 版本号
   180  func (this *AppCmd) runVersion() {
   181  	fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
   182  }
   183  
   184  // 帮助
   185  func (this *AppCmd) runHelp() {
   186  	this.Print()
   187  }
   188  
   189  // 启动
   190  func (this *AppCmd) runStart() {
   191  	var pid = this.getPID()
   192  	if pid > 0 {
   193  		fmt.Println(this.product+" already started, pid:", pid)
   194  		return
   195  	}
   196  
   197  	_ = os.Setenv("EdgeBackground", "on")
   198  
   199  	var cmd = exec.Command(this.exe())
   200  	cmd.SysProcAttr = &syscall.SysProcAttr{
   201  		Foreground: false,
   202  		Setsid:     true,
   203  	}
   204  
   205  	err := cmd.Start()
   206  	if err != nil {
   207  		fmt.Println(this.product+"  start failed:", err.Error())
   208  		return
   209  	}
   210  
   211  	// create symbolic links
   212  	_ = this.createSymLinks()
   213  
   214  	fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
   215  }
   216  
   217  // 停止
   218  func (this *AppCmd) runStop() {
   219  	var pid = this.getPID()
   220  	if pid == 0 {
   221  		fmt.Println(this.product + " not started yet")
   222  		return
   223  	}
   224  
   225  	// 从systemd中停止
   226  	if runtime.GOOS == "linux" {
   227  		systemctl, _ := executils.LookPath("systemctl")
   228  		if len(systemctl) > 0 {
   229  			go func() {
   230  				// 有可能会长时间执行,这里不阻塞进程
   231  				_ = exec.Command(systemctl, "stop", teaconst.SystemdServiceName).Run()
   232  			}()
   233  		}
   234  	}
   235  
   236  	// 如果仍在运行,则发送停止指令
   237  	_, _ = this.sock.SendTimeout(&gosock.Command{Code: "stop"}, 1*time.Second)
   238  
   239  	fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
   240  }
   241  
   242  // 重启
   243  func (this *AppCmd) runRestart() {
   244  	this.runStop()
   245  	time.Sleep(1 * time.Second)
   246  	this.runStart()
   247  }
   248  
   249  // 状态
   250  func (this *AppCmd) runStatus() {
   251  	var pid = this.getPID()
   252  	if pid == 0 {
   253  		fmt.Println(this.product + " not started yet")
   254  		return
   255  	}
   256  
   257  	fmt.Println(this.product + " is running, pid: " + types.String(pid))
   258  }
   259  
   260  // 获取当前的PID
   261  func (this *AppCmd) getPID() int {
   262  	if !this.sock.IsListening() {
   263  		return 0
   264  	}
   265  
   266  	reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
   267  	if err != nil {
   268  		return 0
   269  	}
   270  	return maps.NewMap(reply.Params).GetInt("pid")
   271  }
   272  
   273  // ParseOptions 分析参数中的选项
   274  func (this *AppCmd) ParseOptions(args []string) map[string][]string {
   275  	var result = map[string][]string{}
   276  	for _, arg := range args {
   277  		var pieces = strings.SplitN(arg, "=", 2)
   278  		var key = strings.TrimLeft(pieces[0], "- ")
   279  		key = strings.TrimSpace(key)
   280  		var value = ""
   281  		if len(pieces) == 2 {
   282  			value = strings.TrimSpace(pieces[1])
   283  		}
   284  		result[key] = append(result[key], value)
   285  	}
   286  	return result
   287  }
   288  
   289  func (this *AppCmd) exe() string {
   290  	var exe, _ = os.Executable()
   291  	if len(exe) == 0 {
   292  		exe = os.Args[0]
   293  	}
   294  	return exe
   295  }
   296  
   297  // 创建软链接
   298  func (this *AppCmd) createSymLinks() error {
   299  	if runtime.GOOS != "linux" {
   300  		return nil
   301  	}
   302  
   303  	var exe, _ = os.Executable()
   304  	if len(exe) == 0 {
   305  		return nil
   306  	}
   307  
   308  	var errorList = []string{}
   309  
   310  	// bin
   311  	{
   312  		var target = "/usr/bin/" + teaconst.ProcessName
   313  		old, _ := filepath.EvalSymlinks(target)
   314  		if old != exe {
   315  			_ = os.Remove(target)
   316  			err := os.Symlink(exe, target)
   317  			if err != nil {
   318  				errorList = append(errorList, err.Error())
   319  			}
   320  		}
   321  	}
   322  
   323  	// log
   324  	{
   325  		var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
   326  		var target = "/var/log/" + teaconst.ProcessName + ".log"
   327  		old, _ := filepath.EvalSymlinks(target)
   328  		if old != realPath {
   329  			_ = os.Remove(target)
   330  			err := os.Symlink(realPath, target)
   331  			if err != nil {
   332  				errorList = append(errorList, err.Error())
   333  			}
   334  		}
   335  	}
   336  
   337  	if len(errorList) > 0 {
   338  		return errors.New(strings.Join(errorList, "\n"))
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func (this *AppCmd) callDirective(code string) {
   345  	for _, directive := range this.directives {
   346  		if directive.Arg == code {
   347  			if directive.Callback != nil {
   348  				directive.Callback()
   349  			}
   350  			return
   351  		}
   352  	}
   353  }