package main import ( "fmt" "os" "os/exec" "os/signal" "path/filepath" "strconv" "strings" "syscall" "time" "version/pkg/handler" client "backend-service/pkg/api" "backend-service/pkg/machine" "backend-service/pkg/service" "backend-service/pkg/version" "bash_go_service/shared/pkg/constants" "bash_go_service/shared/pkg/logger" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/sys/unix" ) var rootCmd = &cobra.Command{ Use: "bash_service", Short: "北溟 BASH 后台服务", Long: `欢迎使用北溟云计算服务,这是BASH增强服务,如出现问题请联系管理员`, Run: func(cmd *cobra.Command, args []string) { if err := run(); err != nil { logger.Error("Application failed: %v", err) os.Exit(1) } }, } var daemonCmd = &cobra.Command{ Use: "daemon", Short: "Run the application as a daemon", Run: func(cmd *cobra.Command, args []string) { if err := checkExistingProcess(); err != nil { logger.Error("Error starting daemon.") return } pid := os.Getpid() 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() }, } var forceCmd = &cobra.Command{ Use: "force", Short: "Force stop the daemon and restart", Run: func(cmd *cobra.Command, args []string) { 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) } }, } var quitCmd = &cobra.Command{ Use: "quit", Short: "Force stop the daemon and quit", Run: func(cmd *cobra.Command, args []string) { logger.Debug("Force quit...") if err := forceStopDaemon(); err != nil { logger.Error("Error stopping daemon.") return } }, } var updateCmd = &cobra.Command{ Use: "update", Short: "Update the application", Run: func(cmd *cobra.Command, args []string) { logger.Debug("Update command executed.") need, err := version.CheckUpdate() if err != nil { logger.Error("Error checking update: %v", err) return } if need { logger.Info("Update needed. Restarting the application...") executable, err := os.Executable() if err != nil { logger.Error("Error getting executable path: %v", err) return } env := os.Environ() cmd := exec.Command(executable) cmd.Env = env err = cmd.Start() if err != nil { logger.Error("Error restarting the application: %v", err) return } logger.Info("Application restarted successfully.") os.Exit(0) } }, } func init() { cobra.OnInitialize(initConfig, checkAndUpdate) rootCmd.AddCommand(daemonCmd) rootCmd.AddCommand(forceCmd) rootCmd.AddCommand(quitCmd) rootCmd.AddCommand(updateCmd) } func checkAndUpdate() { exePath, err := os.Executable() if err != nil { logger.Error("Error getting executable path: %v", err) return } info, err := handler.LoadUpdateInfo(exePath) if err != nil { logger.Debug("Error loading update info: %v", err) return } // 删除info文件,下次再试 err = handler.RemoveUpdateInfo(exePath) if err != nil { logger.Error("Error removing update info: %v", err) return } exeDir := filepath.Dir(exePath) // 先更新so文件 logger.Debug("Updating so file...") err = handler.ExecuteUpdate(exeDir, info.SoFileNewPath, info.SoFileOldPath, info.SoTargetPath, false) if err != nil { logger.Error("Error updating so file: %v", err) return } // 更新exe程序 logger.Debug("Updating exe file...") forceStopDaemon() err = handler.ExecuteUpdate(exeDir, info.ExeFileNewPath, info.ExeFileOldPath, info.ExeTargetPath, true) if err != nil { logger.Error("Error updating exe file: %v", err) return } } func main() { if err := rootCmd.Execute(); err != nil { logger.Error("Error executing command: %v", err) os.Exit(1) } } func initConfig() { var configDir string if isDebug() { configDir = constants.ConfigPath logger.Debug("Debug mode, using config path: %s", configDir) } else { exePath, err := os.Executable() if err != nil { logger.Error("Error getting executable path: %v", err) return } exeDir := filepath.Dir(exePath) configDir = filepath.Join(exeDir, constants.ConfigPath) } viper.AddConfigPath(configDir) viper.SetConfigName("config") viper.SetConfigType("yaml") logger.UpdateLogLevel() if err := viper.ReadInConfig(); err != nil { logger.Error("Error initializing config: %v", err) } logger.Debug("Initializing...") } 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 } func forceStopDaemon() error { pidBytes, err := os.ReadFile(constants.PidFilePath) if err != nil { 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 { os.Remove(constants.PidFilePath) return nil } process, err := os.FindProcess(pid) if err != nil { os.Remove(constants.PidFilePath) return nil } if err := process.Signal(syscall.SIGTERM); err != nil { if err == unix.ESRCH { os.Remove(constants.PidFilePath) return nil } 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 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 } 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() } 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.") os.Remove(pidFile) return nil } process, err := os.FindProcess(pid) if err != nil { logger.Debug("Process not found.") 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.") 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) } func startDaemonProcess() error { if err := os.MkdirAll(constants.LogFilePath, constants.LogFileMode); err != nil { return fmt.Errorf("create logs directory: %w", err) } goTimeFormat := formatGoTime(constants.LogNameFormate) logFileName := time.Now().Format(goTimeFormat) // logFilePath := filepath.Join(constants.LogFilePath, logFileName) var logFilePath string if isDebug() { logFilePath = filepath.Join(constants.LogFilePath, logFileName) } else { exePath, err := os.Executable() if err != nil { logger.Error("Error getting executable path: %v", err) return err } exeDir := filepath.Dir(exePath) logFilePath = filepath.Join(exeDir, 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 } func runDaemon() { // daemon的默认日志等级 logger.SetLevel(logger.DEBUG) 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) os.Remove(constants.PidFilePath) os.Exit(0) }() service.Run() } func showWelcomeMessage(info *client.Result) { fmt.Println(info.Msg) } func isDebug() bool { for _, arg := range os.Args[1:] { if arg == constants.DebugFlag { return true } } return false }