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 }