334 lines
7.4 KiB
Go
334 lines
7.4 KiB
Go
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 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 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)
|
||
}
|