3x-ui/docs/install-logic.md
Sora39831 b4047cee54 feat: allow same email across multiple inbounds and auto-add clients on registration
Remove global unique constraint on client_traffics.email, change email
duplication check to per-inbound scope, and automatically register new
users as disabled clients in all existing inbounds within a transaction.
2026-04-03 01:38:31 +08:00

485 lines
15 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.

# install.sh 逻辑文档
## 概述
`install.sh` 是 3x-ui 面板的安装脚本,负责在 Linux 服务器上完成以下工作:
1. 安装系统依赖包
2. 下载并解压 3x-ui 发行版
3. 配置 systemd / OpenRC 服务
4. 生成随机凭据用户名、密码、端口、Web 路径)
5. 配置 SSL 证书Let's Encrypt 域名证书、IP 证书、或自定义证书)
6. 显示安装结果和访问信息
---
## 全局配置
### 颜色变量
| 变量 | 值 | 用途 |
|---------|----------------|------------|
| `red` | `\033[0;31m` | 红色文本 |
| `green` | `\033[0;32m` | 绿色文本 |
| `blue` | `\033[0;34m` | 蓝色文本 |
| `yellow`| `\033[0;33m` | 黄色文本 |
| `plain` | `\033[0m` | 重置颜色 |
### 路径变量
| 变量 | 默认值 | 说明 |
|-----------------|-------------------------|--------------------------|
| `xui_folder` | `/usr/local/x-ui` | x-ui 安装目录 |
| `xui_service` | `/etc/systemd/system` | systemd 服务文件目录 |
可通过环境变量 `XUI_MAIN_FOLDER``XUI_SERVICE` 覆盖。
---
## 入口流程
```
install.sh 被执行
├─ 检查 root 权限
├─ 检测操作系统发行版
├─ 检测 CPU 架构
├─ install_base() ← 安装系统依赖
└─ install_x-ui($1) ← 主安装逻辑($1 为可选的版本号)
```
---
## 函数详解
### 1. root 权限检查(第 14-15 行)
检查 `$EUID` 是否为 0。非 root 用户直接退出并提示使用 root 权限。
### 2. 操作系统检测(第 17-28 行)
读取 `/etc/os-release``/usr/lib/os-release`,将 `$ID` 赋值给 `release` 变量。
支持的发行版:
| 包管理器 | 发行版 |
|----------|--------|
| `apt` | ubuntu, debian, armbian |
| `dnf` | fedora, amzn, virtuozzo, rhel, almalinux, rocky, ol |
| `yum` | centos 7 |
| `pacman` | arch, manjaro, parch |
| `zypper` | opensuse-tumbleweed, opensuse-leap |
| `apk` | alpine |
### 3. `arch()` — CPU 架构检测(第 30-41 行)
通过 `uname -m` 映射到标准架构标识:
| `uname -m` 输出 | 返回值 |
|------------------------|----------|
| x86_64, x64, amd64 | `amd64` |
| i*86, x86 | `386` |
| armv8*, arm64, aarch64 | `arm64` |
| armv7*, arm | `armv7` |
| armv6* | `armv6` |
| armv5* | `armv5` |
| s390x | `s390x` |
| 其他 | 退出报错 |
### 4. IP/域名验证函数(第 46-57 行)
| 函数 | 逻辑 |
|---------------|---------------------------------------------------|
| `is_ipv4()` | 正则匹配 `数字.数字.数字.数字` 格式 |
| `is_ipv6()` | 检查字符串是否包含 `:` |
| `is_ip()` | 调用 `is_ipv4``is_ipv6` |
| `is_domain()` | 正则匹配标准域名格式(含国际化域名 `xn--` 支持) |
### 5. `is_port_in_use()` — 端口占用检测(第 60-74 行)
按优先级尝试三种方式:
1. `ss -ltn` — 检查监听端口
2. `netstat -lnt` — 回退方案
3. `lsof -nP -iTCP:端口 -sTCP:LISTEN` — 最后手段
任一命中即返回 0端口被占用
### 6. `install_base()` — 安装基础依赖(第 76-104 行)
根据 `$release` 使用对应的包管理器安装以下公共依赖:
```
curl, tar, tzdata, socat, ca-certificates, openssl
```
额外安装 `cron`(用于 acme.sh 自动续期,仅 apt 系列)。
- CentOS 7 使用 `yum`,其他版本使用 `dnf`
- 未识别的发行版默认回退到 `apt-get`
### 7. `gen_random_string(length)` — 随机字符串生成(第 106-111 行)
```
openssl rand -base64(length*2) → 过滤 a-zA-Z0-9 → 截取前 length 个字符
```
用于生成用户名、密码、Web 路径等随机值。
### 8. `install_acme()` — 安装 acme.sh第 113-124 行)
```bash
curl -s https://get.acme.sh | sh
```
安装到 `~/.acme.sh/` 目录。失败返回 1。
---
## SSL 证书管理
### 9. `setup_ssl_certificate(domain, server_ip, port, webBasePath)` — 域名 SSL第 126-191 行)
**用途**:为域名签发 Let's Encrypt 证书。
**流程**
```
检查 acme.sh 是否已安装
├─ 未安装 → 调用 install_acme()
└─ 已安装 → 继续
创建证书目录:/root/cert/${domain}/
签发证书:
acme.sh --set-default-ca --server letsencrypt
acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80
↳ 失败 → 清理并返回 1
安装证书:
acme.sh --installcert
--key-file /root/cert/${domain}/privkey.pem
--fullchain-file /root/cert/${domain}/fullchain.pem
--reloadcmd "systemctl restart x-ui"
启用自动续期acme.sh --upgrade --auto-upgrade
设置文件权限:
privkey.pem → 600仅所有者可读
fullchain.pem → 644
配置面板证书路径:
x-ui cert -webCert fullchain.pem -webCertKey privkey.pem
```
**前提条件**80 端口必须可从外网访问。
### 10. `setup_ip_certificate(ipv4, ipv6)` — IP 证书(第 195-343 行)
**用途**:为 IP 地址签发 Let's Encrypt 短期证书(约 6 天有效期)。
**流程**
```
检查 acme.sh
验证 IPv4 地址格式
创建证书目录:/root/cert/ip/
选择 HTTP-01 监听端口:
└─ 默认 80用户可自定义
└─ 循环检测端口占用,被占用则提示换端口
签发证书:
acme.sh --issue
-d ${ipv4} [-d ${ipv6}]
--standalone
--server letsencrypt
--certificate-profile shortlived
--days 6
--httpport ${WebPort}
安装证书:
acme.sh --installcert
--key-file /root/cert/ip/privkey.pem
--fullchain-file /root/cert/ip/fullchain.pem
--reloadcmd "systemctl restart x-ui || rc-service x-ui restart"
↳ 通过检查文件是否存在(而非退出码)判断成功
启用自动续期
设置文件权限
配置面板证书路径
```
**关键特性**
- 使用 `--certificate-profile shortlived` 配置文件,证书有效期约 6 天
- acme.sh cron 任务会在到期前自动续期
- 不依赖退出码判断安装成功(因为 reloadcmd 失败会导致非零退出)
- 支持 IPv4 + IPv6 双栈
### 11. `ssl_cert_issue()` — 手动 SSL 证书签发(第 346-509 行)
**用途**:交互式域名证书签发,提供更多自定义选项。
**流程**
```
读取当前面板的 webBasePath 和 port
检查 acme.sh不存在则安装
获取并验证用户输入的域名:
└─ 循环直到输入有效域名
└─ 检查是否已存在该域名的证书
创建证书目录:/root/cert/${domain}/
选择端口(默认 80
临时停止面板(释放端口)
签发证书:
acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort}
设置 reloadcmd证书续期后执行的命令
├─ 默认systemctl restart x-ui || rc-service x-ui restart
├─ 选项 1systemctl reload nginx ; systemctl restart x-ui
├─ 选项 2自定义命令
└─ 选项 0保持默认
安装证书并启用自动续期
启动面板
询问是否将证书应用到面板:
└─ 是 → x-ui cert -webCert ... -webCertKey ...
└─ 否 → 跳过
```
**特点**
- 签发前会停止面板以释放端口
- 支持自定义 reloadcmd例如先 reload nginx 再重启 x-ui
- 签发失败会自动重新启动面板
### 12. `prompt_and_setup_ssl(panel_port, web_base_path, server_ip)` — SSL 选择菜单(第 513-638 行)
**用途**:安装时的统一 SSL 配置入口,提供三种选择。
**菜单**
```
1. Let's Encrypt 域名证书90 天有效期,自动续期)
└─ 调用 ssl_cert_issue()
└─ 从 acme.sh 列表提取域名作为 SSL_HOST
2. Let's Encrypt IP 证书6 天有效期,自动续期) ← 默认选项
└─ 可选输入 IPv6 地址
└─ 停止面板释放 80 端口
└─ 调用 setup_ip_certificate(server_ip, ipv6)
└─ SSL_HOST = server_ip
3. 自定义 SSL 证书(指定已有文件路径)
└─ 输入域名
└─ 循环验证证书文件(存在、可读、非空)
└─ 循环验证私钥文件(存在、可读、非空)
└─ x-ui cert -webCert ... -webCertKey ...
└─ 提示用户自行管理续期
```
**全局变量**:设置 `SSL_HOST` 供后续显示访问地址使用。
---
## 安装后配置
### 13. `config_after_install()` — 安装后配置(第 640-760 行)
**用途**首次安装后的凭据生成、端口设置、Web 路径生成、SSL 配置。
**流程图**
```
读取当前面板设置:
- hasDefaultCredential是否为默认凭据
- webBasePath
- port
- cert证书路径
获取服务器公网 IP
└─ 依次尝试 6 个 API
1. api4.ipify.org
2. ipv4.icanhazip.com
3. v4.api.ipinfo.io/ip
4. ipv4.myexternalip.com/raw
5. 4.ident.me
6. check-host.net/ip
判断 webBasePath 是否足够长≥4 字符):
┌─ webBasePath 过短
│ ├─ hasDefaultCredential == true首次安装
│ │ ├─ 生成随机 webBasePath18 位)
│ │ ├─ 生成随机用户名10 位)
│ │ ├─ 生成随机密码10 位)
│ │ ├─ 询问是否自定义端口
│ │ │ ├─ 是 → 用户输入端口
│ │ │ └─ 否 → 随机生成 1024-62000 范围端口
│ │ ├─ 应用设置x-ui setting -username ... -password ... -port ... -webBasePath ...
│ │ ├─ prompt_and_setup_ssl() ← 必需
│ │ └─ 显示完整凭据和访问地址
│ │
│ └─ hasDefaultCredential != true非首次安装
│ ├─ 生成新 webBasePath
│ ├─ 检查是否有证书:
│ │ ├─ 无 → prompt_and_setup_ssl()(推荐)
│ │ └─ 有 → 显示 HTTP 访问地址
│ └─ 结束
└─ webBasePath 正常≥4 字符)
├─ hasDefaultCredential == true
│ ├─ 生成随机用户名和密码
│ ├─ 应用新凭据
│ └─ 显示凭据
└─ hasDefaultCredential != true
└─ 提示凭据已正确设置
再次检查证书:
├─ 无证书 → prompt_and_setup_ssl()(推荐)
└─ 有证书 → 跳过
最后执行x-ui migrate数据库迁移
```
---
## 主安装逻辑
### 14. `install_x-ui(version)` — 主安装函数(第 762-958 行)
**参数**`$1` 可选,指定安装版本号(如 `v2.3.5`)。
**流程**
```
cd /usr/local/
┌─ 无版本参数(安装最新版)
│ ├─ 从 GitHub API 获取最新版本号
│ │ └─ IPv4 失败时重试 curl -4
│ └─ 下载x-ui-linux-${arch}.tar.gz
└─ 有版本参数
├─ 验证版本号 ≥ v2.3.5
└─ 下载指定版本
同时下载 x-ui.sh 到 /usr/bin/x-ui-temp
停止已有 x-ui 服务并删除旧安装目录
解压 tar.gz设置执行权限
ARM 架构特殊处理:
armv5/armv6/armv7 → 重命名为 xray-linux-arm
安装 x-ui.sh 到 /usr/bin/x-ui
创建日志目录 /var/log/x-ui/
调用 config_after_install() ← 生成凭据 + SSL
etckeeper 兼容:
└─ 如果 /etc/.git 存在,将 x-ui.db 加入 .gitignore
┌─ Alpine Linux
│ ├─ 下载 OpenRC 脚本 x-ui.rc → /etc/init.d/x-ui
│ ├─ rc-update add x-ui启用开机自启
│ └─ rc-service x-ui start
└─ 其他系统systemd
├─ 优先使用 tar.gz 中的服务文件
│ ├─ x-ui.service ← 通用
│ ├─ x-ui.service.debian ← Ubuntu/Debian
│ ├─ x-ui.service.arch ← Arch/Manjaro
│ └─ x-ui.service.rhel ← 其他CentOS/Fedora 等)
├─ 如果 tar.gz 中没有,从 GitHub 下载对应文件
└─ 配置服务:
chown root:root x-ui.service
chmod 644 x-ui.service
systemctl daemon-reload
systemctl enable x-ui
systemctl start x-ui
显示安装完成信息和子命令用法
```
**子命令列表**(安装完成后显示):
| 命令 | 功能 |
|-------------------|--------------------|
| `x-ui` | 打开管理菜单 |
| `x-ui start` | 启动面板 |
| `x-ui stop` | 停止面板 |
| `x-ui restart` | 重启面板 |
| `x-ui status` | 查看状态 |
| `x-ui settings` | 查看当前设置 |
| `x-ui enable` | 设置开机自启 |
| `x-ui disable` | 取消开机自启 |
| `x-ui log` | 查看日志 |
| `x-ui banlog` | 查看 Fail2ban 日志 |
| `x-ui update` | 更新 |
| `x-ui legacy` | 安装旧版本 |
| `x-ui install` | 安装 |
| `x-ui uninstall` | 卸载 |
---
## 调用关系总结
```
install.sh
├─ install_base()
│ └─ 根据发行版安装 curl, tar, tzdata, socat, ca-certificates, openssl
└─ install_x-ui($1)
├─ 下载 x-ui 发行版和 x-ui.sh
├─ 解压、设置权限
├─ config_after_install()
│ ├─ gen_random_string() × 3用户名/密码/Web路径
│ ├─ 获取公网 IP
│ ├─ prompt_and_setup_ssl()
│ │ ├─ [选项1] ssl_cert_issue()
│ │ │ ├─ install_acme()
│ │ │ └─ acme.sh 签发/安装/续期域名证书
│ │ ├─ [选项2] setup_ip_certificate()
│ │ │ ├─ install_acme()
│ │ │ └─ acme.sh 签发/安装/续期 IP 短期证书
│ │ └─ [选项3] 用户提供自定义证书路径
│ └─ x-ui migrate
└─ 配置系统服务systemd 或 OpenRC
```
---
## 关键设计决策
1. **强制 SSL**:首次安装时必须配置 SSL 证书(三种方式选一),确保面板通过 HTTPS 访问。
2. **随机化安全**用户名、密码、端口、Web 路径全部随机生成,避免使用默认凭据。
3. **多 OS 兼容**:通过 `case` 语句适配 7 大包管理器体系Alpine 使用 OpenRC其余使用 systemd。
4. **IP 证书支持**:利用 Let's Encrypt 的 shortlived profile为无域名场景提供 SSL 支持6 天有效期,自动续期)。
5. **优雅降级**
- GitHub API 失败时用 `curl -4` 重试
- `ss` 不可用时回退到 `netstat`,再回退到 `lsof`
- tar.gz 中无服务文件时从 GitHub 下载
- acme.sh reloadcmd 失败不阻止证书安装
6. **etckeeper 兼容**:自动将数据库文件加入 `/etc/.gitignore`,避免 etckeeper 追踪频繁变化的数据库。