bash_go_service/backend-service/cmd/main.go

367 lines
8.6 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"
"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")
if err := viper.ReadInConfig(); err != nil {
logger.Error("Error initializing config: %v", err)
}
logger.UpdateLogLevel()
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)
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
}