diff --git a/README.md b/README.md index d4cb43a..b20dd07 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ **TOC** -* [☑️ 特性](#-特性) +* [:ballot_box_with_check: 特性](#ballot_box_with_check-特性) * [🚀 安装和升级](#-安装和升级) * [Linux/MacOS](#linuxmacos) * [ArchLinux](#archlinux) @@ -28,8 +28,7 @@ - -## ☑️ 特性 +## :ballot_box_with_check: 特性 - 单文件运行,多平台兼容,无需安装任何依赖。Windows运行截图: diff --git a/cmd/kd.go b/cmd/kd.go index 12d9332..690c178 100644 --- a/cmd/kd.go +++ b/cmd/kd.go @@ -22,7 +22,7 @@ import ( "go.uber.org/zap" ) -var VERSION = "v0.0.4" +var VERSION = "v0.0.5" func showPrompt() { exename, err := pkg.GetExecutableBasename() @@ -114,6 +114,7 @@ func flagUpdate(ctx *cli.Context, _ bool) (err error) { if doUpdate { emoji.Println(":lightning: Let's update now") + go daemon.KillDaemonIfRunning() err = update.UpdateBinary(VERSION) } return err @@ -162,7 +163,7 @@ func flagEditConfig(*cli.Context, bool) error { } func flagStatus(*cli.Context, bool) error { - di := internal.GetDaemonInfo() + di := daemon.GetDaemonInfo() d.EchoRun("运行和相关配置信息如下:") fmt.Printf(" Daemon端口:%s\n", di.Port) fmt.Printf(" Daemon PID:%d\n", di.PID) @@ -254,7 +255,7 @@ func main() { } } - if cfg.FileExists && cfg.ModTime > internal.GetDaemonInfo().StartTime { + if cfg.FileExists && cfg.ModTime > daemon.GetDaemonInfo().StartTime { d.EchoWarn("检测到配置文件发生修改,正在重启守护进程") flagStop(cCtx, true) flagDaemon(cCtx, true) diff --git a/internal/client.go b/internal/client.go index 8fe97d6..cb7f478 100644 --- a/internal/client.go +++ b/internal/client.go @@ -10,6 +10,7 @@ package internal import ( "fmt" + "os" "regexp" "strings" @@ -18,20 +19,52 @@ import ( "github.com/Karmenzind/kd/internal/daemon" "github.com/Karmenzind/kd/internal/model" q "github.com/Karmenzind/kd/internal/query" + "github.com/Karmenzind/kd/pkg" d "github.com/Karmenzind/kd/pkg/decorate" + "github.com/Karmenzind/kd/pkg/proc" "github.com/Karmenzind/kd/pkg/str" "go.uber.org/zap" ) func ensureDaemon(running chan bool) { - if !daemon.ServerIsRunning() { - err := daemon.StartDaemonProcess() + p, _ := daemon.FindServerProcess() + var err error + if p == nil { + d.EchoRun("未找到守护进程,正在启动...") + err = daemon.StartDaemonProcess() if err != nil { - d.EchoRun("未找到守护进程,正在启动...") d.EchoFatal(err.Error()) } - running <- true + } else { + exename, err := pkg.GetExecutablePath() + if err == nil { + runningExename, _ := p.Exe() + // TODO (k): <2024-01-03> 增加检查版本 + if exename != runningExename { + d.EchoWarn(fmt.Sprintf("正在运行的守护程序(%s)与当前程序(%s)文件信息或版本不一致,将尝试重新启动守护进程", runningExename, exename)) + err := proc.KillProcess(p) + if err != nil { + cmd := proc.GetKillCMD(p.Pid) + d.EchoError(fmt.Sprintf("停止进程%v失败,请手动执行:", p.Pid)) + fmt.Println(cmd.String()) + os.Exit(1) + } + d.EchoRun("已终止,正在启动新的守护进程...") + err = daemon.StartDaemonProcess() + if err != nil { + d.EchoFatal(err.Error()) + } + } + } } + // if !daemon.ServerIsRunning() { + // err := daemon.StartDaemonProcess() + // if err != nil { + // d.EchoRun("未找到守护进程,正在启动...") + // d.EchoFatal(err.Error()) + // } + // running <- true + // } running <- true } diff --git a/internal/daemon/process.go b/internal/daemon/process.go index 99034f2..fe566b3 100644 --- a/internal/daemon/process.go +++ b/internal/daemon/process.go @@ -2,20 +2,51 @@ package daemon import ( "fmt" + "os" "os/exec" + "path/filepath" "runtime" - "strconv" "strings" - "syscall" "time" + "github.com/Karmenzind/kd/internal/cache" "github.com/Karmenzind/kd/pkg" d "github.com/Karmenzind/kd/pkg/decorate" + "github.com/Karmenzind/kd/pkg/proc" "github.com/shirou/gopsutil/v3/process" "go.uber.org/zap" ) +type DaemonInfoType struct { + StartTime int64 + Port string + PID int +} + +var DaemonInfo = &DaemonInfoType{} + +func RecordRunInfo(port string) { + DaemonInfo.StartTime = time.Now().Unix() + DaemonInfo.PID = os.Getpid() + DaemonInfo.Port = port + pkg.SaveJson( + filepath.Join(cache.CACHE_RUN_PATH, "daemon.json"), + DaemonInfo, + ) + zap.S().Infof("Recorded running information of daemon %+v", DaemonInfo) +} + +func GetDaemonInfo() *DaemonInfoType { + if *DaemonInfo == (DaemonInfoType{}) { + err := pkg.LoadJson(filepath.Join(cache.CACHE_RUN_PATH, "daemon.json"), DaemonInfo) + if err != nil { + d.EchoFatal("获取守护进程信息失败,请执行`kd --stop && kd --daemon`") + } + } + return DaemonInfo +} + func getKdPIDs() { var cmd *exec.Cmd @@ -40,13 +71,28 @@ func FindServerProcess() (*process.Process, error) { if err != nil { return nil, err } + di := GetDaemonInfo() for _, p := range processes { // XXX err n, _ := p.Name() + if p.Pid == int32(di.PID) { + zap.S().Debugf("Got daemon process %v via daemon info", di.PID) + cmdslice, _ := p.CmdlineSlice() + if len(cmdslice) > 1 && cmdslice[1] == "--server" { + return p, nil + } + } + if n == "kd" || (runtime.GOOS == "windows" && n == "kd.exe") { cmd, _ := p.Cmdline() + if p.Pid == 13328 { + name, _ := p.Name() + cmdslice, _ := p.CmdlineSlice() + zap.S().Debugf("13328:Name: `%s` Cmd: `%s` cmdslice: `%+v`", name, cmd, cmdslice) + } + zap.S().Debugf("Found process kd.exe with CMD: %s", cmd) if strings.Contains(cmd, " --server") { - zap.S().Debugf("Found process %+v Cmd: %s", p, cmd) + zap.S().Debugf("Found process %+v Cmd: `%s`", p, cmd) return p, nil } } @@ -91,43 +137,18 @@ func StartDaemonProcess() error { func KillDaemonIfRunning() error { p, err := FindServerProcess() - var trySysKill bool if err == nil { if p == nil { d.EchoOkay("未发现守护进程,无需停止") return nil - } else if runtime.GOOS != "windows" { - zap.S().Infof("Found running daemon PID: %d,", p.Pid) - errSig := p.SendSignal(syscall.SIGINT) - if errSig != nil { - zap.S().Warnf("Failed to stop PID %d with syscall.SIGINT: %s", p.Pid, errSig) - trySysKill = true - } - } else { - trySysKill = true } } else { zap.S().Warnf("[process] Failed to find daemon: %s", err) - trySysKill = true - } - pidStr := strconv.Itoa(int(p.Pid)) - - if trySysKill { - var cmd *exec.Cmd - switch runtime.GOOS { - case "windows": - cmd = exec.Command("taskkill", "/F", "/T", "/PID", pidStr) - // cmd = exec.Command("taskkill", "/im", "kd", "/T", "/F") - case "linux": - cmd = exec.Command("kill", "-9", pidStr) - // cmd = exec.Command("killall", "kd") - } - output, err := cmd.Output() - zap.S().Infof("Executed '%s'. Output %s", cmd, output) - if err != nil { - zap.S().Warnf("Failed to kill daemon with system command. Error: %s", output, err) - } + return err } + + err = proc.KillProcess(p) + if err == nil { zap.S().Info("Terminated daemon process.") d.EchoOkay("守护进程已经停止") diff --git a/internal/server.go b/internal/server.go index 5a6d6df..b7ee0ef 100644 --- a/internal/server.go +++ b/internal/server.go @@ -6,52 +6,16 @@ import ( "fmt" "io" "net" - "os" - "path/filepath" - "time" - d "github.com/Karmenzind/kd/pkg/decorate" - - "github.com/Karmenzind/kd/internal/cache" "github.com/Karmenzind/kd/internal/daemon" "github.com/Karmenzind/kd/internal/model" "github.com/Karmenzind/kd/internal/query" - "github.com/Karmenzind/kd/pkg" "go.uber.org/zap" ) // TODO 支持自定义 var SERVER_PORT = 19707 -type DaemonInfoType struct { - StartTime int64 - Port string - PID int -} - -var DaemonInfo = &DaemonInfoType{} - -func recordRunningInfo(port string) { - DaemonInfo.StartTime = time.Now().Unix() - DaemonInfo.PID = os.Getpid() - DaemonInfo.Port = port - pkg.SaveJson( - filepath.Join(cache.CACHE_RUN_PATH, "daemon.json"), - DaemonInfo, - ) - zap.S().Infof("Recorded running information of daemon %+v", DaemonInfo) -} - -func GetDaemonInfo() *DaemonInfoType { - if *DaemonInfo == (DaemonInfoType{}) { - err := pkg.LoadJson(filepath.Join(cache.CACHE_RUN_PATH, "daemon.json"), DaemonInfo) - if err != nil { - d.EchoFatal("获取守护进程信息失败,请执行`kd --stop && kd --daemon`") - } - } - return DaemonInfo -} - func StartServer() (err error) { IS_SERVER = true addr := fmt.Sprintf("localhost:%d", SERVER_PORT) @@ -67,7 +31,7 @@ func StartServer() (err error) { return err } daemon.InitCron() - go recordRunningInfo(port) + go daemon.RecordRunInfo(port) fmt.Printf("Listening on host: %s, port: %s\n", host, port) diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go new file mode 100644 index 0000000..14a1d16 --- /dev/null +++ b/pkg/proc/proc.go @@ -0,0 +1,51 @@ +package proc + +import ( + "os/exec" + "runtime" + "strconv" + "syscall" + + "github.com/shirou/gopsutil/v3/process" + "go.uber.org/zap" +) + +// not for Win +func KillProcess(p *process.Process) (err error) { + if runtime.GOOS != "windows" { + errSig := p.SendSignal(syscall.SIGINT) + if errSig == nil { + if yes, errCheck := p.IsRunning(); errCheck == nil && !yes { + zap.S().Infof("Stopped process %v with SIGINT.", p.Pid) + return + } + } else { + zap.S().Warnf("Failed to stop PID %v with syscall.SIGINT: %s", p.Pid, errSig) + } + } + return SysKillPID(p.Pid) +} + +func GetKillCMD(pid int32) *exec.Cmd { + pidStr := strconv.Itoa(int(pid)) + var cmd *exec.Cmd + switch runtime.GOOS { + case "windows": + cmd = exec.Command("taskkill", "/F", "/T", "/PID", pidStr) + // cmd = exec.Command("taskkill", "/im", "kd", "/T", "/F") + case "linux": + cmd = exec.Command("kill", "-9", pidStr) + // cmd = exec.Command("killall", "kd") + } + return cmd +} + +func SysKillPID(pid int32) (err error) { + cmd := GetKillCMD(pid) + output, err := cmd.Output() + zap.S().Infof("Executed '%s'. Output %s", cmd, output) + if err != nil { + zap.S().Warnf("Failed to kill %v with system command. Output: `%s` Error: `%s`", pid, output, err) + } + return +} diff --git a/plan.md b/plan.md index c4d1eaf..f39cea8 100644 --- a/plan.md +++ b/plan.md @@ -40,7 +40,7 @@ # BUG ## Risk -- 实际文件名 不改的时候的process_name +- 实际文件名 不改的时候的process_name,增加同时校验kd和当前文件名 ## low priority - (action) linux 386