bash_go_service/backend-service/cmd/main.go

334 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}