550 lines
14 KiB
Go
550 lines
14 KiB
Go
package main
|
||
|
||
import (
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"syscall"
|
||
"time"
|
||
|
||
"github.com/gookit/color"
|
||
"gopkg.in/ini.v1"
|
||
)
|
||
|
||
const (
|
||
SECRET_KEY = ")VQbB(vpy=U(wcp)"
|
||
)
|
||
|
||
// GOOS=linux GOARCH=amd64 go build -o /data/devops/MergePet/tool/tool main.go
|
||
var help = `
|
||
Usage: app.ini [options]
|
||
|
||
start start the server
|
||
stop stop the server
|
||
restart restart the server
|
||
status get the server status
|
||
install install the server
|
||
uninstall uninstall the server
|
||
kill kill the server
|
||
`
|
||
var cfg *ini.File
|
||
var FuncMap map[string]func() error
|
||
var dirPath string
|
||
var app_path string
|
||
var (
|
||
infoColor = color.New(color.FgGreen)
|
||
warn = color.New(color.FgYellow)
|
||
err = color.New(color.FgRed)
|
||
)
|
||
|
||
func info(format string, a ...interface{}) {
|
||
time := time.Now().Format("2006-01-02 15:04:05")
|
||
format = fmt.Sprintf("[%s] %s\n", time, format)
|
||
infoColor.Printf(format, a...)
|
||
}
|
||
|
||
func main() {
|
||
defer func() {
|
||
if err := recover(); err != nil {
|
||
info("%s", help)
|
||
}
|
||
}()
|
||
FuncMap = make(map[string]func() error)
|
||
// 检查是否提供了命令行参数
|
||
if len(os.Args) < 2 {
|
||
log.Fatal(help)
|
||
}
|
||
var err error
|
||
|
||
// 获取当前文件的绝对路径
|
||
execPath, err := os.Executable()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
absPath, err := filepath.Abs(execPath)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
// 获取当前文件的绝对路径的文件夹路径
|
||
dirPath = filepath.Dir(absPath)
|
||
|
||
// 加载 app.ini 文件
|
||
cfg, err = ini.Load(dirPath + "/app.ini")
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
app_path = cfg.Section("app").Key("app_path").String()
|
||
register("start", start)
|
||
register("stop", stop)
|
||
register("install", install)
|
||
register("status", status)
|
||
register("restart", restart)
|
||
register("uninstall", uninstall)
|
||
register("reload", reload)
|
||
register("kill", kill)
|
||
funcName := os.Args[1]
|
||
if f, ok := FuncMap[funcName]; ok {
|
||
e := f()
|
||
if e != nil {
|
||
log.Fatal(e)
|
||
}
|
||
} else {
|
||
log.Fatal(help)
|
||
}
|
||
}
|
||
|
||
func register(name string, f func() error) {
|
||
FuncMap[name] = f
|
||
}
|
||
|
||
func kill() error {
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要停止的服务类型和区号 kill [center|node] 区号")
|
||
}
|
||
NodeType := os.Args[2]
|
||
Zone := os.Args[3]
|
||
// 示例命令
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone)
|
||
// 示例进程名称
|
||
|
||
// 获取进程号
|
||
pid, err := getPidByArgs(processName)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 使用命令行命令 kill -9 杀死进程
|
||
cmd := exec.Command("kill", "-9", strconv.Itoa(pid))
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stderr
|
||
err = cmd.Run()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
info("server %s_%s 已被 kill -9 杀死, 进程号:%d", NodeType, Zone, pid)
|
||
return nil
|
||
}
|
||
|
||
func install() error {
|
||
// log.Println("install")
|
||
if len(os.Args) < 4 {
|
||
err.Println("请输入要安装的服务类型和区号 install [center|node] 区号")
|
||
}
|
||
serverType := os.Args[2]
|
||
zone := os.Args[3]
|
||
if serverType != "center" && serverType != "node" {
|
||
err.Println("请输入正确的服务类型 center|node")
|
||
}
|
||
AppName := cfg.Section("app").Key("app_name").String()
|
||
// 生成服务名
|
||
serviceName := fmt.Sprintf("%s_%s_%s", AppName, serverType, zone)
|
||
// 判断文件夹是否存在
|
||
folderPath := fmt.Sprintf("%s/zone/%s", app_path, serviceName)
|
||
if _, err := os.Stat(folderPath); os.IsNotExist(err) {
|
||
err := os.MkdirAll(folderPath, os.ModePerm)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
info("文件夹 %s 创建成功\n", folderPath)
|
||
} else {
|
||
info("文件夹 %s 已存在\n", folderPath)
|
||
}
|
||
|
||
// 生成配置文件
|
||
configPath := fmt.Sprintf("%s/server.json", folderPath)
|
||
file, err := os.Create(configPath)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer file.Close()
|
||
zoneId, _ := strconv.Atoi(zone)
|
||
cf, confs := createConfigFile(zoneId, serverType)
|
||
file.WriteString(confs)
|
||
|
||
// 读取 SQL 文件内容并替换 %database% 字符串
|
||
sqlFilePath := fmt.Sprintf("%s/tool/Merge_Pet.sql", app_path)
|
||
sqlContent, err := os.ReadFile(sqlFilePath)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
dbName := cf["DbName"].(string)
|
||
modifiedSQL := strings.ReplaceAll(string(sqlContent), "%database%", dbName)
|
||
|
||
// 将修改后的内容写入临时文件
|
||
tempSQLFile, err := os.CreateTemp("", "modified_*.sql")
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
_, err = tempSQLFile.WriteString(modifiedSQL)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
tempSQLFile.Close()
|
||
|
||
// 创建数据库
|
||
dbUser := cfg.Section("mysql").Key("mysql_user").String()
|
||
dbPassword, _ := Decrypt(cfg.Section("mysql").Key("mysql_password").String())
|
||
dbHost := cfg.Section("mysql").Key("mysql_host").String()
|
||
dbPort := cfg.Section("mysql").Key("mysql_port").String()
|
||
// log.Println("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", dbName))
|
||
createDBCmd := exec.Command("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", dbName))
|
||
createDBCmd.Stdout = os.Stdout
|
||
createDBCmd.Stderr = os.Stderr
|
||
|
||
err = createDBCmd.Run()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
// log.Println("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, dbName, "-e", fmt.Sprintf("source %s", tempSQLFile.Name()))
|
||
// 执行修改后的 SQL 文件
|
||
cmd := exec.Command("mysql", "-u"+dbUser, "-p"+dbPassword, "-h"+dbHost, "-P"+dbPort, dbName, "-e", fmt.Sprintf("source %s", tempSQLFile.Name()))
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stderr
|
||
|
||
err = cmd.Run()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
cmd = exec.Command("mkdir", cf["LogPath"].(string))
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stderr
|
||
err = cmd.Run()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
info("SQL 文件 %s 执行成功\n", sqlFilePath)
|
||
info("配置文件 %s 创建成功\n", configPath)
|
||
return nil
|
||
}
|
||
|
||
func uninstall() error {
|
||
stop()
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要卸载的服务类型和区号 uninstall [center|node] 区号")
|
||
}
|
||
serverType := os.Args[2]
|
||
zone := os.Args[3]
|
||
if serverType != "center" && serverType != "node" {
|
||
log.Fatal("请输入正确的服务类型 center|node")
|
||
}
|
||
AppName := cfg.Section("app").Key("app_name").String()
|
||
// 生成服务名
|
||
serviceName := fmt.Sprintf("%s_%s_%s", AppName, serverType, zone)
|
||
// 判断文件夹是否存在
|
||
folderPath := fmt.Sprintf("%s/zone/%s", app_path, serviceName)
|
||
if _, err := os.Stat(folderPath); os.IsNotExist(err) {
|
||
info("文件夹 %s 不存在\n", folderPath)
|
||
return nil
|
||
} else {
|
||
info("文件夹 %s 存在\n", folderPath)
|
||
}
|
||
// 删除文件夹
|
||
err := os.RemoveAll(folderPath)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
info("文件夹 %s 删除成功\n", folderPath)
|
||
return nil
|
||
}
|
||
|
||
func createConfigFile(Id int, Type string) (map[string]interface{}, string) {
|
||
conf := make(map[string]interface{})
|
||
conf["LogLevel"] = cfg.Section("log").Key("log_level").String()
|
||
|
||
TcpStartAddr, _ := cfg.Section("server").Key("tcp_addr").Int()
|
||
conf["TcpAddr"] = fmt.Sprintf(":%d", TcpStartAddr+Id)
|
||
|
||
WsStartAddr, _ := cfg.Section("server").Key("ws_addr").Int()
|
||
conf["WsAddr"] = fmt.Sprintf(":%d", WsStartAddr+Id)
|
||
|
||
ListenAddr, _ := cfg.Section("server").Key("listen_addr").Int()
|
||
|
||
conf["MySqlAddr"] = cfg.Section("mysql").Key("mysql_host").String()
|
||
conf["MySqlUsr"] = cfg.Section("mysql").Key("mysql_user").String()
|
||
conf["MySqlPwd"] = cfg.Section("mysql").Key("mysql_password").String()
|
||
conf["MySqlPort"] = cfg.Section("mysql").Key("mysql_port").String()
|
||
MaxConnNum, _ := cfg.Section("server").Key("max_conn").Int()
|
||
conf["MaxConnNum"] = MaxConnNum
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
conf["LogPath"] = fmt.Sprintf("%s/zone/%s_%s_%d/log", app_path, app_name, Type, Id)
|
||
conf["DbName"] = fmt.Sprintf("%s_%d", app_name, Id)
|
||
|
||
conf["TELOGDIR"] = cfg.Section("log").Key("te_log_path").String()
|
||
|
||
conf["GameName"] = app_name
|
||
AppId, _ := cfg.Section("app").Key("app_id").Int()
|
||
conf["AppID"] = AppId
|
||
conf["AppPath"] = app_path
|
||
conf["ServerType"] = Type
|
||
conf["ServerID"] = Id
|
||
|
||
conf["ServerOpenTime"] = "2028-01-01 00:00:00"
|
||
conf["ServerName"] = fmt.Sprintf("%s_%d", app_name, Id)
|
||
conf["ServerStatus"] = 1
|
||
CenterId, _ := cfg.Section("app").Key("center_id").Int()
|
||
conf["ServerCenter"] = CenterId
|
||
|
||
conf["RedisAddr"] = cfg.Section("redis").Key("redis_host").String()
|
||
conf["RedisPort"] = cfg.Section("redis").Key("redis_port").String()
|
||
conf["GameConfPath"] = app_path + "/config/"
|
||
conf["RemoteAddr"] = fmt.Sprintf("%s:%d", cfg.Section("server").Key("remote").String(), ListenAddr+Id) // 服务器地址
|
||
conf["ListenAddr"] = fmt.Sprintf(":%d", ListenAddr+Id)
|
||
conf["CenterAddr"] = fmt.Sprintf("%s:%s", cfg.Section("cluster").Key("center_host").String(), cfg.Section("cluster").Key("center_port").String()) // 服务器地址
|
||
b, _ := json.MarshalIndent(conf, "", " ")
|
||
return conf, string(b)
|
||
}
|
||
|
||
func start() error {
|
||
// log.Println("start")
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要启动的服务类型和区号 start [center|node] 区号")
|
||
}
|
||
err := status()
|
||
if err == nil {
|
||
return err
|
||
}
|
||
|
||
info("正在启动服务:server %s_%s ...", os.Args[2], os.Args[3])
|
||
NodeType := os.Args[2]
|
||
Zone := os.Args[3]
|
||
// 示例命令
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
|
||
cmdName := app_path + "/main"
|
||
cmdArgs := []string{fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone)}
|
||
|
||
// 创建命令
|
||
cmd := exec.Command(cmdName, cmdArgs...)
|
||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||
// 创建管道
|
||
stdin, err := cmd.StdinPipe()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
cmd.Env = os.Environ()
|
||
// 打开输出文件
|
||
outfile, err := os.OpenFile(fmt.Sprintf("%s/zone/%s_%s_%s/output.log", app_path, app_name, NodeType, Zone), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer outfile.Close()
|
||
|
||
// 重定向标准输出和标准错误到文件
|
||
cmd.Stdout = outfile
|
||
cmd.Stderr = outfile
|
||
err = cmd.Start()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 打印进程号
|
||
info("server %s_%s已启动, 进程号:%d", os.Args[2], os.Args[3], cmd.Process.Pid)
|
||
|
||
// 发送命令到子进程
|
||
go func() {
|
||
time.Sleep(2 * time.Second) // 等待子进程启动
|
||
_, err := stdin.Write([]byte("your_command\n"))
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
stdin.Close()
|
||
}()
|
||
|
||
// 释放与子进程相关的资源
|
||
err = cmd.Process.Release()
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func reload() error {
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要重启的服务类型和区号 reload [center|node] 区号")
|
||
}
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
NodeType := os.Args[2]
|
||
Zone := os.Args[3]
|
||
args := fmt.Sprintf("%s_%s_%s", app_name, NodeType, Zone)
|
||
|
||
pid, err := getPidByArgs(args)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 向进程发送SIGTERM信号
|
||
process, err := os.FindProcess(pid)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
err = process.Signal(syscall.SIGINT)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
fmt.Printf("server %s_%s 已发送SIGINT信号, 进程号: %d\n", NodeType, Zone, pid)
|
||
return nil
|
||
}
|
||
|
||
func stop() error {
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要停止的服务类型和区号 stop [center|node] 区号")
|
||
}
|
||
err := statusInfo()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
info("正在关闭服务:server %s_%s ...", os.Args[2], os.Args[3])
|
||
NodeType := os.Args[2]
|
||
Zone := os.Args[3]
|
||
// 示例命令
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone)
|
||
// 示例进程名称
|
||
|
||
// 获取进程号
|
||
pid, err := getPidByArgs(processName)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 查找进程
|
||
process, err := os.FindProcess(pid)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
// 关闭进程
|
||
err = process.Signal(syscall.SIGTERM)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
|
||
info("server %s_%s 已关闭,进程号:%d", NodeType, Zone, pid)
|
||
return nil
|
||
}
|
||
|
||
func getPidByArgs(args string) (int, error) {
|
||
cmd := exec.Command("pgrep", "-f", args)
|
||
output, err := cmd.Output()
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
// 解析输出,获取第一个匹配的进程号
|
||
outputStr := strings.TrimSpace(string(output))
|
||
pidStr := strings.Split(outputStr, "\n")[0]
|
||
pid, err := strconv.Atoi(pidStr)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
return pid, nil
|
||
}
|
||
|
||
func status() error {
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要查询的服务类型和区号 status [center|node] 区号")
|
||
}
|
||
|
||
NodeType := os.Args[2]
|
||
Zone := os.Args[3]
|
||
// 示例命令
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone)
|
||
// 示例进程名称
|
||
|
||
// 获取进程号
|
||
pid, err := getPidByArgs(processName)
|
||
if err != nil {
|
||
return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone)
|
||
}
|
||
|
||
// 查找进程
|
||
_, err = os.FindProcess(pid)
|
||
if err != nil {
|
||
return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone)
|
||
}
|
||
|
||
info("节点 %s_%s 启动中, 进程号 %d\n", NodeType, Zone, pid)
|
||
return nil
|
||
}
|
||
|
||
func statusInfo() error {
|
||
NodeType := os.Args[2]
|
||
Zone := os.Args[3]
|
||
// 示例命令
|
||
app_name := cfg.Section("app").Key("app_name").String()
|
||
processName := fmt.Sprintf("%s/zone/%s_%s_%s/server.json", app_path, app_name, NodeType, Zone)
|
||
// 示例进程名称
|
||
|
||
// 获取进程号
|
||
pid, err := getPidByArgs(processName)
|
||
if err != nil {
|
||
return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone)
|
||
}
|
||
|
||
// 查找进程
|
||
_, err = os.FindProcess(pid)
|
||
if err != nil {
|
||
return fmt.Errorf("进程 %s_%s 未启动", NodeType, Zone)
|
||
}
|
||
|
||
// info.Println("节点 %s_%s 启动中, 进程号 %d\n", NodeType, Zone, pid)
|
||
return nil
|
||
}
|
||
|
||
func restart() error {
|
||
if len(os.Args) < 4 {
|
||
log.Fatal("请输入要重启的服务类型和区号 restart [center|node] 区号")
|
||
}
|
||
err := stop()
|
||
if err != nil {
|
||
log.Println(err)
|
||
}
|
||
for statusInfo() == nil {
|
||
time.Sleep(1 * time.Second)
|
||
}
|
||
err = start()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 解密字符串
|
||
func Decrypt(cipherText string) (string, error) {
|
||
cipherTextBytes, err := base64.URLEncoding.DecodeString(cipherText)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
block, err := aes.NewCipher([]byte(SECRET_KEY))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if len(cipherTextBytes) < aes.BlockSize {
|
||
return "", fmt.Errorf("cipherText too short")
|
||
}
|
||
|
||
iv := cipherTextBytes[:aes.BlockSize]
|
||
cipherTextBytes = cipherTextBytes[aes.BlockSize:]
|
||
|
||
stream := cipher.NewCFBDecrypter(block, iv)
|
||
stream.XORKeyStream(cipherTextBytes, cipherTextBytes)
|
||
|
||
return string(cipherTextBytes), nil
|
||
}
|