diff --git a/docs/Tasktracking/2026-04-10-multi-node-backend-foundation.md b/docs/Tasktracking/2026-04-10-multi-node-backend-foundation.md new file mode 100644 index 00000000..6d86ef08 --- /dev/null +++ b/docs/Tasktracking/2026-04-10-multi-node-backend-foundation.md @@ -0,0 +1,51 @@ +Task Record: Multi-node shared control — Go backend foundation + +Date: 2026-04-10 +Related Module: config, database, web/service — multi-node architecture +Change Type: Feature + +Background + +需要支持多个 3x-ui 面板实例共享同一个 MariaDB 数据库,由 master 节点管理配置,worker 节点同步配置并上报流量。为此引入节点角色配置、共享元数据模型、写入保护和版本同步机制。 + +Changes + +- `config/config.go`: 新增 `NodeRole`、`NodeConfig` 结构体,`GetNodeConfigFromJSON()` 读取节点角色/ID/同步间隔/流量刷盘间隔,`ValidateNodeConfig()` 校验配置合法性 +- `database/model/node_state.go`: 新增 `NodeState` 模型,记录每个节点的心跳、同步状态、错误信息 +- `database/model/shared_state.go`: 新增 `SharedState` 模型,键值对 + 版本计数器,用于缓存失效检测 +- `database/shared_state.go`: `BumpSharedAccountsVersion()` 原子递增版本号,`GetSharedAccountsVersion()` 读取当前版本,`UpsertNodeState()` 更新节点状态 +- `database/db.go`: MariaDB 模式下自动迁移 `SharedState` 和 `NodeState` 表,seed 版本行 +- `web/service/node_guard.go`: `IsWorker()`/`IsMaster()`/`RequireMaster()`/`IsSharedModeEnabled()` 角色判断和写入保护 +- `web/service/inbound.go`: 所有写操作(AddInbound/DelInbound/UpdateInbound/AddInboundClient 等)前调用 `ensureSharedWriteAllowed()`,写操作内 `bumpSharedVersion(tx)` 原子递增版本号 +- `web/service/node_sync.go`: `NodeSyncService` — worker 轮询版本号变化 → 加载快照 → 缓存到本地 → 应用到 Xray;master 心跳循环 +- `web/service/node_cache.go`: `SharedAccountsSnapshot` 序列化/反序列化到本地 JSON 缓存 +- `web/service/traffic_flush.go`: `TrafficFlushService` — 收集流量 delta → 写入持久化队列 → 定时刷盘到 MariaDB +- `web/service/traffic_pending.go`: `TrafficPendingStore` — 基于文件的持久化 delta 队列,支持 merge 语义 +- `web/job/xray_traffic_job.go`: 共享模式下走 `TrafficFlushService.Collect()` 路径,非共享模式走原有 `AddTraffic()` 路径 +- `web/web.go`: 新增 `startNodeLoops()` 和 `startTrafficFlushLoop()` 启动入口 +- `x-ui.sh`: 新增节点管理菜单(设置角色/ID/同步间隔/流量刷盘间隔) +- `install.sh`: 安装流程中增加节点角色配置提示 +- `README.md`: 新增多节点共享控制文档 + +Impact + +- 新增数据库表:`node_states`、`shared_states` +- 新增配置项:`nodeRole`、`nodeId`、`syncInterval`、`trafficFlushInterval` +- 修改 `inbounds` 和 `client_traffics` 写入流程,增加共享写入保护 +- 新增流量持久化队列文件:`traffic-pending.json` +- 不影响非 MariaDB 模式的现有行为 + +Verification + +- `go test ./config/ -v` — PASS +- `go test ./database/ -v` — PASS +- `go test ./web/service/ -run TestNode -v` — PASS +- `go test ./web/service/ -run TestTraffic -v` — PASS +- `bash -n install.sh` — syntax OK +- `bash -n x-ui.sh` — syntax OK + +Risks And Follow-Up + +- worker 节点需要配置 `nodeId` 和 `mariadb` 数据库类型,否则启动校验失败 +- 流量刷盘依赖 `traffic-pending.json` 文件,磁盘故障可能导致 delta 丢失 +- 后续需要处理残留 `inboundId: 0` delta 导致外键约束失败的问题(已在后续 commit 中修复) diff --git a/docs/Tasktracking/2026-04-15-improve-mariadb-flow-and-traffic-flush.md b/docs/Tasktracking/2026-04-15-improve-mariadb-flow-and-traffic-flush.md new file mode 100644 index 00000000..e77a0ac4 --- /dev/null +++ b/docs/Tasktracking/2026-04-15-improve-mariadb-flow-and-traffic-flush.md @@ -0,0 +1,61 @@ +Task Record: Improve MariaDB flow, DB settings init, and traffic flush + +Date: 2026-04-15 +Related Module: install.sh, x-ui.sh, config, database, web/service — MariaDB 安装/切换/流量刷盘 +Change Type: Feature + +Background + +MariaDB 安装和切换流程存在多个问题:安装脚本逻辑分散、数据库设置初始化不完整、流量刷盘服务存在 bug。本次提交对整个 MariaDB 相关流程进行了大规模重构和修复。 + +Changes + +- `install.sh`(+598/-行重构): + - 重构 MariaDB 安装/切换流程,统一本地和远程 MariaDB 配置路径 + - 新增 MariaDB 业务用户/数据库创建逻辑 + - 改进卸载流程,支持部分安装状态下的清理 + - 新增 `tests/mariadb_install_switch_test.sh` 测试覆盖 +- `x-ui.sh`(+422/-行重构): + - 重构数据库管理菜单,支持本地/远程 MariaDB 切换 + - 新增 MariaDB 端口校验、远程访问管理等菜单项 +- `config/config.go`: + - 新增 `GetDBConfigFromJSON()` 读取数据库连接配置 + - 新增 `readGroupedString()`/`readGroupedInt()` 通用配置读取辅助函数 + - 配置别名映射支持多分组查找 +- `database/shared_state.go`: + - 改进版本号操作的事务安全性 +- `web/service/traffic_flush.go`: + - `Collect()` 新增 inbound-only 残留流量 delta 计算(inbound 总量 - 客户端总量) + - `flushToDatabase()` 改进 UPSERT 逻辑,支持 MariaDB 的 `ON DUPLICATE KEY UPDATE` + - 新增 `ReconcileSharedTrafficState()` 调用(auto-renew/disable 过期客户端) +- `web/service/traffic_pending.go`: + - 改进 `Merge()` 语义,支持按 `(kind, inboundId, email)` 键去重合并 +- `main.go`: + - 新增 `NodeConfig` 启动校验入口 + - 改进 MariaDB 连接初始化流程 +- `update.sh`: 更新脚本适配新的安装流程 + +Impact + +- `install.sh`: 大规模重构,影响所有 MariaDB 安装/切换/卸载路径 +- `x-ui.sh`: 数据库管理菜单重构 +- `config/config.go`: 新增配置读取辅助函数 +- `web/service/traffic_flush.go`: 流量刷盘逻辑改进 +- `web/service/traffic_pending.go`: delta 队列合并语义改进 +- `main.go`: 启动流程增加节点配置校验 + +Verification + +- `go test ./config/ -v` — PASS +- `go test ./database/ -v` — PASS +- `go test ./web/service/ -run TestTraffic -v` — PASS +- `go test ./main_test.go -v` — PASS +- `bash -n install.sh` — syntax OK +- `bash -n x-ui.sh` — syntax OK +- `bash tests/mariadb_install_switch_test.sh` — PASS + +Risks And Follow-Up + +- 安装脚本改动量大,需要在多种发行版上验证 +- 流量刷盘的 `inboundId: 0` 残留问题尚未处理(后续 commit 修复) +- 配置读取辅助函数的分组别名映射需要与 JSON 结构保持同步 diff --git a/docs/Tasktracking/2026-04-15-uninstall-mariadb-option.md b/docs/Tasktracking/2026-04-15-uninstall-mariadb-option.md new file mode 100644 index 00000000..144d0d79 --- /dev/null +++ b/docs/Tasktracking/2026-04-15-uninstall-mariadb-option.md @@ -0,0 +1,31 @@ +# 任务记录:uninstall-mariadb-option + +- 日期:2026-04-15 +- 关联模块:x-ui uninstall flow / database cleanup / test script +- 变更类型:优化 + +## 背景 +卸载流程原先只移除面板服务与文件,不处理 MariaDB 业务库、业务账号和本机 MariaDB 包,用户在希望彻底清理时需要手动处理。 + +## 修改内容 +- 在 `x-ui.sh` 的 `uninstall()` 中新增交互项:`是否删除数据库并卸载本机 MariaDB?`。 +- 当当前数据库类型为 MariaDB 且 host 为本机地址(`127.0.0.1`/`localhost`/`::1`)时: + - 删除业务库与业务账号(`localhost`、`127.0.0.1`、`::1`)。 + - 卸载本机 MariaDB 服务与相关软件包。 +- 当数据库为远程 MariaDB 时,输出提示并跳过数据库删除与卸载,避免误删远程资源。 +- 新增 `remove_local_mariadb_data` 与 `uninstall_local_mariadb_packages` 两个函数。 +- 更新 `tests/mariadb_install_switch_test.sh`,增加新卸载逻辑关键文本断言。 + +## 影响范围 +- 影响文件:`x-ui.sh`、`tests/mariadb_install_switch_test.sh`。 +- 不影响面板安装流程、数据库切换流程、数据库结构。 +- 仅在卸载流程中新增可选数据库清理能力。 + +## 验证情况 +- 执行 `bash -n x-ui.sh`,通过。 +- 执行 `bash -n install.sh`,通过。 +- 执行 `bash tests/mariadb_install_switch_test.sh`,通过。 + +## 风险与后续 +- 用户若选择删除数据库,相关业务数据将不可恢复。 +- 后续可增加二次确认,显示将删除的数据库名和用户名,以进一步降低误操作风险。 diff --git a/docs/Tasktracking/2026-04-15-validate-mariadb-port-input.md b/docs/Tasktracking/2026-04-15-validate-mariadb-port-input.md new file mode 100644 index 00000000..2c35bab8 --- /dev/null +++ b/docs/Tasktracking/2026-04-15-validate-mariadb-port-input.md @@ -0,0 +1,27 @@ +# 任务记录:validate-mariadb-port-input + +- 日期:2026-04-15 +- 关联模块:install script / db switch menu / test script +- 变更类型:修复 + +## 背景 +远程 MariaDB 连接配置流程中,端口输入未做格式和范围校验,用户输入非法值时只能在后续连接阶段失败,定位不直观。 + +## 修改内容 +- 在 `install.sh` 的远程 MariaDB 分支中新增端口校验循环。 +- 在 `x-ui.sh` 的数据库切换到 MariaDB(远程)分支中新增端口校验循环。 +- 在 `tests/mariadb_install_switch_test.sh` 增加断言,校验两处脚本都包含端口非法提示文本。 + +## 影响范围 +- 影响文件:`install.sh`、`x-ui.sh`、`tests/mariadb_install_switch_test.sh`。 +- 不影响数据库结构、接口协议、构建流程。 +- 仅影响交互式输入阶段的参数合法性检查。 + +## 验证情况 +- 执行 `bash -n install.sh`,通过。 +- 执行 `bash -n x-ui.sh`,通过。 +- 执行 `bash tests/mariadb_install_switch_test.sh`,通过。 + +## 风险与后续 +- 当前风险较低,变更仅限输入校验逻辑。 +- 后续可考虑将端口校验抽为统一函数,减少重复逻辑。 diff --git a/docs/Tasktracking/2026-04-23-fix-mariadb-restart-diagnostics.md b/docs/Tasktracking/2026-04-23-fix-mariadb-restart-diagnostics.md new file mode 100644 index 00000000..dad4af62 --- /dev/null +++ b/docs/Tasktracking/2026-04-23-fix-mariadb-restart-diagnostics.md @@ -0,0 +1,34 @@ +Task Record: Fix MariaDB restart error diagnostics + +Date: 2026-04-23 +Related Module: install.sh, x-ui.sh — MariaDB service management +Change Type: Fix + +Background + +用户在安装过程中选择本地 MariaDB 后,遇到 "重启 MariaDB 失败,请检查配置文件" 错误。该错误信息过于笼统,因为 `restart_mariadb_service()` 和 `start_mariadb_service()` 函数使用 `2>/dev/null` 抑制了 stderr,导致 systemctl 返回的实际错误信息被隐藏,无法定位根因。 + +Changes + +- `restart_mariadb_service()`(install.sh + x-ui.sh):移除 stderr 抑制,捕获 systemctl/rc-service 输出,失败时打印实际错误信息和 `systemctl status` 诊断输出 +- `start_mariadb_service()`(install.sh + x-ui.sh):systemctl start/enable 失败时使用 `|| true` 避免 set -e 场景下脚本意外退出,保持行为一致 + +Impact + +- install.sh: `restart_mariadb_service()` 和 `start_mariadb_service()` +- x-ui.sh: `restart_mariadb_service()` 和 `start_mariadb_service()` +- 不影响功能逻辑,仅改善错误诊断输出 +- 无 API、数据库、配置变更 + +Verification + +- `bash -n install.sh` — syntax OK +- `bash -n x-ui.sh` — syntax OK +- `bash tests/mariadb_install_switch_test.sh` — PASS +- `bash tests/mariadb_admin_empty_password_test.sh` — PASS +- `bash tests/install_uninstall_resilience_test.sh` — PASS + +Risks And Follow-Up + +- 无风险。改动仅影响错误输出,不改变控制流 +- 用户重新运行安装脚本后,应能看到 systemctl 实际报错原因(如配置文件语法错误、端口冲突等),可据此进一步定位 diff --git a/docs/Tasktracking/2026-04-23-fix-stale-mariadb-service-detection.md b/docs/Tasktracking/2026-04-23-fix-stale-mariadb-service-detection.md new file mode 100644 index 00000000..f4428006 --- /dev/null +++ b/docs/Tasktracking/2026-04-23-fix-stale-mariadb-service-detection.md @@ -0,0 +1,33 @@ +Task Record: Detect stale mariadb service file when server package is missing + +Date: 2026-04-23 +Related Module: install.sh, x-ui.sh — has_local_mariadb_service() +Change Type: Fix + +Background + +用户在安装过程中 MariaDB 重启失败。排查发现 systemd unit 文件存在于 `systemctl list-unit-files` 输出中,但 `mariadb-server` 包实际已被卸载,服务文件是残留状态。`has_local_mariadb_service()` 只检查了 unit 文件是否存在,未验证包是否已安装,导致跳过了服务器重新安装。 + +Changes + +- `has_local_mariadb_service()`(install.sh + x-ui.sh):在检测到 unit 文件后,追加 `dpkg -s mariadb-server`(Debian/Ubuntu)或 `rpm -q mariadb-server`(RHEL/Fedora)验证包是否已安装。包不存在时返回 1,触发重新安装。 + +Impact + +- install.sh: `has_local_mariadb_service()` +- x-ui.sh: `has_local_mariadb_service()` +- 不影响正常安装流程,仅在包被卸载但 unit 文件残留时触发重新安装 +- 无 API、数据库、配置变更 + +Verification + +- `bash -n install.sh` — syntax OK +- `bash -n x-ui.sh` — syntax OK +- `bash tests/mariadb_install_switch_test.sh` — PASS +- `bash tests/mariadb_admin_empty_password_test.sh` — PASS +- `bash tests/install_uninstall_resilience_test.sh` — PASS + +Risks And Follow-Up + +- 无风险。仅增加包安装状态检查,不影响已有逻辑 +- Arch/Alpine 等非 dpkg/rpm 发行版保持原行为(仅检查 unit 文件) diff --git a/docs/Tasktracking/2026-04-23-install-cron-before-acme.md b/docs/Tasktracking/2026-04-23-install-cron-before-acme.md new file mode 100644 index 00000000..de1804d7 --- /dev/null +++ b/docs/Tasktracking/2026-04-23-install-cron-before-acme.md @@ -0,0 +1,35 @@ +Task Record: Install cron before acme.sh for all distros + +Date: 2026-04-23 +Related Module: install.sh — cron 安装 +Change Type: Fix + +Background + +acme.sh 依赖 cron 来执行证书自动续期,但在部分发行版(RHEL/Fedora/CentOS/Arch/openSUSE/Alpine)上,cron 服务可能未预装。acme.sh 安装时如果找不到 cron,会静默失败或报错,导致证书续期不生效。 + +Changes + +- `install.sh`: + - 在 `install_base()` 中新增 cron 包安装逻辑 + - RHEL/Fedora/CentOS/Arch/openSUSE: 安装 `cronie` 包 + - Alpine: 安装 `dcron` 包 + - 安装后确保 crond 服务启用并启动(`enable --now`) + - 将 cron 安装移到 acme.sh 安装之前,确保依赖顺序正确 + +Impact + +- `install.sh`: `install_base()` 函数 +- 不影响已有安装流程,仅在 cron 未安装时补充安装 +- 不影响数据库、API、前端 + +Verification + +- `bash -n install.sh` — syntax OK +- 在 Ubuntu/Debian 上验证(cron 通常已预装,无副作用) +- 需要在 RHEL/Alpine 等发行版上验证 cron 安装逻辑 + +Risks And Follow-Up + +- 无风险。仅增加缺失包的安装,不影响已有逻辑 +- 如果用户手动禁用了 cron,证书续期仍会失败(非本次修复范围) diff --git a/docs/Tasktracking/2026-04-24-fix-traffic-flush-blocked-by-stale-delta.md b/docs/Tasktracking/2026-04-24-fix-traffic-flush-blocked-by-stale-delta.md new file mode 100644 index 00000000..98e75197 --- /dev/null +++ b/docs/Tasktracking/2026-04-24-fix-traffic-flush-blocked-by-stale-delta.md @@ -0,0 +1,42 @@ +Task Record: Resolve shared-mode traffic flush blocked by stale inboundId=0 delta + +Date: 2026-04-24 +Related Module: web/service/traffic_flush.go, web/web.go, web/job/xray_traffic_job.go — 流量刷盘 +Change Type: Fix + +Background + +共享模式下流量统计始终为 0,MariaDB 的 `client_traffics` 表从未被写入。排查发现 `traffic-pending.json` 中存在一个残留的 `inboundId: 0` 客户端流量 delta(在 InboundId 解析修复前产生)。`flushToDatabase()` 尝试将其写入 `client_traffics` 时,违反外键约束 `fk_inbounds_client_stats`(`inbounds` 表不存在 `id=0`),导致整个事务回滚,所有流量永远无法写入。 + +此外,`NewXrayTrafficJob()` 和 `startTrafficFlushLoop()` 各自创建了独立的 `TrafficPendingStore` 实例,指向同一个 `traffic-pending.json` 文件但使用独立的 `sync.Mutex`,存在数据竞争风险。 + +Changes + +- `web/service/traffic_flush.go`: + - `flushToDatabase()` 循环开头新增 `InboundID == 0` 检查,跳过无效 delta 并记录 warning 日志 +- `web/job/xray_traffic_job.go`: + - `NewXrayTrafficJob()` 改为接受 `*service.TrafficPendingStore` 参数,不再自行创建 store + - 移除 `config` 包依赖 +- `web/web.go`: + - `Server` struct 新增 `trafficStore *service.TrafficPendingStore` 字段 + - `Start()` 中统一创建一个 `TrafficPendingStore` 实例 + - `startTask()` 和 `startTrafficFlushLoop()` 共享同一个 store 实例,消除双实例竞争 +- `web/service/traffic_flush_test.go`: + - 新增 `TestFlushOnceSkipsZeroInboundIdDelta` 测试 + +Impact + +- `web/service/traffic_flush.go`: flushToDatabase() 跳过无效 delta +- `web/web.go`: Server 启动流程变更,store 统一创建 +- `web/job/xray_traffic_job.go`: 构造函数签名变更 +- 修复后需要删除残留的 `traffic-pending.json` 文件才能生效 + +Verification + +- `go test ./web/service/ -run TestTraffic -v` — PASS +- `go test ./web/service/ -run TestFlushOnceSkipsZeroInboundIdDelta -v` — PASS + +Risks And Follow-Up + +- 部署时必须删除 `/etc/x-ui/traffic-pending.json`,否则残留的 `inboundId: 0` delta 仍会被跳过(不影响功能,但会产生 warning 日志) +- `TrafficPendingStore` 的文件级锁已通过共享实例解决,但如果未来有多个进程访问同一文件,仍需考虑进程级锁 diff --git a/docs/Tasktracking/2026-04-24-resolve-client-traffic-inboundid.md b/docs/Tasktracking/2026-04-24-resolve-client-traffic-inboundid.md new file mode 100644 index 00000000..e383de88 --- /dev/null +++ b/docs/Tasktracking/2026-04-24-resolve-client-traffic-inboundid.md @@ -0,0 +1,39 @@ +Task Record: Resolve client traffic InboundId from DB in shared mode + +Date: 2026-04-24 +Related Module: web/service/traffic_flush.go, web/job/xray_traffic_job.go — 共享模式流量收集 +Change Type: Fix + +Background + +共享模式(MariaDB 多节点)下,Xray gRPC Stats API 返回的客户端流量只包含 email,不包含 InboundId(始终为 0)。`Collect()` 函数直接使用了这个 `InboundId: 0`,导致流量无法正确关联到 inbound,写入数据库时违反外键约束或写入错误的 inbound。 + +Changes + +- `web/service/traffic_flush.go`: + - `Collect()` 新增 `emailToInboundID` 映射:在处理客户端流量前,先从 `client_traffics` 表查询所有 email 对应的 `inbound_id` + - 用查询到的真实 `InboundId` 替换 Xray API 返回的 `InboundId: 0` + - 未知 email(数据库中无对应记录)跳过并记录 warning 日志 + - 新增测试用例:`TestCollectResolvesInboundIdFromDB`、`TestCollectSkipsUnknownEmail`、`TestCollectClampsNegativeResidualAndLogsDetailedWarning` +- `web/job/xray_traffic_job.go`: + - 共享模式下跳过 `addClientTraffic()`(因为 `Collect()` 已处理),改为手动计算并设置在线客户端列表 +- `web/service/inbound.go`: + - 新增 `SetOnlineClients()` 和 `GetOnlineClients()` 方法,供共享模式设置在线状态 +- `x-ui.sh`: + - 节点配置菜单增加 `trafficFlushInterval` 输入提示 + +Impact + +- `web/service/traffic_flush.go`: Collect() 逻辑变更,影响所有共享模式节点的流量收集 +- `web/job/xray_traffic_job.go`: 共享模式的在线客户端检测逻辑 +- 不影响非共享模式(SQLite/单节点 MariaDB) + +Verification + +- `go test ./web/service/ -run TestCollect -v` — PASS +- `go test ./web/service/ -run TestTraffic -v` — PASS + +Risks And Follow-Up + +- 如果 `client_traffics` 表为空(首次部署),所有客户端流量都会被跳过,直到第一个 inbound 被创建并产生 `client_traffics` 行 +- 旧的 `inboundId: 0` 残留 delta 仍可能存在于 `traffic-pending.json` 中(后续 commit 修复)