package main import ( "fmt" "os" "os/exec" "os/signal" "path/filepath" "strconv" "strings" "syscall" "time" client "backend-service/pkg/api" "backend-service/pkg/machine" "backend-service/pkg/service" "bash_go_service/shared/pkg/constants" "bash_go_service/shared/pkg/logger" "github.com/spf13/viper" "golang.org/x/sys/unix" ) // isDaemon 检查是否以守护进程模式运行 func isDaemon() bool { return len(os.Args) > 1 && os.Args[1] == constants.DaemonFlag } func isForce() bool { return len(os.Args) > 1 && os.Args[1] == constants.ForceFlag } func isDebug() bool { for _, arg := range os.Args[1:] { if arg == constants.DebugFlag { return true } } return false } func isQuit() bool { for _, arg := range os.Args[1:] { if arg == constants.QuitFlag { return true } } return false } func init() { // 初始化配置 if err := initConfig(); err != nil { logger.Error("Error initializing config: %v", err) return } logger.UpdateLogLevel() logger.Debug("Initializing...") if isDaemon() { logger.Debug("Starting daemon...") err := checkExistingProcess() if err != nil { logger.Error("Error starting daemon.") return } pid := os.Getpid() // 写入 PID 文件 pidStr := []byte(fmt.Sprintf("%d", pid)) if err := os.WriteFile(constants.PidFilePath, pidStr, constants.LogFileMode); err != nil { logger.Error("Failed to write PID file: %v", err) os.Exit(1) } runDaemon() } } func forceStopDaemon() error { // 读取 PID 文件 pidBytes, err := os.ReadFile(constants.PidFilePath) if err != nil { if os.IsNotExist(err) { // PID 文件不存在,说明没有运行中的守护进程 return nil } return fmt.Errorf("read pid file: %w", err) } // 解析 PID pid, err := strconv.Atoi(strings.TrimSpace(string(pidBytes))) if err != nil { // PID 文件损坏,删除它 os.Remove(constants.PidFilePath) return nil } // 查找进程 process, err := os.FindProcess(pid) if err != nil { // 进程不存在,清理 PID 文件 os.Remove(constants.PidFilePath) return nil } // 尝试发送 SIGTERM 信号 if err := process.Signal(syscall.SIGTERM); err != nil { if err == unix.ESRCH { // 进程不存在,清理 PID 文件 os.Remove(constants.PidFilePath) return nil } // 如果 SIGTERM 失败,尝试 SIGKILL if err := process.Kill(); err != nil { return fmt.Errorf("kill process: %w", err) } } // 等待进程退出 for i := 0; i < 10; i++ { if err := unix.Kill(pid, 0); err != nil { if err == unix.ESRCH { // 进程已经退出 os.Remove(constants.PidFilePath) return nil } } time.Sleep(100 * time.Millisecond) } // 如果进程仍然存在,返回错误 return fmt.Errorf("failed to stop daemon process") } func main() { if isDaemon() { return } // 如果是force,先强制结束先前的后台进程 if isForce() { logger.Debug("Force start...") // 强制结束先前的后台进程 if err := forceStopDaemon(); err != nil { logger.Error("Error stopping daemon.") return } if isQuit() { logger.Debug("Force quit...") return } } if err := run(); err != nil { logger.Error("Application failed: %v", err) os.Exit(1) } } // run 程序主逻辑 func run() error { // 采集并发送机器信息 info, err := collectAndSendMachineInfo() if err != nil { return fmt.Errorf("collect and send machine info: %w", err) } // 显示欢迎信息 showWelcomeMessage(info) // 检查并启动守护进程 if err := handleDaemonProcess(); err != nil { logger.Debug("Handle daemon process failed: %s", err) } return nil } // initConfig 初始化配置 func initConfig() error { var configDir string // 检查是否包含 --debug 参数 if isDebug() { // 调试模式:使用常量路径 configDir = constants.ConfigPath } else { // 非调试模式:使用可执行文件路径拼接 exePath, err := os.Executable() if err != nil { return err } exeDir := filepath.Dir(exePath) configDir = filepath.Join(exeDir, constants.ConfigPath) } viper.AddConfigPath(configDir) viper.SetConfigName("config") viper.SetConfigType("yaml") return viper.ReadInConfig() } // collectAndSendMachineInfo 采集并发送机器信息 func collectAndSendMachineInfo() (*client.Result, error) { info := machine.CollectMachineInfo() result, err := client.SendMachineInfo(info) if err != nil { return nil, fmt.Errorf("send machine info: %w", err) } return &result, nil } // handleDaemonProcess 处理守护进程相关逻辑 func handleDaemonProcess() error { // 获取可执行文件路径 exePath, err := os.Executable() if err != nil { return fmt.Errorf("get executable path: %w", err) } logger.Debug("Exe path: %s", filepath.Dir(exePath)) // 检查现有进程 if err := checkExistingProcess(); err != nil { return err } // 启动新的守护进程 return startDaemonProcess() } // checkExistingProcess 检查是否已有守护进程在运行 func checkExistingProcess() error { pidFile := constants.PidFilePath pidBytes, err := os.ReadFile(pidFile) if err != nil { logger.Debug("Pid file not exist.") // 如果文件不存在,直接返回,不做任何处理 if os.IsNotExist(err) { return nil } return fmt.Errorf("read pid file: %w", err) } pid, err := strconv.Atoi(strings.TrimSpace(string(pidBytes))) if err != nil { logger.Debug("Pid file is broken.") // PID 文件损坏,直接删除 os.Remove(pidFile) return nil } process, err := os.FindProcess(pid) if err != nil { logger.Debug("Process not found.") // 进程不存在,删除 PID 文件 os.Remove(pidFile) return nil } else { logger.Debug("Process found, pid is: %d", process.Pid) } // 更可靠的进程检测方式 if err := unix.Kill(pid, 0); err != nil { logger.Debug("Kill process error: %v", err) if err == unix.ESRCH { logger.Debug("Process not exist.") // 进程不存在,删除 PID 文件 os.Remove(pidFile) return nil } } // 进程确实存在 logger.Info("Daemon already running with PID: %d", pid) return fmt.Errorf("daemon already running") } func formatGoTime(format string) string { replacer := strings.NewReplacer( "%Y", "2006", "%m", "01", "%d", "02", "%H", "15", "%M", "04", "%S", "05", ) return replacer.Replace(format) } // startDaemonProcess 启动守护进程 func startDaemonProcess() error { // 确保日志目录存在 if err := os.MkdirAll(constants.LogFilePath, constants.LogFileMode); err != nil { return fmt.Errorf("create logs directory: %w", err) } // 将 strftime 风格格式转换为 Go 的时间格式 goTimeFormat := formatGoTime(constants.LogNameFormate) logFileName := time.Now().Format(goTimeFormat) logFilePath := filepath.Join(constants.LogFilePath, logFileName) logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, constants.LogFileMode) if err != nil { return fmt.Errorf("open log file: %w", err) } cmd := exec.Command(os.Args[0], constants.DaemonFlag) cmd.Stdout = logFile cmd.Stderr = logFile cmd.SysProcAttr = &syscall.SysProcAttr{ Setsid: true, } if err := cmd.Start(); err != nil { return fmt.Errorf("start daemon: %w", err) } return nil } // runDaemon 守护进程主逻辑 func runDaemon() { logger.Info("Daemon started with PID: %d", os.Getpid()) // 设置信号处理 sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) go func() { sig := <-sigChan logger.Info("Received signal: %v", sig) // 清理 PID 文件 os.Remove(constants.PidFilePath) os.Exit(0) }() service.Run() } // showWelcomeMessage 显示欢迎信息 func showWelcomeMessage(info *client.Result) { fmt.Println(info.Msg) }