Skip to content

Commit beb2c04

Browse files
committed
feat(cmd): 添加守护进程管理命令集
添加守护进程管理相关命令,包括启动、停止、重启、状态查看和日志跟踪功能 重构代码结构,将命令逻辑拆分到独立文件 添加版本更新检查和处理逻辑 更新protobuf定义,增加版本更新通知类型
1 parent 1b71a03 commit beb2c04

File tree

13 files changed

+805
-608
lines changed

13 files changed

+805
-608
lines changed

cmd/daemon.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strconv"
10+
"strings"
11+
"syscall"
12+
"time"
13+
14+
"github.com/orange-juzipi/cert-deploy/internal/updater"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
// CreateDaemonCmd 创建守护进程命令
19+
func CreateDaemonCmd() *cobra.Command {
20+
return &cobra.Command{
21+
Use: "daemon",
22+
Short: "启动守护进程(后台运行)",
23+
Long: "在后台启动证书部署守护进程,进程崩溃或更新后将自动重启",
24+
Run: func(cmd *cobra.Command, args []string) {
25+
// 检查是否已经在运行,如果是则先停止
26+
if IsRunning() {
27+
fmt.Println("守护进程已在运行,正在重启...")
28+
if err := StopDaemon(); err != nil {
29+
fmt.Printf("停止失败: %v\n", err)
30+
os.Exit(1)
31+
}
32+
time.Sleep(2 * time.Second)
33+
}
34+
35+
execPath, err := os.Executable()
36+
if err != nil {
37+
fmt.Printf("获取可执行文件路径失败: %v\n", err)
38+
os.Exit(1)
39+
}
40+
41+
supervisorCmd := exec.Command(execPath, "_supervisor", "-c", ConfigFile)
42+
if err := supervisorCmd.Start(); err != nil {
43+
fmt.Printf("启动失败: %v\n", err)
44+
os.Exit(1)
45+
}
46+
47+
time.Sleep(500 * time.Millisecond)
48+
49+
if !IsRunning() {
50+
fmt.Println("启动失败")
51+
os.Exit(1)
52+
}
53+
54+
fmt.Println("守护进程已启动")
55+
56+
// 异步检查更新
57+
go func() {
58+
time.Sleep(1 * time.Second)
59+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
60+
defer cancel()
61+
62+
info, err := updater.CheckUpdate(ctx)
63+
if err == nil && info.HasUpdate {
64+
fmt.Printf("\n发现新版本: %s -> %s\n", info.CurrentVersion, info.LatestVersion)
65+
fmt.Println("执行 './cert-deploy update' 进行更新")
66+
}
67+
}()
68+
69+
time.Sleep(100 * time.Millisecond)
70+
},
71+
}
72+
}
73+
74+
// createSupervisorCmd 创建 supervisor 命令(内部使用)
75+
func createSupervisorCmd() *cobra.Command {
76+
return &cobra.Command{
77+
Use: "_supervisor",
78+
Hidden: true,
79+
Short: "运行守护进程监控器(内部命令)",
80+
Run: func(cmd *cobra.Command, args []string) {
81+
runSupervisor()
82+
},
83+
}
84+
}
85+
86+
// runSupervisor 运行守护进程监控器
87+
func runSupervisor() {
88+
execPath, err := os.Executable()
89+
if err != nil {
90+
fmt.Printf("获取可执行文件路径失败: %v\n", err)
91+
return
92+
}
93+
94+
supervisorLogFile, err := os.OpenFile(GetLogFile(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
95+
if err != nil {
96+
fmt.Printf("打开日志文件失败: %v\n", err)
97+
return
98+
}
99+
defer supervisorLogFile.Close()
100+
101+
logSupervisor := func(format string, args ...interface{}) {
102+
msg := fmt.Sprintf("[Supervisor %s] ", time.Now().Format("15:04:05"))
103+
msg += fmt.Sprintf(format, args...)
104+
msg += "\n"
105+
supervisorLogFile.WriteString(msg)
106+
supervisorLogFile.Sync()
107+
}
108+
109+
logSupervisor("已启动")
110+
111+
restartDelay := 1 * time.Second
112+
maxRestartDelay := 30 * time.Second
113+
consecutiveFailures := 0
114+
115+
for {
116+
if shouldStopSupervisor() {
117+
logSupervisor("停止")
118+
return
119+
}
120+
121+
cmd := exec.Command(execPath, "start", "-c", ConfigFile)
122+
123+
logFile, err := os.OpenFile(GetLogFile(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
124+
if err != nil {
125+
time.Sleep(restartDelay)
126+
continue
127+
}
128+
129+
cmd.Stdout = logFile
130+
cmd.Stderr = logFile
131+
132+
startTime := time.Now()
133+
if err := cmd.Start(); err != nil {
134+
logFile.Close()
135+
consecutiveFailures++
136+
time.Sleep(restartDelay)
137+
continue
138+
}
139+
140+
pidFile := GetPIDFile()
141+
if err := os.WriteFile(pidFile, []byte(strconv.Itoa(cmd.Process.Pid)), 0644); err != nil {
142+
cmd.Process.Kill()
143+
logFile.Close()
144+
time.Sleep(restartDelay)
145+
continue
146+
}
147+
148+
err = cmd.Wait()
149+
logFile.Close()
150+
uptime := time.Since(startTime)
151+
152+
if err != nil {
153+
consecutiveFailures++
154+
logSupervisor("异常退出: %v", err)
155+
156+
if uptime < 10*time.Second {
157+
restartDelay = time.Duration(consecutiveFailures) * time.Second
158+
if restartDelay > maxRestartDelay {
159+
restartDelay = maxRestartDelay
160+
}
161+
logSupervisor("等待 %v 后重启", restartDelay)
162+
time.Sleep(restartDelay)
163+
} else {
164+
consecutiveFailures = 0
165+
}
166+
} else {
167+
// 检查更新标记(程序同级目录)
168+
execDir := filepath.Dir(execPath)
169+
updateMarker := filepath.Join(execDir, ".cert-deploy-updated")
170+
if _, err := os.Stat(updateMarker); err == nil {
171+
logSupervisor("应用更新,重启中...")
172+
os.Remove(updateMarker)
173+
consecutiveFailures = 0
174+
time.Sleep(1 * time.Second)
175+
} else {
176+
logSupervisor("正常退出")
177+
os.Remove(pidFile)
178+
return
179+
}
180+
}
181+
}
182+
}
183+
184+
// shouldStopSupervisor 检查是否应该停止监控器
185+
func shouldStopSupervisor() bool {
186+
homeDir, _ := os.UserHomeDir()
187+
stopMarker := filepath.Join(homeDir, ".cert-deploy-stop")
188+
if _, err := os.Stat(stopMarker); err == nil {
189+
os.Remove(stopMarker)
190+
return true
191+
}
192+
return false
193+
}
194+
195+
// StopDaemon 停止守护进程
196+
func StopDaemon() error {
197+
homeDir, _ := os.UserHomeDir()
198+
stopMarker := filepath.Join(homeDir, ".cert-deploy-stop")
199+
os.WriteFile(stopMarker, []byte("stop"), 0644)
200+
201+
pidFile := GetPIDFile()
202+
data, err := os.ReadFile(pidFile)
203+
if err != nil {
204+
return fmt.Errorf("读取PID文件失败: %w", err)
205+
}
206+
207+
pidStr := strings.TrimSpace(string(data))
208+
pid, err := strconv.Atoi(pidStr)
209+
if err != nil {
210+
return fmt.Errorf("无效的PID: %w", err)
211+
}
212+
213+
process, err := os.FindProcess(pid)
214+
if err != nil {
215+
return fmt.Errorf("查找进程失败: %w", err)
216+
}
217+
218+
if err := process.Signal(syscall.SIGTERM); err != nil {
219+
return fmt.Errorf("发送停止信号失败: %w", err)
220+
}
221+
222+
for i := 0; i < 10; i++ {
223+
if !IsRunning() {
224+
break
225+
}
226+
time.Sleep(1 * time.Second)
227+
}
228+
229+
if IsRunning() {
230+
if err := process.Signal(syscall.SIGKILL); err != nil {
231+
return fmt.Errorf("强制停止失败: %w", err)
232+
}
233+
}
234+
235+
os.Remove(pidFile)
236+
os.Remove(stopMarker)
237+
238+
return nil
239+
}

cmd/log.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
"time"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
// CreateLogCmd 创建日志查看命令
15+
func CreateLogCmd() *cobra.Command {
16+
var follow bool
17+
18+
cmd := &cobra.Command{
19+
Use: "log",
20+
Short: "查看守护进程日志",
21+
Long: "查看证书部署守护进程的日志输出",
22+
Run: func(cmd *cobra.Command, args []string) {
23+
logFile := GetLogFile()
24+
if _, err := os.Stat(logFile); os.IsNotExist(err) {
25+
fmt.Println("日志文件不存在")
26+
return
27+
}
28+
29+
if follow {
30+
followLogs(logFile)
31+
} else {
32+
content, err := os.ReadFile(logFile)
33+
if err != nil {
34+
fmt.Printf("读取日志失败: %v\n", err)
35+
return
36+
}
37+
fmt.Print(string(content))
38+
}
39+
},
40+
}
41+
42+
cmd.Flags().BoolVarP(&follow, "follow", "f", false, "实时跟踪日志")
43+
44+
return cmd
45+
}
46+
47+
// followLogs 实时跟踪日志文件
48+
func followLogs(logFile string) {
49+
content, err := os.ReadFile(logFile)
50+
if err == nil && len(content) > 0 {
51+
fmt.Print(string(content))
52+
}
53+
54+
sigChan := make(chan os.Signal, 1)
55+
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
56+
57+
file, err := os.Open(logFile)
58+
if err != nil {
59+
fmt.Printf("打开日志失败: %v\n", err)
60+
return
61+
}
62+
defer file.Close()
63+
64+
file.Seek(0, 2)
65+
66+
buffer := make([]byte, 1024)
67+
done := make(chan bool)
68+
69+
go func() {
70+
for {
71+
select {
72+
case <-done:
73+
return
74+
default:
75+
n, err := file.Read(buffer)
76+
if err != nil {
77+
if err == io.EOF {
78+
time.Sleep(100 * time.Millisecond)
79+
continue
80+
}
81+
done <- true
82+
return
83+
}
84+
if n > 0 {
85+
fmt.Print(string(buffer[:n]))
86+
}
87+
}
88+
}
89+
}()
90+
91+
<-sigChan
92+
done <- true
93+
}

cmd/restart.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"time"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// CreateRestartCmd 创建重启命令
13+
func CreateRestartCmd() *cobra.Command {
14+
return &cobra.Command{
15+
Use: "restart",
16+
Short: "重启守护进程",
17+
Long: "重启证书部署守护进程",
18+
Run: func(cmd *cobra.Command, args []string) {
19+
if IsRunning() {
20+
if err := StopDaemon(); err != nil {
21+
fmt.Printf("停止失败: %v\n", err)
22+
os.Exit(1)
23+
}
24+
time.Sleep(2 * time.Second)
25+
}
26+
27+
execPath, err := os.Executable()
28+
if err != nil {
29+
fmt.Printf("获取可执行文件路径失败: %v\n", err)
30+
os.Exit(1)
31+
}
32+
33+
supervisorCmd := exec.Command(execPath, "_supervisor", "-c", ConfigFile)
34+
if err := supervisorCmd.Start(); err != nil {
35+
fmt.Printf("启动失败: %v\n", err)
36+
os.Exit(1)
37+
}
38+
39+
time.Sleep(500 * time.Millisecond)
40+
41+
if !IsRunning() {
42+
fmt.Println("启动失败")
43+
os.Exit(1)
44+
}
45+
46+
fmt.Println("守护进程已重启")
47+
},
48+
}
49+
}

0 commit comments

Comments
 (0)