3x-ui/docs/x-ui-logic.md
root f5862abc2e feat: add CodeMirror YAML editor for Clash template and fix settings save button bug
- Replace plain textarea with CodeMirror editor (YAML syntax highlighting, line numbers, auto-indent) for Clash subscription template
- Fix confAlerts crash when subClashURI/subURI/subJsonURI is null/undefined (prevented save button from enabling)
- Add yaml.js CodeMirror mode asset
- Include docs and .gitignore cleanup
2026-04-24 16:15:22 +08:00

937 lines
27 KiB
Markdown
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.

# x-ui.sh 逻辑文档
## 概述
`x-ui.sh` 是 3x-ui 面板的管理脚本,提供 26 个交互式菜单选项和 15 个子命令涵盖面板的安装、更新、卸载、凭据管理、服务控制、SSL 证书、防火墙、Fail2ban IP 限制、BBR 加速、Geo 文件更新等功能。
---
## 全局配置
### 颜色变量
| 变量 | 值 | 用途 |
|---------|----------------|----------|
| `red` | `\033[0;31m` | 红色 |
| `green` | `\033[0;32m` | 绿色 |
| `blue` | `\033[0;34m` | 蓝色 |
| `yellow`| `\033[0;33m` | 黄色 |
| `plain` | `\033[0m` | 重置 |
### 日志函数
| 函数 | 前缀 | 用途 |
|---------|-----------|------------|
| `LOGD()` | `[调试]` | 调试信息 |
| `LOGE()` | `[错误]` | 错误信息 |
| `LOGI()` | `[信息]` | 普通信息 |
### 路径变量
| 变量 | 默认值 | 说明 |
|-------------------------|---------------------------|-------------------------|
| `xui_folder` | `/usr/local/x-ui` | x-ui 安装目录 |
| `xui_service` | `/etc/systemd/system` | systemd 服务文件目录 |
| `log_folder` | `/var/log/x-ui` | 日志目录 |
| `iplimit_log_path` | `.../3xipl.log` | IP 限制日志 |
| `iplimit_banned_log_path`| `.../3xipl-banned.log` | IP 封禁日志 |
### 辅助函数
| 函数 | 功能 |
|-----------------------|----------------------------------------------|
| `confirm()` | 通用确认提示,支持自定义默认值 |
| `confirm_restart()` | 确认后重启面板(重启 x-ui 也会重启 xray |
| `before_show_menu()` | 按回车返回主菜单 |
| `gen_random_string()` | 通过 openssl 生成指定长度的随机字母数字字符串 |
| `is_port_in_use()` | 端口占用检测ss → netstat → lsof |
| `is_ipv4/is_ipv6/is_ip/is_domain()` | IP/域名格式验证 |
---
## 入口流程
```
x-ui.sh 被执行
├─ 检查 root 权限
├─ 检测操作系统发行版和版本号
├─ 初始化路径和日志目录
├─ 有命令行参数 → 执行对应子命令(不显示菜单)
└─ 无参数 → 显示交互式菜单 show_menu()
├─ 显示当前状态(运行/停止/未安装 + 开机自启 + xray 状态)
├─ 读取用户输入 [0-26]
└─ 根据选择调用对应功能
```
---
## 主菜单 (show_menu)
```
╔────────────────────────────────────────────────╗
│ 0. 退出脚本 │
│────────────────────────────────────────────────│
│ 1. 安装 2. 更新 3. 更新菜单 │
│ 4. 安装旧版本 5. 卸载 │
│────────────────────────────────────────────────│
│ 6. 重置用户名和密码 7. 重置 Web 路径 │
│ 8. 重置设置 9. 修改端口 │
│ 10. 查看当前设置 │
│────────────────────────────────────────────────│
│ 11. 启动 12. 停止 13. 重启 │
│ 14. 重启 Xray 15. 查看状态 │
│ 16. 日志管理 │
│────────────────────────────────────────────────│
│ 17. 设置开机自启 18. 取消开机自启 │
│────────────────────────────────────────────────│
│ 19. SSL 证书管理 20. Cloudflare SSL │
│ 21. IP 限制管理 22. 防火墙管理 │
│ 23. SSH 端口转发管理 │
│────────────────────────────────────────────────│
│ 24. BBR 管理 25. 更新 Geo 文件 │
│ 26. 网速测试 (Speedtest) │
╚────────────────────────────────────────────────╝
```
大部分选项在执行前调用 `check_install`(检查面板是否已安装)或 `check_uninstall`(检查面板是否未安装),防止误操作。
---
## 状态检测函数
| 函数 | 返回值 | 逻辑 |
|------------------------|---------------------------|-------------------------------------------|
| `check_status()` | 0=运行中, 1=未运行, 2=未安装 | Alpine 检查 init.d其他检查 systemd |
| `check_enabled()` | 0=已启用, 1=未启用 | Alpine 检查 rc-update其他检查 systemctl |
| `check_xray_status()` | 0=运行中, 1=未运行 | ps 查找 xray-linux 进程 |
| `check_install()` | 前置检查 | 未安装则提示并返回菜单 |
| `check_uninstall()` | 前置检查 | 已安装则提示"勿重复安装"并返回菜单 |
---
## 菜单选项详解
### 选项 0退出脚本
```bash
exit 0
```
直接退出,无额外逻辑。
---
### 选项 1安装
**函数**`install()`
```
下载并执行 install.sh从 GitHub raw 文件)
└─ 成功后自动调用 start()
```
- 执行 `bash <(curl -Ls https://raw.githubusercontent.com/Sora39831/3x-ui/main/install.sh)`
- 安装成功后自动启动面板
---
### 选项 2更新
**函数**`update()`
```
确认提示:"更新所有 x-ui 组件到最新版本,数据不会丢失"
├─ 取消 → 返回菜单
└─ 确认 → 执行 update.sh从 GitHub 下载)
└─ 成功 → "更新完成,面板已自动重启"
```
---
### 选项 3更新菜单
**函数**`update_menu()`
```
确认提示
└─ 确认 → 下载最新 x-ui.sh 到 /usr/bin/x-ui
└─ 成功 → "更新成功" 并 exit 0
```
仅更新管理脚本自身,不影响面板程序。
---
### 选项 4安装旧版本
**函数**`legacy_version()`
```
提示用户输入版本号(如 2.4.0
├─ 空 → 退出
└─ 有效 → 执行对应版本的 install.sh传入版本参数
```
- 下载指定 tag 的 install.sh`v$tag_version/install.sh`
- 传入参数 `v$tag_version` 进行安装
- install.sh 内部会验证版本 ≥ v2.3.5
---
### 选项 5卸载
**函数**`uninstall()`
```
确认:"卸载面板xray 也会被卸载!"(默认 n
├─ 取消 → 返回菜单
└─ 确认 →
Alpine: rc-service stop → rc-update del → rm init.d
其他: systemctl stop → disable → rm service → daemon-reload → reset-failed
删除 /etc/x-ui/ 和 ${xui_folder}/
显示重装命令
删除脚本自身trap SIGTERM → rm $0
```
---
### 选项 6重置用户名和密码
**函数**`reset_user()`
```
确认提示(默认 n
└─ 确认 →
输入用户名(默认随机 10 位)
输入密码(默认随机 18 位)
询问是否禁用双因素认证
├─ 是 → -resetTwoFactor true
└─ 否 → -resetTwoFactor false
应用设置x-ui setting -username ... -password ...
确认后重启面板
```
---
### 选项 7重置 Web 路径
**函数**`reset_webbasepath()`
```
确认提示
└─ 确认 → 生成随机 18 位字符串
应用x-ui setting -webBasePath ...
重启面板
```
---
### 选项 8重置设置
**函数**`reset_config()`
```
确认:"重置所有面板设置?账户数据不会丢失,用户名和密码不会改变"(默认 n
└─ 确认 → x-ui setting -reset
重启面板
```
仅重置面板配置,不影响账户数据库。
---
### 选项 9修改端口
**函数**`set_port()`
```
输入端口号 [1-65535]
├─ 空 → 取消
└─ 有效 → x-ui setting -port ${port}
确认后重启面板
```
---
### 选项 10查看当前设置
**函数**`check_config()`
```
获取面板设置x-ui setting -show true
获取公网 IPapi.ipify.org → 4.ident.me
检查是否有证书:
├─ 有证书 → 从证书路径提取域名,显示 https://域名:端口/路径
└─ 无证书 →
显示警告
询问是否为 IP 生成 SSL 证书
├─ 是 → 停止面板 → ssl_cert_issue_for_ip() → 启动面板
└─ 否 → 显示 http://IP:端口/路径,建议使用选项 19
```
---
### 选项 11启动
**函数**`start()`
```
检查当前状态
├─ 运行中 → "面板正在运行,无需重复启动"
└─ 未运行 →
Alpine: rc-service x-ui start
其他: systemctl start x-ui
等待 2 秒后再次检查状态
├─ 成功 → "x-ui 启动成功"
└─ 失败 → "面板启动失败,可能是因为启动时间超过两秒"
```
---
### 选项 12停止
**函数**`stop()`
```
检查当前状态
├─ 已停止 → "面板已停止,无需重复停止!"
└─ 运行中 →
Alpine: rc-service x-ui stop
其他: systemctl stop x-ui
等待 2 秒后检查状态
├─ 成功 → "x-ui 和 xray 已停止"
└─ 失败 → "面板停止失败"
```
---
### 选项 13重启
**函数**`restart()`
```
Alpine: rc-service x-ui restart
其他: systemctl restart x-ui
等待 2 秒后检查状态
├─ 成功 → "x-ui 和 xray 重启成功"
└─ 失败 → "面板重启失败"
```
---
### 选项 14重启 Xray
**函数**`restart_xray()`
```
systemctl reload x-ui ← 发送 reload 信号,不重启面板本身
"已发送重启信号,请查看日志确认"
等待 2 秒 → 显示 xray 运行状态
```
与选项 13 的区别:选项 13 重启整个 x-ui 服务,选项 14 仅重载 xray-core。
---
### 选项 15查看状态
**函数**`status()`
```
Alpine: rc-service x-ui status
其他: systemctl status x-ui -l
```
显示完整的 systemd/服务状态信息。
---
### 选项 16日志管理
**函数**`show_log()`
```
Alpine:
1. 调试日志 → grep 'x-ui[' /var/log/messages
0. 返回
其他 (systemd):
1. 调试日志 → journalctl -u x-ui -e --no-pager -f -p debug
2. 清除所有日志 → journalctl --rotate → --vacuum-time=1s → 重启面板
0. 返回
```
---
### 选项 17设置开机自启
**函数**`enable()`
```
Alpine: rc-update add x-ui default
其他: systemctl enable x-ui
```
---
### 选项 18取消开机自启
**函数**`disable()`
```
Alpine: rc-update del x-ui
其他: systemctl disable x-ui
```
---
### 选项 19SSL 证书管理
**函数**`ssl_cert_issue_main()` — 子菜单入口
#### 子菜单
```
1. 获取 SSL域名
2. 吊销证书
3. 强制续期
4. 查看已有域名
5. 为面板设置证书路径
6. 为 IP 地址获取 SSL6 天证书,自动续期)
0. 返回主菜单
```
#### 子选项 1获取 SSL域名证书
**函数**`ssl_cert_issue()`
```
检查/安装 acme.sh
按发行版安装 socat
获取并验证域名(循环直到有效)
检查是否已有该域名的证书acme.sh --list
创建证书目录 /root/cert/${domain}/
选择端口(默认 80
签发证书:
acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
↳ 失败 → 清理并退出
设置 reloadcmd
默认x-ui restart
可选systemctl reload nginx ; x-ui restart
可选:自定义命令
安装证书:
acme.sh --installcert
--key-file /root/cert/${domain}/privkey.pem
--fullchain-file /root/cert/${domain}/fullchain.pem
--reloadcmd ${reloadCmd}
启用自动续期acme.sh --upgrade --auto-upgrade
设置文件权限privkey.pem → 600, fullchain.pem → 644
询问是否为面板设置证书:
├─ 是 → x-ui cert -webCert ... -webCertKey ... → 重启
└─ 否 → 跳过
```
#### 子选项 2吊销证书
```
列出 /root/cert/ 下所有域名目录
选择域名 → acme.sh --revoke -d ${domain}
```
#### 子选项 3强制续期
```
列出所有域名
选择域名 → acme.sh --renew -d ${domain} --force
```
#### 子选项 4查看已有域名
```
遍历 /root/cert/ 下的域名目录
显示每个域名的 fullchain.pem 和 privkey.pem 路径
缺失文件的标记为"证书或密钥缺失"
```
#### 子选项 5为面板设置证书路径
```
列出所有域名
选择域名 → 验证文件存在
x-ui cert -webCert ... -webCertKey ...
重启面板
```
#### 子选项 6为 IP 地址获取 SSL
**函数**`ssl_cert_issue_for_ip()`
```
获取服务器公网 IPapi.ipify.org → 4.ident.me
询问是否包含 IPv6 地址
检查/安装 acme.sh
按发行版安装 socat
创建证书目录 /root/cert/ip/
构建域名参数:-d ${server_ip} [-d ${ipv6}]
选择 HTTP-01 监听端口(默认 80
└─ 循环检测端口占用,被占用则提示换端口
签发证书:
acme.sh --issue
-d ${server_ip} [-d ${ipv6}]
--standalone --server letsencrypt
--certificate-profile shortlived
--days 6 --httpport ${WebPort} --force
安装证书(不依赖退出码,通过检查文件判断成功)
启用自动续期
设置文件权限
为面板设置证书路径 → 显示 https://IP:端口/路径 → 重启面板
```
---
### 选项 20Cloudflare SSL 证书
**函数**`ssl_cert_issue_CF()`
```
显示使用说明(需要:邮箱、全局 API 密钥、域名)
确认提示
检查/安装 acme.sh
输入域名 (CF_Domain)
输入 API 密钥 (CF_GlobalKey)
输入注册邮箱 (CF_AccountEmail)
设置 CA 为 Let's Encrypt
导出环境变量CF_Key, CF_Email
签发通配符证书:
acme.sh --issue --dns dns_cf -d ${domain} -d *.${domain} --force
↳ 使用 Cloudflare DNS 验证
创建证书目录 /root/cert/${domain}/
设置 reloadcmd同域名证书流程
安装证书(含 *.${domain} 通配符)
启用自动续期
询问是否为面板设置证书 → 同域名证书流程
```
**特点**:支持通配符证书 `*.domain.com`,不需要开放 80 端口(使用 DNS 验证)。
---
### 选项 21IP 限制管理Fail2ban
**函数**`iplimit_main()` — 子菜单入口
#### 子菜单
```
1. 安装 Fail2ban 并配置 IP 限制
2. 修改封禁时长
3. 解封所有人
4. 封禁日志
5. 封禁指定 IP 地址
6. 解封指定 IP 地址
7. 实时日志
8. 服务状态
9. 重启服务
10. 卸载 Fail2ban 和 IP 限制
0. 返回主菜单
```
#### 子选项 1安装 Fail2ban
**函数**`install_iplimit()`
```
检查 Fail2ban 是否已安装
└─ 未安装 → 按发行版安装:
Ubuntu ≥ 24: 额外安装 python3-pip + pyasynchat
Debian ≥ 12: 额外安装 python3-systemd
CentOS 7: 先装 epel-release
清除 jail 配置冲突iplimit_remove_conflicts
创建日志文件3xipl.log, 3xipl-banned.log
创建 jail 配置create_iplimit_jails
启动并启用 Fail2ban 服务
```
**Jail 配置详情** (`create_iplimit_jails`)
```ini
# /etc/fail2ban/jail.d/3x-ipl.conf
[3x-ipl]
enabled=true
backend=auto
filter=3x-ipl
action=3x-ipl
logpath=/var/log/x-ui/3xipl.log
maxretry=2
findtime=32
bantime=30m # 默认 30 分钟,可通过子选项 2 修改
```
**过滤器**:匹配 `[LIMIT_IP] Email=... || Disconnecting OLD IP=... || Timestamp=...` 格式的日志行。
**动作**:使用 iptables 封禁/解封 IP同时写入封禁日志文件。
#### 子选项 2修改封禁时长
```
输入新的封禁时长(分钟)
重新生成 jail 配置 → 重启 Fail2ban
```
#### 子选项 3解封所有人
```
fail2ban-client reload --restart --unban 3x-ipl
清空封禁日志文件
```
#### 子选项 5/6手动封禁/解封 IP
```
输入 IP 地址 → 正则验证IPv4/IPv6
fail2ban-client set 3x-ipl banip/unbanip "$ip"
```
#### 子选项 10卸载
```
选项 1仅移除 IP 限制配置(保留 Fail2ban
删除 filter.d/3x-ipl.conf, action.d/3x-ipl.conf, jail.d/3x-ipl.conf
重启 Fail2ban
选项 2完全卸载
删除 /etc/fail2ban
停止服务
按发行版卸载 fail2ban 包 + autoremove
```
---
### 选项 22防火墙管理
**函数**`firewall_menu()` — 子菜单入口(基于 UFW
#### 子菜单
```
1. 安装防火墙
2. 端口列表 [带编号]
3. 开放端口
4. 删除列表中的端口
5. 启用防火墙
6. 禁用防火墙
7. 防火墙状态
0. 返回主菜单
```
#### 子选项 1安装防火墙
**函数**`install_firewall()`
```
检查 ufw 是否安装 → 未安装则 apt-get install ufw
检查防火墙是否激活 → 未激活则:
ufw allow ssh
ufw allow http
ufw allow https
ufw allow 2053/tcp ← webPort
ufw allow 2096/tcp ← subport
ufw --force enable
```
#### 子选项 3开放端口
**函数**`open_ports()`
```
输入端口(逗号分隔或范围,如 80,443,2053 或 400-500
验证输入格式
逐个处理:
范围 → ufw allow start:end/tcp + ufw allow start:end/udp
单端口 → ufw allow port
确认显示已开放的端口
```
#### 子选项 4删除端口
**函数**`delete_ports()`
```
显示当前规则ufw status numbered
选择删除方式:
1. 按规则编号删除 → ufw delete $number
2. 按端口号删除 → ufw delete allow $port
确认显示已删除的端口
```
**注意**:原始代码中选项 4 有一个已知 bug`firewall_wall_menu` 应为 `firewall_menu`),这会导致删除端口后不返回菜单。
---
### 选项 23SSH 端口转发管理
**函数**`SSH_port_forwarding()`
```
获取服务器公网 IP多 API 轮询)
读取当前面板设置:
- webBasePath, port, listenIP, cert, key
判断状态:
├─ 已有证书+密钥 → "面板已配置 SSL安全" → 返回
├─ 无证书且 listenIP 为空或 0.0.0.0 → "面板不安全" 警告
└─ listenIP 已设置且非 0.0.0.0 → 显示 SSH 转发命令
子菜单:
1. 设置监听 IP
├─ 默认 127.0.0.1 或自定义
├─ x-ui setting -listenIP ${ip}
└─ 显示 SSH 转发命令:
ssh -L 2222:${listenIP}:${port} root@${server_ip}
访问 http://localhost:2222${webBasePath}
2. 清除监听 IP
└─ x-ui setting -listenIP 0.0.0.0 → 重启
0. 返回
```
**用途**:将面板绑定到 127.0.0.1,只能通过 SSH 隧道访问,提高安全性。
---
### 选项 24BBR 管理
**函数**`bbr_menu()` — 子菜单入口
#### 子菜单
```
1. 启用 BBR
2. 禁用 BBR
0. 返回主菜单
```
#### 启用 BBR
**函数**`enable_bbr()`
```
检查是否已启用tcp_congestion_control == bbr 且 default_qdisc 为 fq/cake
├─ 已启用 → 直接返回
└─ 未启用 →
有 /etc/sysctl.d/ →
创建 /etc/sysctl.d/99-bbr-x-ui.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
注释 sysctl.conf 中的旧设置
sysctl --system
无 /etc/sysctl.d/ →
直接修改 /etc/sysctl.conf
sysctl -p
验证tcp_congestion_control == bbr → "BBR 已成功启用"
```
**特性**:启用前会备份当前设置(写入注释行 `#旧qdisc:旧拥塞控制`),以便禁用时恢复。
#### 禁用 BBR
**函数**`disable_bbr()`
```
检查是否已启用 → 未启用则返回
有 99-bbr-x-ui.conf →
读取备份的旧设置
恢复 net.core.default_qdisc 和 net.ipv4.tcp_congestion_control
删除配置文件
sysctl --system
无 99-bbr-x-ui.conf →
将 sysctl.conf 中的 fq→pfifo_fast, bbr→cubic
sysctl -p
验证tcp_congestion_control != bbr → "BBR 已成功替换为 CUBIC"
```
---
### 选项 25更新 Geo 文件
**函数**`update_geo()` — 子菜单入口
#### 子菜单
```
1. Loyalsoldier (geoip.dat, geosite.dat)
2. chocolate4u (geoip_IR.dat, geosite_IR.dat)
3. runetfreedom (geoip_RU.dat, geosite_RU.dat)
4. 全部更新
0. 返回主菜单
```
#### 数据源
| 选项 | 数据源 | 文件 | 用途 |
|------|---------------------------------------|------------------------------|------------------|
| 1 | Loyalsoldier/v2ray-rules-dat | geoip.dat, geosite.dat | 通用规则 |
| 2 | chocolate4u/Iran-v2ray-rules | geoip_IR.dat, geosite_IR.dat | 伊朗规则 |
| 3 | runetfreedom/russia-v2ray-rules-dat | geoip_RU.dat, geosite_RU.dat | 俄罗斯规则 |
| 4 | 以上全部 | 全部 6 个文件 | 一键更新 |
**下载逻辑** (`update_geofiles`)
```
每个文件:
curl -fLRo ${xui_folder}/bin/${dat}.dat
-z ${xui_folder}/bin/${dat}.dat ← 仅在远程更新时下载
https://github.com/${source}/releases/latest/download/${remote_file}.dat
```
`-z` 参数确保只有远程文件比本地新时才下载,节省带宽。
更新后自动重启面板以加载新规则。
---
### 选项 26网速测试 (Speedtest)
**函数**`run_speedtest()`
```
检查 speedtest 命令是否存在
└─ 不存在 →
有 snap → snap install speedtest
无 snap → 按包管理器安装:
dnf/yum → rpm 包源
apt-get/apt → deb 包源
curl 安装脚本 → 包管理器安装
执行 speedtest
```
---
## 子命令(命令行模式)
当脚本以参数调用时(如 `x-ui start`),跳过交互菜单直接执行:
| 子命令 | 对应菜单 | 附加行为 |
|------------------------|----------|-------------------------------|
| `start` | 11 | 执行后不返回菜单 |
| `stop` | 12 | 执行后不返回菜单 |
| `restart` | 13 | 执行后不返回菜单 |
| `restart-xray` | 14 | 执行后不返回菜单 |
| `status` | 15 | 执行后不返回菜单 |
| `settings` | 10 | 执行后不返回菜单 |
| `enable` | 17 | 执行后不返回菜单 |
| `disable` | 18 | 执行后不返回菜单 |
| `log` | 16 | 执行后不返回菜单 |
| `banlog` | 4(限制) | 执行后不返回菜单 |
| `update` | 2 | 执行后不返回菜单 |
| `legacy` | 4 | 执行后不返回菜单 |
| `install` | 1 | 使用 check_uninstall 前置检查 |
| `uninstall` | 5 | 执行后不返回菜单 |
| `update-all-geofiles` | 25-4 | 更新后自动重启 |
| 无效参数 | — | 显示用法帮助 |
所有子命令传递参数 `0` 给功能函数,使其执行后不调用 `before_show_menu()` 返回菜单。
---
## 调用关系总览
```
x-ui.sh
├─ show_menu()
│ ├─ show_status() → check_status() + show_enable_status() + show_xray_status()
│ ├─ 0: exit
│ ├─ 1: install() → install.sh → start()
│ ├─ 2: update() → update.sh
│ ├─ 3: update_menu() → 下载 x-ui.sh
│ ├─ 4: legacy_version() → install.sh v$version
│ ├─ 5: uninstall() → 停止服务 + 删除文件
│ ├─ 6: reset_user() → x-ui setting -username/-password
│ ├─ 7: reset_webbasepath() → x-ui setting -webBasePath
│ ├─ 8: reset_config() → x-ui setting -reset
│ ├─ 9: set_port() → x-ui setting -port
│ ├─ 10: check_config() → x-ui setting -show + ssl_cert_issue_for_ip()
│ ├─ 11: start() → systemctl/rc-service start
│ ├─ 12: stop() → systemctl/rc-service stop
│ ├─ 13: restart() → systemctl/rc-service restart
│ ├─ 14: restart_xray() → systemctl reload
│ ├─ 15: status() → systemctl/rc-service status
│ ├─ 16: show_log() → journalctl/grep messages
│ ├─ 17: enable() → systemctl/rc-update enable
│ ├─ 18: disable() → systemctl/rc-update disable
│ ├─ 19: ssl_cert_issue_main()
│ │ ├─ 1: ssl_cert_issue() → acme.sh 域名证书
│ │ ├─ 2: 吊销证书 → acme.sh --revoke
│ │ ├─ 3: 强制续期 → acme.sh --renew --force
│ │ ├─ 4: 查看已有域名
│ │ ├─ 5: 设置面板证书路径
│ │ └─ 6: ssl_cert_issue_for_ip() → acme.sh IP 短期证书
│ ├─ 20: ssl_cert_issue_CF() → acme.sh Cloudflare DNS 通配符证书
│ ├─ 21: iplimit_main()
│ │ ├─ 1: install_iplimit() → install fail2ban + create_iplimit_jails()
│ │ ├─ 2: 修改封禁时长
│ │ ├─ 3: 解封所有人
│ │ ├─ 4: show_banlog()
│ │ ├─ 5/6: 手动封禁/解封 IP
│ │ ├─ 7: tail -f fail2ban.log
│ │ ├─ 8/9: 服务状态/重启
│ │ └─ 10: remove_iplimit()
│ ├─ 22: firewall_menu() → UFW 防火墙管理
│ ├─ 23: SSH_port_forwarding() → 设置 listenIP 为 127.0.0.1
│ ├─ 24: bbr_menu() → enable_bbr() / disable_bbr()
│ ├─ 25: update_geo() → update_geofiles() → 下载 geoip/geosite .dat
│ └─ 26: run_speedtest() → speedtest
└─ 子命令模式($# > 0
└─ case $1 in "start"|"stop"|... → 对应函数 0
```
---
## 关键设计决策
1. **Alpine 兼容**:所有服务管理操作都区分 Alpine (OpenRC) 和其他系统 (systemd),通过 `$release` 变量判断。
2. **操作确认**:危险操作(卸载、重置凭据等)默认为 "n",防止误操作。安全操作(更新等)默认为 "y"。
3. **子命令模式**:支持 `x-ui start` 等非交互式调用,传递参数 `0` 抑制 `before_show_menu()` 的回车等待。
4. **状态前置检查**:大多数菜单选项先调用 `check_install``check_uninstall`,确保操作的前提条件满足。
5. **等待机制**start/stop/restart 后等待 2 秒再检查状态,给 systemd/init.d 足够时间完成操作。
6. **Geo 文件条件下载**:使用 `curl -z` 参数,仅在远程文件比本地新时才下载,节省带宽和时间。
7. **BBR 备份恢复**:启用 BBR 前将当前设置备份到注释行中,禁用时精确恢复原始值。
8. **Fail2ban jail 隔离**IP 限制使用独立的 `3x-ipl` jail与系统默认 jail 分离,互不影响。