commit 95c96dce426eb0714b2e001bf967875795d00a8d Author: lwt <215586800@qq.com> Date: Fri May 22 00:16:08 2026 +0800 仓库初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0701c6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# 可选:若手动放置上一版 main_*.exe 于此目录,发版时可 --old-exe release_backup/main_x.y.z.exe 生成增量 +# release_backup/ + +.build/ + +# Python/runtime generated files +__pycache__/ +*.py[cod] +*.log + +# 本地打开项目时生成的 Server Manager 项目配置 +.server_manager/ diff --git a/MCP.md b/MCP.md new file mode 100644 index 0000000..c0fe71d --- /dev/null +++ b/MCP.md @@ -0,0 +1,57 @@ +# Server Manager MCP + +本项目提供一个 MCP stdio 服务,入口为新的 CLI 模块: + +```bash +python -m entrypoints.cli mcp +``` + +源码运行时需要让 Python 能找到 `src`: + +```bat +set PYTHONPATH=E:\learn\web\backend\storage\server_manager\src +python -m entrypoints.cli --root E:\game\server_root mcp +``` + +打包后可直接使用安装目录或 `src\dist\main\main.exe`: + +```bat +main.exe --root E:\game\server_root mcp +``` + +## 客户端配置示例 + +```json +{ + "mcpServers": { + "server-manager": { + "command": "python", + "args": [ + "-m", + "entrypoints.cli", + "--root", + "E:\\game\\server_root", + "mcp" + ], + "env": { + "PYTHONPATH": "E:\\learn\\web\\backend\\storage\\server_manager\\src" + } + } + } +} +``` + +## 暴露工具 + +- `server_manager_list_servers` +- `server_manager_check_status` +- `server_manager_read_config` +- `server_manager_create_server` +- `server_manager_regenerate_config` +- `server_manager_start_server` +- `server_manager_stop_server` +- `server_manager_compile` +- `server_manager_search_logs` +- `server_manager_read_log` + +注意:`create/start/stop/compile/regenerate` 会修改本地项目或启动外部进程,建议在 MCP 客户端里保留工具调用确认。 diff --git a/PROJECT_REPLICATION_PROMPT.md b/PROJECT_REPLICATION_PROMPT.md new file mode 100644 index 0000000..cfc9325 --- /dev/null +++ b/PROJECT_REPLICATION_PROMPT.md @@ -0,0 +1,2933 @@ +# Server Manager 项目总结与复刻提示词 + +本文档用于从零复刻当前 `server_manager` 项目。复刻目标是还原源码、配置模板、运行脚本、打包脚本、更新机制和主要交互行为;`src/dist/`、`output/*.exe` 等二进制构建产物不需要手写,应通过脚本重新生成。 + +## 当前项目总结 + +`Server Manager` 是一个跨平台的 Erlang 游戏服务器管理工具。 + +- Windows 侧提供 PyQt6 图形界面,支持打开服务器项目、维护项目配置、编译代码、创建服务器、管理本地/远程节点、查看日志和自动更新。 +- Linux/Windows 都提供命令行入口,支持 `list`、`create`、`start`、`stop`、`connect`、`compile`、`regen`、`interactive` 等命令。 +- 工具面向一个 Erlang/rebar3 游戏服务器工程,约定服务器工程根目录包含 `run/`、`config/`、`rebar3` 等内容。 +- 每个服务器实例位于 `<服务器项目根>/run/__s/`,实例差异配置写入 `config/kv.config`,启动前根据模板生成 `config/sys.config`。 +- 项目级工具配置位于 `<服务器项目根>/.server_manager/config/`,首次打开项目时从软件自带模板目录同步 `default.kv` 和 `sys_*.config.example`。 +- 应用级配置位于 `%LOCALAPPDATA%/ServerManager` 或 `~/.config/ServerManager`,用于保存最近项目列表和应用日志。 +- 打包使用 PyInstaller 目录模式生成 `src/dist/main/main.exe`,Inno Setup 生成 `output/ServerManager_Setup.exe`。 +- 实际 GUI/CLI 主入口是 `src/server_manager_gui.py`;根目录 `main.py` 是一个 Tkinter 自动更新集成示例/遗留入口。 +- 更新机制支持 `version.json` 清单、全量安装包更新,以及基于 `bsdiff4` 的 `main.exe` 增量补丁;替换 exe 时由独立 `mini_updater.exe` 等待主进程退出后完成替换并重启。 +- 日志查看支持单文件加载、玩家协议日志、日期归档日志、ripgrep 全局搜索、正文高亮、匹配行过滤和 less 风格快捷键。 + +## 核心文件树 + +```text +server_manager/ +├── README.md +├── RELEASE.md +├── PROJECT_REPLICATION_PROMPT.md +├── version.json +├── main.py +├── updater.py +├── mini_updater.py +├── release.py +├── release.bat +├── pack_all.bat +├── pack_local_test.py +├── pack_local_test.bat +├── build_installer.iss +├── rg.exe # 可选,ripgrep Windows 可执行文件 +├── output/ +│ ├── config/ +│ │ ├── default.kv +│ │ ├── sys_game.config.example +│ │ ├── sys_login.config.example +│ │ ├── sys_center.config.example +│ │ ├── sys_cross.config.example +│ │ └── sys_client.config.example +│ ├── patches/ +│ ├── version.json +│ └── ServerManager_Setup.exe # 构建产物 +├── output_local/ # 本地测试包产物,通常忽略 +└── src/ + ├── app_config.py + ├── server_manager_gui.py + ├── server_manager_cli.py + ├── server_commands.py + ├── server_creator.py + ├── log_viewer_tab.py + ├── log_viewer_pane.py + ├── rg_search.py + ├── updater.py + ├── config.json + ├── requirements.txt + ├── run.bat + ├── run.sh + ├── build.bat + ├── build_portable.bat + ├── build_linux.sh + └── icon/ + ├── icon.png + ├── icon.ico + └── convert_icon.py +``` + +## 完整复刻提示词 + +将下面提示词交给代码生成模型,即可要求其从零生成当前项目的等价实现。 + +```text +你是资深 Python 桌面工具工程师。请从零创建一个名为 Server Manager 的项目,目标是复刻一个用于 Erlang/rebar3 游戏服务器工程的跨平台管理工具。输出完整仓库源码、配置模板、运行脚本、打包脚本和文档。不要手工生成 exe、dist、installer 这类二进制产物;这些产物必须由脚本重新构建。 + +一、项目目标 + +1. 实现一个 Python 3.8+ 项目。 +2. Windows 下提供 PyQt6 GUI;Windows/Linux 下提供 CLI。 +3. GUI 无参数启动;命令行带任意参数时进入 CLI,行为与 src/server_manager_cli.py 一致。 +4. 工具管理一个外部 Erlang 游戏服务器项目,项目根目录通常含 run、config、rebar3、apps 等。 +5. 首次打开服务器项目时,将软件自带模板 output/config 下的 default.kv 与 sys_*.config.example 同步到 <项目根>/.server_manager/config/。 +6. 所有项目级配置保存在 <项目根>/.server_manager/config/tool.config;应用级配置和日志保存在 %LOCALAPPDATA%/ServerManager 或 ~/.config/ServerManager。 +7. 支持服务器创建、启动、快速启动、停止、remsh 连接、编译、配置编辑、sys.config 重新生成、本地/远程服务器列表、节点状态批量检查、日志查看、日志搜索、自动更新、打包发版。 + +二、技术栈与依赖 + +1. Python 3.8+。 +2. GUI: PyQt6。 +3. 打包: pyinstaller。 +4. 数据库访问: pymysql。 +5. 更新下载: requests,缺失时可回退 urllib。 +6. 增量更新: bsdiff4。 +7. 日志全局搜索: 优先使用 ripgrep,可执行文件名 rg.exe 或 rg;也支持 RG_PATH 环境变量。 +8. Windows 安装包: Inno Setup 6。 +9. 被管理服务器依赖 Erlang/OTP、rebar3;Linux 后台启动优先 screen,其次 tmux,最后后台进程。 + +三、仓库文件 + +请创建如下文件: + +根目录: +- README.md:写明项目介绍、安装、运行、配置、打包、version.json 更新说明、CLI 参数和注意事项。 +- RELEASE.md:写明本地测试包、正式发版、增量更新、ripgrep 放置方式。 +- version.json:版本清单,当前版本为 1.0.46,字段包含 version、update_url、release_notes、full_installer_url、delta_updates。 +- main.py:Tkinter 示例/遗留入口,LOCAL_VERSION = "1.0.46",演示 updater 自动更新集成;真实桌面工具入口仍以 src/server_manager_gui.py 为准。 +- updater.py:保留一个 Tkinter/独立入口可用的无感更新模块,兼容 delta 与 delta_updates。 +- mini_updater.py:独立进程外更新器,等待主进程 PID 退出后替换 main.exe 并重启。 +- release.py:发版脚本,负责更新 build_installer.iss、main.py、version.json,生成 delta_updates,打包后可生成 bsdiff4 patch 并填 new_exe_sha256。 +- release.bat:一键正式发版脚本,顺序为 PyInstaller -> release.py 版本预处理 -> Inno Setup -> release.py --post-build。 +- pack_all.bat:正式打包但不改版本号,生成 output/ServerManager_Setup.exe,并复制 version.json 到 output/。 +- pack_local_test.py 与 pack_local_test.bat:生成 output_local/ServerManager_Setup_LOCAL.exe,不改版本号、不覆盖 output/。 +- build_installer.iss:Inno Setup 脚本,正式版 AppName=ServerManager,测试版 AppName=ServerManager (本地测试),安装 src/dist/main/*、src/dist/mini_updater.exe、version.json、output/config/*。 +- .gitignore:忽略 output_local/,可注明 release_backup/ 可选忽略。 +- rg.exe:可选,不需要生成二进制;文档说明放置在根目录即可被打包复制。 + +src 目录: +- requirements.txt:包含 PyQt6>=6.4.0、pyinstaller>=6.0.0、pymysql>=1.0.0、requests>=2.28.0、bsdiff4>=1.2.0。 +- app_config.py:应用级配置目录、app_config.json、最近项目列表、应用日志目录。 +- server_manager_gui.py:PyQt6 主 GUI 和主入口;无参数 GUI,有参数转发 CLI。 +- server_manager_cli.py:命令行逻辑。 +- server_commands.py:服务器命令、配置模板同步、节点状态、启动/停止/remsh、sys.config 生成、远程服务器 RPC 查询、日志路径和 trace RPC。 +- server_creator.py:服务器创建、kv.config 生成、配置项定义、ID 范围、端口默认值。 +- log_viewer_tab.py:日志查看顶级页签,含“日志搜索”和“全局搜索”两个子页。 +- log_viewer_pane.py:可嵌入或独立窗口的日志正文检索组件。 +- rg_search.py:ripgrep 封装。 +- updater.py:GUI 使用的自动更新工具函数。 +- config.json:旧版/示例配置,可包含 workspace、database、server、erlang、commands、login_db。 +- run.bat 与 run.sh:源码/打包入口自动选择脚本。 +- build.bat、build_portable.bat、build_linux.sh:PyInstaller 打包脚本。 +- icon/convert_icon.py、icon/icon.png、icon/icon.ico:图标资源和转换脚本。 + +output/config 目录: +- default.kv:默认配置和配置项定义。 +- sys_game.config.example、sys_login.config.example、sys_center.config.example、sys_cross.config.example、sys_client.config.example:Erlang sys.config 模板,使用 ${key} 占位符。 + +四、version.json 内容 + +生成根目录 version.json,内容结构如下: + +{ + "version": "1.0.46", + "update_url": "http://172.18.180.94:3000/api/file?path=server_manager/output/version.json", + "release_notes": "优化ui", + "full_installer_url": "http://172.18.180.94:3000/api/download?path=server_manager/output/ServerManager_Setup.exe", + "delta_updates": { + "1.0.45": { + "patch_url": "http://172.18.180.94:3000/api/download?path=server_manager/output/patches/v1.0.45_to_v1.0.46.patch", + "new_exe_sha256": "" + } + } +} + +五、配置体系 + +1. 项目级配置目录固定为 <项目根>/.server_manager/config。 +2. 软件自带模板目录: + - 打包后:main.exe 同级的 config/。 + - 源码运行:仓库根目录 output/config/。 +3. 受管模板文件: + - default.kv + - sys_center.config.example + - sys_client.config.example + - sys_cross.config.example + - sys_game.config.example + - sys_login.config.example +4. 打开项目时必须确保 .server_manager/config 存在并同步上述模板;保留用户自己的 tool.config。 +5. 配置合并优先级从低到高: + - .server_manager/config/default.kv + - .server_manager/config/tool.config + - run_dir/config/tool.config 或 run_dir/tool.config + - run_dir//config/kv.config +6. 合并配置后强制用当前机器 IP 覆盖 ip 和 game_host。 +7. tool.config 是 key=value 文件,至少写入: + - server_root、run_dir + - r25_path + - db_host、db_port、db_user、db_pass + - login_db_name、login_db_server_id、login_db_host、login_db_port、login_db_user、login_db_pass + - prefix、default_server_id、server_type、server_name、login_node、center_node、cookie +8. 应用级配置: + - Windows: %LOCALAPPDATA%/ServerManager/app_config.json + - Linux/macOS: ~/.config/ServerManager/app_config.json + - 保存最近 10 个有效项目,判断有效项目时需存在 .server_manager/config/default.kv 或旧式 config/default.kv。 + - 应用日志写到同目录 logs/,日志文件名 server_manager_YYYYMMDD.log 或 server_manager_cli_YYYYMMDD.log,保留最近 7 天。 + +六、default.kv 要求 + +default.kv 必须含以下默认值和配置定义: + +- prefix=ddxq2 +- server_id=1 +- server_type=game_server +- server_name=自己名字_1 +- ip= +- game_host= +- tcp_port=18001 +- http_port=19001 +- login_node=ddxq2_login_server_s900@172.16.14.122 +- center_node=ddxq2_center_server_s1@172.16.14.122 +- db_host=172.16.12.220 +- db_port=3306 +- db_user=ddxqadmin +- db_pass=ddxq@44168deabe63 +- db_save_time=300000 +- db_save_count=10000 +- log_save_time=120000 +- log_save_count=1000 +- log_dir=run/server/log +- role_log=run/server/log +- logger_level=debug +- open_time={{2025,1,1},{10,0,0}} +- merge_server_ids=[] +- merge_server_time={{1970,1,1},{0,0,0}} +- last_merge_server_ids=[] +- game_tcp_port=18001 +- login_host=172.16.14.122 +- login_http_port=19900 +- is_develop=true +- is_inner=true +- auto_reload=true +- gm_auth=true +- risk_control_server_ip=msg.4399sy.com +- risk_control_server_post=5978 +- game_id_min=100, game_id_max=9999, game_id_default=100 +- login_id_min=11, login_id_max=99, login_id_default=10 +- center_id_min=1, center_id_max=10, center_id_default=1 +- cross_id_min=10000, cross_id_max=20000, cross_id_default=10000 +- client_id_min=1, client_id_max=10000, client_id_default=1 +- login_db_name= +- login_db_server_id=900 +- login_db_host=172.16.12.220 +- login_db_port=3306 +- login_db_user=ddxqadmin +- login_db_pass=ddxq@44168deabe63 +- cookie=ddxq2-node + +必须配置项定义: +- required_keys_base=prefix,server_id,server_type,ip,db_host,db_user,db_pass,db_port,log_dir,db_game_name,auto_reload +- required_keys_game=server_name,game_host,tcp_port,http_port,open_time,login_node,center_node,db_log_name +- required_keys_login=tcp_port +- required_keys_center=open_time,login_node,db_log_name +- required_keys_cross=open_time,center_node,db_log_name +- required_keys_client=tcp_port,game_host,login_host,login_http_port,login_node + +配置项定义使用 config_keys_base_*、config_keys_game_*、config_keys_login_*、config_keys_center_*、config_keys_cross_*、config_keys_client_*;编辑对话框可选项使用 editable_config_*。至少包含当前项目中的这些 key:prefix、server_id、server_type、ip、db_host、db_port、db_user、db_pass、db_save_time、db_save_count、log_save_time、log_save_count、log_dir、logger_level、is_develop、is_inner、auto_reload、gm_auth、server_name、game_host、tcp_port、http_port、login_node、center_node、open_time、merge_server_ids、merge_server_time、last_merge_server_ids、risk_control_server_ip、risk_control_server_post、role_log、game_tcp_port、login_host、login_http_port。 + +七、sys.config 模板要求 + +1. 每种服务器类型都有一个 sys_*.config.example。 +2. 模板中使用 ${key} 占位符,至少覆盖: + ${auto_reload}, ${center_node}, ${db_game_name}, ${db_host}, ${db_log_name}, ${db_pass}, ${db_port}, ${db_save_count}, ${db_save_time}, ${db_user}, ${game_host}, ${game_tcp_port}, ${gm_auth}, ${http_port}, ${is_develop}, ${is_inner}, ${log_dir}, ${log_save_count}, ${log_save_time}, ${logger_level}, ${login_host}, ${login_http_port}, ${login_node}, ${merge_server_ids}, ${open_time}, ${prefix}, ${risk_control_server_ip}, ${risk_control_server_post}, ${role_log}, ${server_id}, ${server_name}, ${server_type}, ${tcp_port}。 +3. 生成 sys.config 时必须根据 server_type 选择模板: + - game_server -> sys_game.config.example + - login_server -> sys_login.config.example + - client_server -> sys_client.config.example + - center_server -> sys_center.config.example + - cross_server -> sys_cross.config.example +4. 生成 sys.config 前需要将 log_dir 和 role_log 归一化为 run//。如果 default.kv 中是 run/server/log,则实际应变为 run//log。 +5. 若模板中仍有未替换占位符,要输出警告但不要崩溃。 + +八、服务器创建逻辑 + +1. 支持服务器类型: + - game -> game_server,显示名“游戏服” + - login -> login_server,显示名“登录服” + - client -> client_server,显示名“客户端测试服” + - center -> center_server,显示名“中心服” + - cross -> cross_server,显示名“跨服” +2. 目录名格式:__s,例如 ddxq2_game_s100。 +3. 服务器目录://config/。 +4. 差异配置文件:kv.config,只写入当前实例的差异配置。 +5. 创建后立即调用 generate_start_config 生成 sys.config。 +6. 默认端口: + - tcp_port = 18000 + server_id + - http_port = 19000 + server_id +7. 默认游戏服显示名:_,GUI 中可由 server_name 前缀 + ID 动态生成。 +8. 数据库名: + - db_game_name=__s + - db_log_name=__log_s,登录服不写 db_log_name +9. game 额外写入 server_name、game_host、tcp_port、http_port、open_time、role_log。 +10. login 写入 tcp_port。 +11. center 写入 open_time、login_node。 +12. cross 写入 open_time、center_node。 +13. client 写入 tcp_port、game_host、game_tcp_port、login_host、login_http_port、login_node。 +14. auto_reload 作为布尔复选项,写成 true/false。 +15. GUI 创建前检查同类型同 ID 是否已存在;已存在时警告,覆盖只在二次确认后允许。 + +九、命令构建与服务器运行 + +1. 平台检测: + - Windows: REBAR3_CMD = rebar3.cmd + - Linux: REBAR3_CMD = ./rebar3 +2. 如果配置了 Erlang 根目录 r25_path: + - Windows 使用 /bin/erl.exe 与 escript.exe + - Linux 使用 /bin/erl 与 escript +3. rebar3 命令: + - Windows 如配置了 Erlang 路径,用 "" "/rebar3",否则 rebar3.cmd。 + - Linux 如配置了 Erlang 路径,用 "" "/rebar3",否则 ./rebar3。 +4. rebar profile: + - login_server -> login_server_dev, app login_server + - client_server -> client_server_dev, app client_server + - 其他 -> game_server_dev, app game_server +5. 标准启动命令: + rebar3 as shell --eval -1,"application:ensure_all_started()." --setcookie --name --config "" +6. 快速启动命令: + erl -pa <_build profile ebin paths> -smp enable -setcookie -name -config "" -eval "application:ensure_all_started()." +7. 启动工作目录统一为 server_root,不要切换到 run/;日志路径由 sys.config 指向 run//log。 +8. stop 命令用 erl -noshell -name stop_@ -setcookie -eval "rpc:call('', init, stop, [])" -s c q,参数列表方式执行,避免 shell 引号问题。 +9. remsh 命令用 erl -setcookie -name remsh_@ -remsh 。 +10. 节点状态使用 net_adm:ping;批量状态检查需要缓存 NodeStatusCache,减少重复查询。 +11. 可实现 EPMD 快速查询作为优化;至少提供 check_node_status 和 check_nodes_status。 +12. Windows 新窗口启动用 cmd/start 或 powershell/cmd 方案,并记录窗口标题,停止服务器后尝试关闭由本工具启动的服务窗口和 remsh 窗口。 +13. Linux 后台启动优先 screen,其次 tmux,否则后台进程。 + +十、CLI 规格 + +创建 server_manager_cli.py,命令行参数: + +全局参数: +- --root / -r:服务器根目录 +- --config / -c:指定配置文件 +- --cookie:覆盖 Erlang cookie + +子命令: +- list:列出所有服务器,按 game/cross/login/client/center/other 分组。 +- create:创建服务器。 + - --type/-t 必填,choices game/login/center/cross/client + - --id/-i 必填 int + - --prefix/-p + - --db-host、--db-port、--db-user、--db-pass + - --login-node、--center-node、--server-name + - --tcp-port、--http-port + - --open-time + - --overwrite +- start: + - --server/-s 必填 + - --quick/-q 表示不用 rebar3,直接 erl + - --background/-b 表示后台运行;默认前台 +- stop:--server/-s 必填 +- connect:--server/-s 必填,--ip 可选 +- compile: + - --target/-t choices all/code/proto/table/tbllog,默认 all + - --type choices game/login,默认 game +- regen:--server/-s 必填,重新生成 sys.config +- interactive / i:交互式菜单 + +CLI 初始化时自动检测 server_root: +1. 命令行 --root 优先。 +2. 读取 tool.config 的 server_root/run_dir 次之。 +3. 当前工作目录、脚本目录、上级目录中查找 apps 与 config。 +4. 加载配置后执行 run_startup_migrations。 + +十一、GUI 规格 + +GUI 使用 PyQt6。主窗口标题为 Server Manager v,最小尺寸 1000x750,深色主题: +- 背景 #181b22 +- 面板 #242932 +- 边框 #3a4352 +- 主色 #4f8cff +- 成功 #25c889 +- 危险 #ff5c6a +- 文本 #e8eef7 / #aab4c3 + +主窗口行为: +1. 无项目启动时显示欢迎页。 +2. 菜单栏: + - 文件:打开项目、最近的项目、关闭项目、退出。 + - 帮助:检查更新、关于。 +3. 状态栏显示运行状态、版本号、系统时间。 +4. 打开项目成功后显示主项目内容:上方主标签页,下方控制台输出区。 +5. 主标签页: + - 控制台 + - 创建服务器 + - 服务器管理 + - 日志查看 + - 工具设置 +6. 控制台输出区只在“控制台”页显示;其他页隐藏。 +7. 顶部角落显示本机 IP,点击可复制。 +8. 首次打开项目后初始化节点状态缓存。 + +GUI 图标: +使用代码内嵌简洁线性 SVG 图标渲染为 QIcon,至少支持 calendar、plus、server、log、settings、code、bolt、shield、table、database、file、trash、play、stop、rocket、network、user、branch、download、folder、check、link、edit、refresh、save、external、eye、eye_off。 + +控制台页: +1. 分三组卡片: + - 编译与构建:编译代码、快速编译、编译协议、编译Table、编译Tbllog、清理编译。 + - 服务器运行:运行服务器、快速启动、连接节点、查看账号、停止服务器、服务器清档。 + - 版本控制:SVN 更新。 +2. 编译命令: + - svn update + - rebar3 as game_server_dev compile + - rebar3 protobuf compile -a game_server + - rebar3 cache compile -a game_server + - rebar3 tbllog compile + - rebar3 clean + - 快速编译:escript shell/es/emake.escript 1 && escript shell/es/emake.escript 2 +3. 运行/快速启动前检查节点是否已在线,在线则询问是否 remsh 连接。 +4. 停止服务器要异步执行,进度输出到控制台,成功后更新状态缓存。 +5. 清档功能:停止服务器后连接 MySQL,删除游戏库和日志库,需要明确危险提示。 +6. 查看账号功能:连接数据库展示玩家账号相关数据,结果表格支持数字排序。 + +创建服务器页: +1. 卡片布局包含“服务器配置”“数据库配置”“节点配置”。 +2. 服务器类型下拉:游戏服务器、登录服务器、客户端服务器、中心服务器、跨服服务器。 +3. 按类型动态显示字段: + - game:server_name、tcp_port、http_port、auto_reload、open_time、login_node、center_node + - login:tcp_port、auto_reload + - center:auto_reload、open_time、login_node + - cross:auto_reload、open_time、center_node + - client:连接服务器ID、连接服务器端口、登录服地址、登录服HTTP端口、auto_reload、login_node +4. ID 范围从 default.kv 的 _id_min/max/default 读取,越界时自动修正并提示。 +5. 服务器 ID 改变时自动更新服务器名与默认端口。 +6. 数据库和节点支持“使用默认配置”复选框,默认勾选并禁用手工输入。 +7. 点击创建前展示确认信息,再调用 server_creator.create_server。 + +服务器管理页: +1. 顶部显示运行目录。 +2. 两个子标签: + - 本地服务器:扫描 run_dir,按 game/cross/login/client/center/other 分类显示;每项显示状态图标、类型图标、是否有 kv.config。 + - 远程服务器:从登录服节点 RPC 加载远程服务器列表,表格列为服务器ID、服务器名称、服务器节点,支持过滤和排序。 +3. 本地操作:刷新列表、刷新状态、远程连接、编辑配置、重新生成配置、删除服务器。 +4. 远程操作:从登录服加载、刷新状态、搜索过滤、双击或按钮 remsh 连接。 +5. 远程列表通过登录服节点 rpc:call 获取 server_temp_info/server_info_lib 数据;输出需解析 server_id、server_name、server_node。 +6. 状态刷新使用批量 check_nodes_status,并写入 NodeStatusCache。 +7. 编辑配置打开 KvConfigEditDialog,读取 kv.config,允许添加/删除 default.kv 中声明的可编辑配置项;保存后重新生成 sys.config。 + +日志查看页: +1. 顶级“日志查看”内有两个子页签: + - 日志搜索:加载单个日志文件。 + - 全局搜索:用 ripgrep 在当前服务器 log 目录递归搜索。 +2. 日志搜索支持: + - 选择服务器目录。 + - 日志类型:服务器日志 / 玩家协议(tag_log)。 + - 服务器日志预设:error.log、debug.log、http_info.log、http_error.log、info.log、http.log。 + - 自定义日志文件名。 + - 玩家协议按角色 ID 生成 network_codec-.log。 + - 指定归档日期,文件名变为 .YYYYMMDD。 + - 加载日志、开启玩家协议、关闭玩家协议、打开文件夹、独立窗口、清空、自动滚动。 +3. 日志文件路径规则: + - 无日期://log// + - 有日期://log//.YYYYMMDD + - 玩家协议 subdir=tag_log +4. 单文件读取最大约 3MB,编码依次尝试 utf-8、utf-8-sig、gbk、latin-1。 +5. 开启/关闭玩家协议通过 rpc_role_gs_trace_network 调 role_gs:trace_network/1 或 trace_network_close/1。 +6. 全局搜索要求找到 rg,否则提示用户放置 rg.exe 或设置 RG_PATH;搜索结果在独立窗口打开。 + +日志正文组件: +1. LogViewerPane 包含搜索行和只读 QPlainTextEdit。 +2. 支持区分大小写、仅显示匹配行、正则表达、全词匹配。 +3. 检测到 rg 时,过滤、高亮、上/下一个都尽量与 ripgrep 一致;否则回退 Python re。 +4. 支持快捷键: + - Enter/F3/n:下一个 + - Shift+F3/Shift+N:上一个 + - g:文件开头 + - Shift+G:文件末尾 + - / 或 Ctrl+F:聚焦搜索框 +5. 使用 rg --json 时要将 UTF-8 byte offsets 转为 Qt UTF-16 光标位置。 + +工具设置页: +1. 表单分组: + - 项目目录配置:服务器根目录、运行目录,均有浏览按钮。 + - 数据库配置:host、port、user、password,密码框带眼睛图标。 + - 服务器默认配置:prefix、默认服务器ID、server_type、server_name、默认 TCP/HTTP 端口。 + - Erlang 与节点配置:Erlang 路径、Erlang 版本、登录服节点、中心服节点、Cookie。 +2. 提供“检查版本”按钮,比对系统 erl 和配置路径 erl 的 OTP 版本。 +3. 登录服节点、中心服节点提供“测试连接”,使用 net_adm:ping。 +4. “填充默认值”从 default.kv 填入表单。 +5. “保存配置”写入 tool.config;若 Erlang 路径变化,询问是否删除 server_root/_build 并执行 rebar3 as game_server_dev compile。 + +欢迎页: +1. 无项目时显示欢迎页,提供打开项目、最近项目、定位项目、帮助入口。 +2. 最近项目来自应用级 app_config.json,只显示仍存在且含 default.kv 的项目。 + +更新机制: +1. src/updater.py 提供: + - get_current_version + - get_version_info + - version_less + - fetch_update_manifest + - get_delta_for_current + - download_file + - clean_up_old_version + - apply_delta_patch + - run_installer_and_exit +2. 打包后 version.json 优先从 main.exe 同目录读取,其次从 _MEIPASS。 +3. GUI 启动 1.5 秒后后台检查 update_url;远端 version 与本地不同则询问是否更新。 +4. 当前版本有 delta_updates 且 patch_url、new_exe_sha256 完整时优先增量,否则下载 full_installer_url 或 download_url。 +5. 下载时显示 QProgressDialog;可取消。 +6. 增量使用 bsdiff4.patch(当前 main.exe, patch) 生成临时新 exe,校验 sha256,通过 mini_updater.exe 替换并重启。 +7. 全量更新下载安装包后用 /VERYSILENT /SUPPRESSMSGBOXES /FORCECLOSEAPPLICATIONS 静默安装并退出。 +8. mini_updater.py 只依赖标准库,参数: + - --pid + - --install-dir + - --new-exe-path + - --target-exe-name + 等待 pid 退出,重命名旧 exe 为 .old,移动新 exe,cwd 为安装目录启动新程序。 + +打包脚本: +1. src/build.bat: + - 安装 PyQt6、pyinstaller、pymysql、Pillow。 + - 调 icon/convert_icon.py。 + - 清理 dist/build/spec。 + - PyInstaller onedir console 模式,name=main,入口 server_manager_gui.py。 + - add-data: ../version.json、config.json、server_commands.py、server_creator.py、icon。 + - hidden-import: PyQt6.QtWidgets/Core/Gui、pymysql、server_commands、server_creator、server_manager_cli、updater、app_config、bsdiff4、requests。 + - collect-all PyQt6 与 bsdiff4。 + - 若根目录有 rg.exe,复制到 dist/main/rg.exe。 + - 复制 ../output/config 到 dist/main/config。 + - 再用 PyInstaller onefile windowed 打包 ../mini_updater.py 到 dist/mini_updater.exe。 +2. src/build_linux.sh 与 build.bat 等价,但使用 python3、Linux add-data 冒号分隔,输出 dist/main/main。 +3. src/build_portable.bat 提供单文件便携版尝试,失败回退 onedir。 +4. build_installer.iss: + - 正式输出 output/ServerManager_Setup.exe。 + - 本地测试 /DLOCAL_TEST=1 输出 output_local/ServerManager_Setup_LOCAL.exe,独立 AppId。 + - 安装 src/dist/main/* 到 {app},安装 mini_updater.exe、version.json、output/config/*。 + - 创建开始菜单/可选桌面快捷方式。 + - 安装完成后运行 main.exe。 +5. release.py: + - get_current_version 以 output/version.json 为准读取旧版本。 + - set_version_pre(new_version, notes):更新 build_installer.iss 的 MyAppVersion、main.py 的 LOCAL_VERSION、version.json 的 version/release_notes/delta_updates,并同步到 src/dist/main/version.json 和 _internal/version.json。 + - set_version_post(old_exe):复制 version.json 到 output;若提供旧版 main.exe 且安装 bsdiff4,生成 output/patches/v_to_v.patch 并填 new_exe_sha256。 + +入口脚本: +1. src/run.bat: + - 自动计算 SERVER_ROOT 为脚本目录向上两级。 + - 优先使用 SERVER_MANAGER_MAIN、同目录 main.exe、dist/main/main.exe。 + - 无参数进入 interactive。 + - gui 启动 GUI。 + - 其他参数转发 CLI,并附加 --root SERVER_ROOT。 +2. src/run.sh 同理,优先 SERVER_MANAGER_MAIN、同目录 main、dist/main/main。 + +十二、核心代码复刻细则 + +下面是必须复刻的核心代码结构、关键函数签名和关键实现逻辑。生成代码时可以按这些骨架实现,允许适度重构,但外部行为、配置文件路径、命令格式、函数职责必须保持一致。 + +1. app_config.py 核心代码 + + 必须提供这些函数: + + - get_app_config_dir() -> Path + - get_app_config_path() -> Path + - load_app_config() -> Dict[str, Any] + - save_app_config(data: Dict[str, Any]) -> None + - get_recent_projects() -> List[str] + - save_recent_project(project_path: str) -> None + - get_app_log_dir() -> Path + + 实现要点: + + - Windows 使用 os.environ["LOCALAPPDATA"] 或用户主目录下的 ServerManager。 + - 非 Windows 使用 Path.home() / ".config" / "ServerManager"。 + - load_app_config 读取 JSON,异常或格式错误返回 {}。 + - save_app_config 使用 json.dump(..., ensure_ascii=False, indent=2)。 + - get_recent_projects 从 app_config.json 的 recent_projects 读取,去重,只保留存在且 project_has_default_kv_for_manager(path) 为 True 的项目,最多 10 个。 + - save_recent_project 将 resolve 后的路径插到列表首位。 + + 伪代码: + + def get_app_config_dir(): + if sys.platform == "win32": + base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~") + root = Path(base) / "ServerManager" + else: + root = Path.home() / ".config" / "ServerManager" + root.mkdir(parents=True, exist_ok=True) + return root + + def get_recent_projects(): + data = load_app_config() + raw = data.get("recent_projects") or [] + result = [] + for item in raw[:15]: + p = Path(str(item).strip()) + if p.exists() and str(p) not in result and project_has_default_kv_for_manager(p): + result.append(str(p)) + if len(result) >= 10: + break + return result + +2. server_commands.py 配置与模板同步核心代码 + + 必须定义: + + - IS_WINDOWS = platform.system() == "Windows" + - IS_LINUX = platform.system() == "Linux" + - REBAR3_CMD = "rebar3.cmd" if IS_WINDOWS else "./rebar3" + - MANAGED_CONFIG_TEMPLATE_FILES = ("default.kv", "sys_center.config.example", "sys_client.config.example", "sys_cross.config.example", "sys_game.config.example", "sys_login.config.example") + + 必须提供这些函数: + + - get_server_manager_config_dir(server_root: Union[str, Path]) -> Path + - get_bundled_tool_config_template_dir() -> Path + - sync_server_manager_config_templates(project_root: Path) -> Tuple[bool, str] + - ensure_server_manager_config(project_root: Path) -> Tuple[bool, str] + - ensure_and_get_server_manager_config_dir(project_root: Path) -> Tuple[Optional[Path], str] + - project_has_default_kv_for_manager(project_root: Union[str, Path]) -> bool + - read_config_file(file_path: str) -> Dict[str, str] + - write_config_file(file_path: str, config: Dict[str, str]) -> None + - read_merged_config(server_root: str, server_dir: str = None, run_dir: str = None) -> Dict[str, str] + + 实现要点: + + - get_server_manager_config_dir 返回 Path(server_root).resolve() / ".server_manager" / "config"。 + - get_bundled_tool_config_template_dir 打包后返回 Path(sys.executable).parent / "config",源码运行返回 Path(__file__).parent.parent / "output" / "config"。 + - sync_server_manager_config_templates 必须检查模板目录存在、default.kv 存在、每个 MANAGED_CONFIG_TEMPLATE_FILES 都存在;逐个 copy2 到项目 .server_manager/config。 + - read_config_file 读取 key=value,忽略空行、# 注释、% 注释;去除空白和首尾双引号。 + - read_merged_config 合并顺序为 default.kv -> tool.config -> run_dir tool.config -> kv.config,最后强制 merged["ip"] 和 merged["game_host"] 为 get_local_ip()。 + + read_merged_config 伪代码: + + def read_merged_config(server_root, server_dir=None, run_dir=None): + cfg_dir = get_server_manager_config_dir(server_root) + merged = {} + if (cfg_dir / "default.kv").exists(): + merged.update(read_config_file(str(cfg_dir / "default.kv"))) + elif (cfg_dir / "default.config").exists(): + merged.update(read_config_file(str(cfg_dir / "default.config"))) + + if (cfg_dir / "tool.config").exists(): + merged.update(read_config_file(str(cfg_dir / "tool.config"))) + + if run_dir: + for p in (Path(run_dir) / "config" / "tool.config", Path(run_dir) / "tool.config"): + if p.exists(): + merged.update(read_config_file(str(p))) + break + + if server_dir: + kv = (Path(run_dir) if run_dir else Path(server_root) / "run") / server_dir / "config" / "kv.config" + if kv.exists(): + merged.update(read_config_file(str(kv))) + + local_ip = get_local_ip() + merged["ip"] = local_ip + merged["game_host"] = local_ip + return merged + +3. server_commands.py 命令生成核心代码 + + 必须提供这些函数: + + - get_local_ip() -> str + - get_erl_cmd(erl_path: str = None) -> str + - get_escript_cmd(erl_path: str = None) -> str + - get_rebar3_cmd(server_root: str, erl_path: str = None) -> str + - get_ebin_paths(server_root: str, profile: str = "game_server_dev") -> List[str] + - get_rebar_profile(server_type: str) -> Tuple[str, str] + - build_start_command_by_config(server_root: str, merged_config: Dict[str, str], cookie: str, use_rebar: bool = True, erl_path: str = None) -> str + - build_start_command(server_root: str, server_name: str, cookie: str, use_rebar: bool = True, erl_path: str = None) -> str + - build_stop_command(server_name: str, cookie: str, local_host: str = None, erl_path: str = None) -> List[str] + - build_remsh_command(server_name: str, cookie: str, target_ip: str = None, erl_path: str = None) -> str + - subprocess_args_to_display(args: List[str]) -> str + + 命令格式必须符合: + + - login_server 使用 profile login_server_dev,app login_server。 + - client_server 使用 profile client_server_dev,app client_server。 + - game/center/cross 使用 profile game_server_dev,app game_server。 + - rebar3 启动: + as shell --eval -1,"application:ensure_all_started()." --setcookie --name --config "" + - 快速 erl 启动: + "" -pa "" -pa "" ... -smp enable -setcookie -name -config "" -eval "application:ensure_all_started()." + - stop 必须返回参数列表: + [erl, "-noshell", "-name", "stop_@", "-setcookie", cookie, "-eval", "rpc:call('', init, stop, [])", "-s", "c", "q"] + + build_start_command_by_config 伪代码: + + def build_start_command_by_config(server_root, merged_config, cookie, use_rebar=True, erl_path=None): + server_dir = merged_config.get("_server_dir", "") + config_file = merged_config.get("_config_file", "") + game_host = merged_config.get("game_host", get_local_ip()) + node_name = merged_config.get("node_name", f"{server_dir}@{game_host}").replace('"', '').replace("'", "") + server_type = merged_config.get("server_type", "game_server").strip('"').strip("'") + profile, app_name = get_rebar_profile(server_type) + if use_rebar: + rebar3 = get_rebar3_cmd(server_root, erl_path) + return f'{rebar3} as {profile} shell --eval -1,"application:ensure_all_started({app_name})." --setcookie {cookie} --name {node_name} --config "{config_file}"' + erl = get_erl_cmd(erl_path) + pa_args = " ".join(f'-pa "{p}"' for p in get_ebin_paths(server_root, profile)) + return f'"{erl}" {pa_args} -smp enable -setcookie {cookie} -name {node_name} -config "{config_file}" -eval "application:ensure_all_started({app_name})."' + +4. server_commands.py sys.config 生成核心代码 + + 必须提供: + + - replace_placeholders(template_content: str, config: Dict[str, str]) -> str + - get_template_file(config_path: Path, server_type: str) -> Optional[Path] + - build_replace_map(config: Dict[str, str]) -> Dict[str, str] + - generate_start_config(server_root: str, server_dir: str) -> Optional[Dict[str, str]] + + generate_start_config 必须: + + - 定位 kv.config: /run//config/kv.config。 + - 读取 read_merged_config(server_root, server_dir)。 + - 按 server_type 选择 sys_*.config.example。 + - 将 log_dir 和 role_log 归一化为 run//。 + - 替换所有 ${key}。 + - 写入 /run//config/sys.config。 + - 在 merged_config 中添加内部字段 _server_dir 和 _config_file。 + + 核心伪代码: + + def _normalize_run_log_path(raw, default_tail): + raw = (raw or "").strip('"').strip("'").strip() + if raw.startswith("run/"): + parts = raw.split("/", 2) + tail = parts[2] if len(parts) > 2 and parts[2] else default_tail + else: + tail = raw or default_tail + return f"run/{server_dir}/{tail}" + + merged_config["log_dir"] = _normalize_run_log_path(merged_config.get("log_dir", "log"), "log") + if "role_log" in merged_config: + merged_config["role_log"] = _normalize_run_log_path(merged_config.get("role_log", ""), "log") + + replace_map = build_replace_map(merged_config) + output = template_content + for key, value in replace_map.items(): + output = output.replace(f"${{{key}}}", str(value)) + unreplaced = re.findall(r"\$\{(\w+)\}", output) + if unreplaced: + print(f"[警告] 配置文件中有未替换的占位符: {set(unreplaced)}") + sys_config_file.write_text(output, encoding="utf-8") + + build_replace_map 必须为缺失字段提供默认值: + + - prefix=ddxq2 + - server_id=1 + - server_type=game_server + - tcp_port=18001 + - http_port=19001 + - log_dir=log + - merge_server_ids=[] + - merge_server_time={{1970,1,1},{0,0,0}} + - last_merge_server_ids=[] + - open_time={{2025,1,1},{10,0,0}} + - auto_reload=true + - ip/game_host 使用 get_local_ip() + +5. server_commands.py 迁移与列表核心代码 + + 必须提供: + + - run_startup_migrations(server_root, logger_func=None) -> Dict[str, Any] + - get_config_list(run_dir: str) -> List[str] + - get_server_list(run_dir: str) -> Dict[str, List[str]] + - flatten_server_dirs(run_dir: str) -> List[str] + + 迁移状态: + + - 状态文件为 <项目根>/.server_manager/migrations.json。 + - 迁移 ID 至少包含 sys_config_log_dir_v2 与 sys_config_role_log_v1。 + - 每个迁移只执行一次,执行内容是为所有包含 config/kv.config 的服务器重新 generate_start_config。 + + get_server_list 分类规则: + + - 含 _game_s 或 _game_ -> game + - 含 _cross_s 或 _cross_ -> cross + - 含 _login_s 或 _login_ -> login + - 含 _client_s 或 _client_ -> client + - 含 _center_s 或 _center_ -> center + - 其他有 config 目录的 -> other + +6. server_commands.py 节点状态与远程 RPC 核心代码 + + 必须提供: + + - class NodeStatusCache + - get(node_name) -> Optional[bool] + - set(node_name, online) + - set_online(node_name) + - set_offline(node_name) + - batch_set(results) + - get_all() + - clear() + - is_initialized() + - set_initialized(value=True) + - get_node_status_cache() -> NodeStatusCache + - check_node_status(server_name, cookie, target_ip=None, erl_path=None) -> bool + - check_nodes_status(server_names, cookie, target_ip=None, erl_path=None) -> Dict[str, bool] + - query_remote_servers_from_login_rpc(login_node, cookie, erl_path=None, timeout=120) -> List[Dict[str, Any]] + - rpc_role_gs_trace_network(server_name, cookie, role_id, enable, target_ip=None, erl_path=None) -> Tuple[int, str, str, str] + + 节点状态实现要求: + + - check_node_status 内部可调用 check_nodes_status。 + - check_nodes_status 优先尝试 epmd 快速查询,再用 erl net_adm:ping 批量检测;即使不实现 epmd 优化,也必须提供批量接口和超时保护。 + - 本地临时节点名使用 sm_ping_@ 或类似名称,避免重复。 + - 启动 erl 前尽量执行 epmd -daemon。 + + 远程服务器 RPC 要点: + + - 先 ping 登录服节点,失败则给出清晰错误。 + - 通过临时 Erlang 脚本 file:script/1 调用登录服节点。 + - 输出格式为: + SM_COUNT数量 + server_idserver_name_base64server_node + - Python 侧解析 base64 得到 UTF-8 server_name。 + - 返回字段至少包含 server_id、server_name、server_node、running=False。 + + 玩家协议 trace RPC: + + - enable=True 调 role_gs:trace_network(RoleId)。 + - enable=False 调 role_gs:trace_network_close(RoleId)。 + - 返回 returncode、stdout、stderr、完整命令行。 + +7. server_commands.py 日志路径核心代码 + + 必须提供: + + - resolve_log_file_path(run_dir, server_dir, base_filename, date_yyyymmdd=None, subdir="") -> Path + - read_text_file_best_effort(path: Path, max_bytes: int = 3145728) -> Tuple[str, Optional[str]] + + 路径规则: + + log_root = Path(run_dir) / server_dir / "log" + if subdir: + log_root = log_root / subdir + if date_yyyymmdd 是 8 位数字: + name = f"{base_filename}.{date_yyyymmdd}" + else: + name = base_filename + return log_root / name + + 读取规则: + + - 文件不存在、非普通文件、超过 max_bytes 都返回 ("", 错误说明)。 + - 编码依次尝试 utf-8、utf-8-sig、gbk、latin-1。 + - 都失败则 utf-8 errors="replace"。 + +8. server_creator.py 核心代码 + + 必须定义: + + - SERVER_TYPE_MAP = {"game": "game_server", "login": "login_server", "client": "client_server", "center": "center_server", "cross": "cross_server"} + - SERVER_TYPE_NAMES = {"game": "游戏服", "login": "登录服", "client": "客户端测试服", "center": "中心服", "cross": "跨服"} + - BASE_REQUIRED_KEYS、SERVER_TYPE_REQUIRED_KEYS、BASE_CONFIG_KEYS、SERVER_TYPE_SPECIFIC_KEYS。 + + 必须提供: + + - get_required_keys_from_kv(server_root, server_type) + - get_config_keys_from_kv(server_root, server_type) + - get_editable_config_from_default_kv(server_root) + - get_allowed_config_keys(server_type, server_root="") + - get_all_valid_keys(server_root="") + - get_required_keys(server_type, server_root="") + - get_server_type_name(server_type) + - extract_template_placeholders_with_comments(server_root, server_type) + - get_template_config_keys(server_root, server_type) + - get_server_type_str(server_type) + - generate_server_dir_name(prefix, server_type, server_id) + - get_existing_server_ids(run_dir, server_type, prefix) + - build_kv_config(...) + - create_server(...) + - get_id_range_from_config(default_kv, server_type) + - get_default_port(server_type, server_id) + - get_default_server_name(prefix, server_id) + - build_confirmation_message(...) + + build_kv_config 必须生成差异配置: + + server_type_str = SERVER_TYPE_MAP.get(server_type, "game_server") + type_prefix = {"game": "game", "login": "login", "center": "center", "cross": "cross", "client": "client"}[server_type] + db_game_name = f"{prefix}_{type_prefix}_s{server_id}" + db_log_name = f"{prefix}_{type_prefix}_log_s{server_id}" if server_type != "login" else "" + + diff_config = { + "prefix": prefix, + "server_id": str(server_id), + "server_type": server_type_str, + "ip": ip, + "db_host": db_host, + "db_user": db_user, + "db_pass": db_pass, + "db_port": str(db_port), + "log_dir": "log", + "db_game_name": db_game_name, + } + + 类型字段: + + - game:写 server_name、game_host、tcp_port、http_port、open_time、role_log=run//log。 + - login:写 tcp_port。 + - center:写 open_time。 + - cross:写 open_time。 + - client:写 game_host、tcp_port、game_tcp_port、login_host、login_http_port。 + - game/client/center 有 login_node 时写 login_node。 + - game/cross 有 center_node 时写 center_node。 + + create_server 必须: + + - 从 read_merged_config(server_root) 取 ip,取不到用 get_local_ip。 + - run_dir 为空时使用 Path(server_root) / "run"。 + - 创建 //config。 + - 如果 kv.config 已存在且 overwrite=False,返回 False。 + - 写入 kv.config。 + - 调 generate_start_config(server_root, server_dir)。 + - 返回 (True, "服务器 创建成功", merged_config) 或 (False, 错误, None)。 + +9. server_manager_cli.py 核心代码 + + 必须实现 class ServerManagerCLI: + + - __init__(self, server_root: str = None, config_path: str = None) + - _read_tool_config_value(self, detected_root: str, key: str) -> str + - _read_configured_server_root(self, detected_root: str) -> str + - _read_configured_run_dir(self, detected_root: str) -> str + - _detect_server_root(self) -> str + - _find_server_root(self) -> Path + - _load_config(self, config_path: str = None) + - list_servers(self) -> dict + - create_server_cmd(...) + - start_server(self, server_name: str, use_rebar: bool = True, foreground: bool = False) -> bool + - stop_server(self, server_name: str) -> bool + - connect_server(self, server_name: str, target_ip: str = None) -> bool + - regenerate_config(self, server_name: str) -> bool + - compile(self, target: str = "all", server_type: str = "game") -> bool + - interactive_menu(self) + + CLI 初始化行为: + + - detected_root = _detect_server_root() + - configured_root = _read_configured_server_root(detected_root) + - server_root 优先级:参数 server_root > configured_root > detected_root + - run_dir 优先级:tool.config 的 run_dir > server_root/run + - config_path = get_server_manager_config_dir(server_root) + - 加载配置后设置 prefix、db_host、db_port、db_user、db_pass、cookie。 + - 执行 run_startup_migrations(server_root)。 + + compile 命令格式: + + - all:依次 code、proto、table、tbllog。 + - code: as game_server_dev compile 或 login_server_dev compile。 + - proto: protobuf compile -a game_server。 + - table: cache compile -a game_server。 + - tbllog: tbllog compile。 + +10. server_manager_gui.py Config 核心代码 + + 必须实现 _empty_config_data 与 class Config。 + + _empty_config_data 返回结构: + + { + "workspace": {"server_root": "", "run_dir": ""}, + "database": {"host": "", "user": "", "password": "", "port": 3306}, + "server": { + "prefix": "ddxq2", + "login_server_node": "", + "center_server_node": "", + "default_server_id": "1", + "server_type": "game_server", + "server_name": "", + "ip": get_local_ip(), + "game_host": get_local_ip(), + "cookie": "ddxq2-node", + }, + "erlang": {"r25_path": "", "erl": "erl", "werl": "werl", "escript": "escript"}, + "login_db": {"name": "", "server_id": 900, "host": "", "port": 0, "user": "", "password": ""}, + "commands": {"rebar": REBAR3_CMD, "svn": "svn"}, + } + + Config 必须: + + - __init__(project_root=None):无项目时 config_error="未打开项目"。 + - open_project(project_root):调用 ensure_and_get_server_manager_config_dir;成功后设置 tool_dir、tool_config_path、data。 + - _load_tool_config:读 key=value。 + - _save_tool_config:按固定分组写 tool.config。 + - _load_default_kv(server_root):读 .server_manager/config/default.kv。 + - _load_config:tool.config 为空时从 default.kv 补齐;首次运行自动创建 run_dir;需要保存补齐后的 tool.config。 + - save:写 tool.config 前刷新 ip/game_host 为 get_local_ip()。 + - get/set 支持嵌套 key。 + - has_project/is_config_complete/is_configured/get_missing_config。 + +11. server_manager_gui.py GUI 类结构 + + 必须保留这些核心类和职责: + + - NoWheelSpinBox、NoWheelComboBox、NoWheelDateTimeEdit:禁用滚轮误操作。 + - ServerIdTableItem、NumericTableWidgetItem:表格数字排序。 + - CopyableIpLabel:点击复制 IP。 + - CommandRunner(QThread):执行 shell 命令,分批发 output_signal,结束发 finished_signal。 + - NodeStatusCheckWorker(QThread):批量检查节点状态。 + - GroupedNodeStatusCheckWorker(QThread):按 cookie 分组检查节点状态。 + - StopServerWorker(QThread):停止节点、轮询离线、关闭本工具启动的窗口。 + - OutputConsole(QTextEdit):彩色追加输出,支持独立 ConsoleWindow。 + - CommandTab:编译、启动、remsh、停止、清档、查看账号、SVN。 + - ClearDatabaseDialog/ClearDatabaseThread:清档。 + - ViewAccountsDialog:查账号。 + - ServerSelectDialog:本地服务器选择,含状态刷新。 + - RemoteConnectDialog:本地/远程节点连接选择。 + - KvConfigEditDialog:kv.config 编辑与 sys.config 重新生成。 + - CreateServerTab:创建服务器。 + - ServerManageTab:本地/远程服务器管理。 + - ConfigTab:工具设置。 + - DefaultKvTab:default.kv 编辑。 + - UpdateCheckWorker/UpdateDownloadWorker:自动更新后台工作线程。 + - WelcomePage:无项目欢迎页。 + - MainWindow:主窗口、菜单、欢迎页/项目页切换、更新检查、项目迁移、状态缓存。 + + MainWindow 必须: + + - 初始化深色样式。 + - 使用 QStackedWidget 管理 WelcomePage 与项目内容。 + - 菜单栏 setNativeMenuBar(False)。 + - 文件菜单含打开项目、最近的项目、关闭项目、退出。 + - 帮助菜单含检查更新、关于。 + - 项目内容使用 QSplitter 垂直分割主标签页与控制台。 + - 主标签页添加:控制台、创建服务器、服务器管理、日志查看、工具设置。 + - 切到服务器管理页时从缓存刷新状态;切到日志查看页时刷新服务器列表。 + - closeEvent 中停止还在运行的 QThread,避免退出崩溃。 + + server_manager_gui.py main() 行为: + + - 调 clean_up_old_version。 + - 抑制 PyInstaller 退出时的临时目录告警。 + - 如果 len(sys.argv) > 1,调用 _run_cli_from_argv(),将参数交给 server_manager_cli.main。 + - 无参数创建 QApplication、配置 frozen Qt 运行时、显示 MainWindow。 + +12. log_viewer_pane.py 与 rg_search.py 核心代码 + + rg_search.py 必须提供: + + - find_rg_executable() -> Optional[Path] + - rg_match_spans_qt(full_text, pattern, case_sensitive, use_regex, whole_word, max_spans=3000) + - compile_python_span_pattern(pattern, case_sensitive, use_regex, whole_word) + - rg_filter_lines(full_text, pattern, case_sensitive, use_regex, whole_word) + - rg_search_directory(search_root, pattern, case_sensitive, use_regex, whole_word, glob_globs=None) + + find_rg_executable 查找顺序: + + - RG_PATH 环境变量。 + - 打包后 main.exe 同目录的 rg.exe/rg。 + - 源码仓库根目录的 rg.exe/rg。 + - PATH 中的 rg。 + + rg_match_spans_qt 要点: + + - 调 rg --json --color never。 + - 非大小写敏感加 -i。 + - use_regex=False 用 --fixed-strings;whole_word=True 加 -w。 + - use_regex=True 且 whole_word=True 时 pattern 包装为 \b(?:pattern)\b。 + - 从 rg JSON 的 absolute_offset 和 submatches start/end 得到 UTF-8 byte offsets。 + - 将 UTF-8 byte offsets 转成 Qt QTextCursor 使用的 UTF-16 偏移: + qt_pos = len(prefix.encode("utf-16-le")) // 2。 + + LogViewerPane 必须: + + - 保存 self._full_text、self._current_match_range、spans cache。 + - UI 包含搜索输入、区分大小写、仅显示匹配行、正则表达、全词匹配、上一个、下一个、rg 提示、只读 QPlainTextEdit。 + - set_full_text(text, reset_search=True) 设置全文并可重置搜索选项。 + - _apply_filter_or_full:勾选仅显示匹配行时优先 rg_filter_lines,失败回退 Python re。 + - _match_spans_in_plain:优先 rg_match_spans_qt,失败回退 Python re,最多 3000 个匹配。 + - find_next/find_prev:循环跳转匹配。 + - _highlight_matches:当前匹配用亮色,其余匹配用暗黄色。 + - 快捷键 n、Shift+N、g、Shift+G、/、F3、Shift+F3、Ctrl+F。 + +13. log_viewer_tab.py 核心代码 + + 必须提供: + + - _run_dir_from_config(config) -> str + - _refresh_server_combo(combo, config) -> bool + - class LogLoadTab + - class LogSearchTab + - class LogViewerTab + + LogLoadTab 行为: + + - 预设日志名:error.log、debug.log、http_info.log、http_error.log、info.log、http.log。 + - 两种模式:服务器日志、玩家协议(tag_log)。 + - 玩家协议文件名 network_codec-.log,role_id 必须纯数字。 + - 日期归档文件名 .YYYYMMDD。 + - load_log 调 resolve_log_file_path 和 read_text_file_best_effort;成功显示到内嵌 QPlainTextEdit,并可打开 LogViewerStandaloneDialog。 + - open_log_directory Windows 用 os.startfile,其他平台用 xdg-open。 + - _prompt_trace 只允许 game 类型服务器,输入 role_id 后调 rpc_role_gs_trace_network。 + + LogSearchTab 行为: + + - 必须检测 rg,找不到时 QMessageBox 提示。 + - 搜索根目录为 //log。 + - 调 rg_search_directory;结果用 LogViewerStandaloneDialog 打开。 + - 与 LogLoadTab 的服务器下拉互相同步。 + +14. updater.py 与 mini_updater.py 核心代码 + + src/updater.py 必须实现: + + - _version_json_path:打包后优先 main.exe 同目录 version.json,再 sys._MEIPASS/version.json;源码运行用项目根 version.json。 + - get_current_version:读取 version 字段,失败返回 0.0.0。 + - get_version_info:读取完整 JSON,失败返回 {"version": "0.0.0", "release_notes": ""}。 + - _parse_version/version_less:将版本字符串转 int tuple 比较。 + - fetch_update_manifest:HTTP GET update_url,兼容 full_installer_url 与旧 download_url。 + - get_delta_for_current:从 manifest["delta_updates"][current_version] 取 patch_url/new_exe_sha256。 + - download_file:requests stream 下载,progress_callback(percent)。 + - apply_delta_patch:bsdiff4.patch 当前 exe + patch,校验 sha256,写 TEMP/ServerManager_new.exe,调用 apply_update_and_restart。 + - run_installer_and_exit:Windows 下静默参数启动安装包,frozen 时 os._exit(0)。 + + mini_updater.py 必须: + + - argparse 参数 --pid、--install-dir、--new-exe-path、--target-exe-name。 + - is_process_alive Windows 用 ctypes OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION),Unix 用 os.kill(pid, 0)。 + - 每 0.5 秒等待主进程退出。 + - 删除旧的 target.exe.old。 + - os.rename(target.exe, target.exe.old)。 + - shutil.move(new_exe_path, target.exe)。 + - subprocess.Popen([target_exe_full], cwd=install_dir, close_fds=True, creationflags=DETACHED_PROCESS)。 + - 错误写入 install_dir/logs/update_error.log。 + +15. release.py 核心代码 + + 必须提供: + + - read_text/write_text + - read_version_from_json + - get_current_version:只以 output/version.json 为准。 + - set_version_pre(new_version, release_notes=None) -> str + - set_version_post(old_exe_path=None) -> None + - _copy_version_to_output() + + set_version_pre 必须: + + - 读取根目录 version.json。 + - old_version 从 output/version.json 读取,读不到直接失败。 + - 正则更新 build_installer.iss 中 #define MyAppVersion。 + - 正则更新 main.py 中 LOCAL_VERSION。 + - version.json 设置 version、release_notes。 + - full_installer_url 固定指向 ServerManager_Setup.exe。 + - delta_updates 只保留 old_version -> new_version 一项,patch_url 格式为 output/patches/v_to_v.patch。 + - 如果 src/dist/main/_internal/version.json 或 src/dist/main/version.json 存在,则同步复制。 + + set_version_post 必须: + + - 找新 exe:src/dist/main/main.exe,找不到再找 src/dist/main.exe。 + - 没有 old_exe_path 时只复制 version.json 到 output。 + - 有 old_exe_path 且 bsdiff4 可用时生成 output/patches/v_to_v.patch。 + - 计算新 exe sha256,填回 version.json 的 delta_updates[old_version].new_exe_sha256。 + - 最后复制 version.json 到 output。 + +16. server_commands.py 新窗口启动与窗口追踪细则 + + 必须提供: + + - run_cmd_in_new_window(cmd: str, working_dir: str, window_title: str = "Server", erl_path: str = None) -> str + - run_cmd_in_terminal_linux(cmd: str, working_dir: str, window_title: str = "Server", erl_path: str = None) -> str + - register_launched_window(server_name: str, kind: str, window_title: str, launch_info: str = "") -> None + - get_launched_windows(server_name: str) -> List[Dict[str, Any]] + - close_launched_windows(server_name: str) -> List[Tuple[Dict[str, Any], bool, str]] + - run_cmd_foreground(cmd: str, working_dir: str, window_title: str = "Server") -> subprocess.Popen + + Windows 新窗口启动必须这样实现: + + - 创建临时 .bat 文件。 + - bat 内容包含: + - `@echo off` + - `title ` + - `cd /d ""` + - `set ESCRIPT_EMULATOR=erl` + - 打印命令分隔线和命令内容 + - 执行原始 cmd + - 使用 `subprocess.Popen(["cmd", "/k", bat_path], creationflags=0x00000010, cwd=working_dir)` 启动新控制台。 + - 返回 `winpid:`,用于后续精确关闭窗口。 + - 如果失败,兜底使用 `start cmd /k ""`,返回 bat_path。 + + Linux 新窗口/后台启动: + + - 创建临时 .sh,内容包含 cd、`export ESCRIPT_EMULATOR=erl`、打印命令、执行 cmd。 + - chmod 755。 + - 如果有 screen:`screen -dmS bash