脚本优化

This commit is contained in:
hahwu 2025-07-15 17:14:07 +08:00
parent 955b80919b
commit a90b658798
2 changed files with 586 additions and 0 deletions

36
tools/app.ini Normal file
View File

@ -0,0 +1,36 @@
[app]
app_name = pet_home
app_id = 1001
app_path = /data/devops/Goleaf
conf_path = /etc/pet_home
country_code = 004
[server]
tcp_addr = 3600
ws_addr = 3700
listen_addr = 3800
max_conn = 1000
remote = pethome.bywaystudios.com
[mysql]
mysql_user = root
mysql_password = root
mysql_host = 172.20.0.5
mysql_port = 3306
[redis]
redis_host = 172.20.0.6
redis_port = 6379
[cluster]
center_id = 0
center_host = pethome.bywaystudios.com
center_port = 3800
[log]
log_level = debug
te_log_path = /var/log/pet_home/te
[kafka]
kafka_host = kafka-server
kafka_port = 9092

550
tools/main.go Normal file
View File

@ -0,0 +1,550 @@
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["KafkaHost"] = cfg.Section("kafka").Key("kafka_host").String()
conf["KafkaPort"] = cfg.Section("kafka").Key("kafka_port").String
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["CountryCode"] = cfg.Section("app").Key("country_code").String()
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
}