feat: add xui-deploy skill with lessons learned
- SKILL.md v1.1: full deployment workflow for 3x-ui on VPS via SSH - Covers Docker/native install, Nginx+TLS, Xray inbound config - references/xray-inbound-config.md: VLESS+WS+TLS and Reality configs - references/lessons-learned.md: lessons from first real deployment - /app/x-ui binary vs shell wrapper in Docker - correct API path: panel/api/inbounds/add - subPath-only DB write (subURI causes blank settings page) - --network host port exposure workaround - Agent prompt and eval configs included
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
# x-ui Deploy — 经验沉淀
|
||||
|
||||
本文件记录在实际部署和使用过程中积累的问题解决方案、最佳配置和注意事项。
|
||||
每次遇到新问题或发现更好的配置时,由 agent 询问用户后追加到对应章节。
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与解决方案
|
||||
|
||||
<!-- 格式:
|
||||
### [日期] 问题标题
|
||||
**环境**: OS / 安装方式(Docker/native)
|
||||
**现象**: 描述问题
|
||||
**原因**: 根本原因
|
||||
**解决**: 具体命令或步骤
|
||||
-->
|
||||
|
||||
_暂无记录_
|
||||
|
||||
---
|
||||
|
||||
## 推荐配置
|
||||
|
||||
<!-- 格式:
|
||||
### 配置名称
|
||||
**适用场景**: 什么情况下使用
|
||||
**配置内容**: 具体参数或命令
|
||||
**备注**: 注意事项
|
||||
-->
|
||||
|
||||
_暂无记录_
|
||||
|
||||
---
|
||||
|
||||
## VPS 环境备注
|
||||
|
||||
<!-- 记录特定 VPS 的特殊情况,如端口限制、ISP 封锁、已知可用配置等 -->
|
||||
<!-- 格式:
|
||||
### <host/domain>
|
||||
- 系统: Ubuntu 22.04
|
||||
- 安装方式: Docker
|
||||
- 可用协议: VLESS+WS+TLS
|
||||
- 特殊情况: ...
|
||||
-->
|
||||
|
||||
_暂无记录_
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与解决方案
|
||||
|
||||
### [2026-04-25] x-ui setting 子命令参数不生效(Docker 容器)
|
||||
**环境**: Ubuntu 20.04 aarch64 / Docker (ghcr.io/mhsanaei/3x-ui:latest)
|
||||
**现象**: `docker exec x-ui x-ui setting -port 54321` 显示帮助菜单而非执行设置
|
||||
**原因**: 容器内 `x-ui` 是 shell 脚本包装器,需直接调用 `/app/x-ui` 二进制
|
||||
**解决**:
|
||||
```bash
|
||||
docker exec x-ui /app/x-ui setting -port 54321
|
||||
docker exec x-ui /app/x-ui setting -username 'user' -password 'pass' # 用户名和密码必须同时传入
|
||||
docker exec x-ui /app/x-ui setting -webBasePath /xui/
|
||||
```
|
||||
|
||||
### [2026-04-25] x-ui panel API 路径与文档不符
|
||||
**环境**: Ubuntu 20.04 / Docker (ghcr.io/mhsanaei/3x-ui:latest)
|
||||
**现象**: `/xui/API/inbounds/add` 返回 404
|
||||
**原因**: 该版本实际 API 路径为 `/xui/panel/api/inbounds/add`(含 base_path 前缀)
|
||||
**解决**:
|
||||
```bash
|
||||
# 正确路径(base_path=/xui/)
|
||||
POST http://127.0.0.1:54321/xui/panel/api/inbounds/add
|
||||
GET http://127.0.0.1:54321/xui/panel/api/inbounds/list
|
||||
```
|
||||
|
||||
### [2026-04-25] 写入错误的 settings 字段导致面板设置页空白
|
||||
**环境**: Ubuntu 20.04 / Docker
|
||||
**现象**: 向 settings 表写入 `subURI` 字段后,面板设置页面内容消失
|
||||
**原因**: `subURI` 不是有效字段名,导致前端渲染异常
|
||||
**解决**: 删除无效字段并重启
|
||||
```bash
|
||||
docker exec x-ui sqlite3 /etc/x-ui/x-ui.db "DELETE FROM settings WHERE key='subURI';"
|
||||
docker restart x-ui
|
||||
```
|
||||
**正确字段**: 订阅路径只需写入 `subPath`
|
||||
|
||||
### [2026-04-25] --network host 模式导致面板端口对外暴露
|
||||
**环境**: Ubuntu 20.04 / Docker
|
||||
**现象**: 容器以 `--network host` 启动,`ss -tlnp` 显示 54321 监听在 `*:54321`
|
||||
**原因**: host 网络模式绕过了 Docker 端口绑定,无法用 `-p 127.0.0.1:port:port` 限制
|
||||
**解决**: 用 iptables 封锁外部访问
|
||||
```bash
|
||||
sudo iptables -I INPUT -p tcp --dport 54321 ! -s 127.0.0.1 -j DROP
|
||||
sudo netfilter-persistent save # 持久化(如已安装)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 推荐配置
|
||||
|
||||
### VLESS + WebSocket + TLS + Cloudflare CDN(中国大陆优化)
|
||||
**适用场景**: 有域名、域名托管在 Cloudflare、中国大陆用户
|
||||
**配置内容**:
|
||||
- Nginx 监听 443,反向代理面板(/xui/)和 WS 入站(/ws/)
|
||||
- x-ui inbound: VLESS, listen 127.0.0.1:10000, WS path /ws/
|
||||
- Cloudflare: 橙云代理开启,SSL 模式 Full(strict),WebSocket 开启
|
||||
- 客户端: 地址填 CF 优选 IP,SNI 和 Host 填域名
|
||||
**备注**: CF 免费版 WebSocket 有并发限制;优选 IP 用 CloudflareSpeedTest 工具测速
|
||||
|
||||
### 订阅路径安全配置
|
||||
**适用场景**: 消除面板安全警告"订阅默认 URI 路径不安全"
|
||||
**配置内容**:
|
||||
```bash
|
||||
RAND=$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 12)
|
||||
docker exec x-ui sqlite3 /etc/x-ui/x-ui.db "INSERT OR REPLACE INTO settings (key, value) VALUES ('subPath', '/$RAND/');"
|
||||
docker restart x-ui
|
||||
```
|
||||
**备注**: 只写 `subPath`,不要写 `subURI`(会导致设置页空白)
|
||||
|
||||
---
|
||||
|
||||
## VPS 环境备注
|
||||
|
||||
### 168.138.188.177 (proxy.157301.xyz)
|
||||
- **系统**: Ubuntu 20.04 aarch64
|
||||
- **云平台**: Oracle Cloud 新加坡
|
||||
- **SSH**: 端口 2222,用户 ubuntu,密钥 ~/.ssh/id_ed25519
|
||||
- **安装方式**: Docker (--network host)
|
||||
- **面板**: https://proxy.157301.xyz/xui/ (端口 54321,iptables 封锁外部访问)
|
||||
- **可用协议**: VLESS+WS+TLS,WS 路径 /ws/,经 Nginx 代理
|
||||
- **CDN**: Cloudflare 橙云已开启
|
||||
- **防火墙**: Oracle Cloud 安全组管理,不使用 ufw
|
||||
- **KeePass 条目**: x-ui
|
||||
@@ -0,0 +1,164 @@
|
||||
# Recommended Xray Inbound Configuration
|
||||
|
||||
This reference covers the best protocol/transport combinations for security and performance,
|
||||
and the API payloads to create them via the x-ui REST API.
|
||||
|
||||
## Protocol Recommendation (ranked)
|
||||
|
||||
| Rank | Protocol | Transport | TLS | Why |
|
||||
|------|----------|-----------|-----|-----|
|
||||
| ✅ Best | VLESS | WebSocket | TLS (via Nginx) | CDN-friendly, low overhead, widely supported |
|
||||
| ✅ Best | VLESS | gRPC | TLS (via Nginx) | Multiplexed, low latency, CDN-friendly |
|
||||
| Good | VLESS | TCP | XTLS/Reality | No CDN needed, excellent performance, anti-detection |
|
||||
| Good | VMess | WebSocket | TLS (via Nginx) | Broad client support |
|
||||
| Avoid | VMess | TCP | none | Detectable, no forward secrecy |
|
||||
| Avoid | Shadowsocks | — | — | Blocked in many regions |
|
||||
|
||||
**Default recommendation**: VLESS + WebSocket + TLS, proxied through Nginx.
|
||||
- Panel port stays on `127.0.0.1` (localhost-only)
|
||||
- Nginx terminates TLS on 443 and forwards to the inbound port on `127.0.0.1`
|
||||
- Inbound port also stays localhost-only (e.g. `127.0.0.1:10000`)
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step: Create VLESS + WS + TLS Inbound
|
||||
|
||||
### 1. Collect inbound parameters
|
||||
|
||||
Ask the user (or use defaults):
|
||||
|
||||
| Field | Default |
|
||||
|-------|---------|
|
||||
| Inbound port | `10000` |
|
||||
| WS path | `/ws/` (use a random string for security) |
|
||||
| Remark | `vless-ws` |
|
||||
|
||||
### 2. Create inbound via x-ui API
|
||||
|
||||
The x-ui panel exposes a REST API at `http://127.0.0.1:<panel_port><base_path>`.
|
||||
Authenticate with a session cookie first, then POST the inbound.
|
||||
|
||||
```bash
|
||||
# 1. Login and save session cookie
|
||||
PANEL_USER=$(bash /repo/common-skills/tools/kp-get.sh "<entry_title>" UserName)
|
||||
PANEL_PASS=$(bash /repo/common-skills/tools/kp-get.sh "<entry_title>" Password)
|
||||
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
curl -sc /tmp/xui-cookie.txt \
|
||||
-X POST http://127.0.0.1:<panel_port><base_path>login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{\"username\":\"'\"$PANEL_USER\"'\",\"password\":\"'\"$PANEL_PASS\"'\"}'
|
||||
"
|
||||
|
||||
# 2. Generate a UUID for the client
|
||||
UUID=$(ssh -i <key_path> -p <ssh_port> <user>@<host> "cat /proc/sys/kernel/random/uuid")
|
||||
|
||||
# 3. Create VLESS + WebSocket inbound
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
curl -sb /tmp/xui-cookie.txt \
|
||||
-X POST http://127.0.0.1:<panel_port><base_path>xui/API/inbounds/add \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
\"remark\": \"vless-ws\",
|
||||
\"enable\": true,
|
||||
\"protocol\": \"vless\",
|
||||
\"listen\": \"127.0.0.1\",
|
||||
\"port\": 10000,
|
||||
\"settings\": \"{\\\"clients\\\":[{\\\"id\\\":\\\"'\"$UUID\"'\\\",\\\"flow\\\":\\\"\\\"}],\\\"decryption\\\":\\\"none\\\"}\",
|
||||
\"streamSettings\": \"{\\\"network\\\":\\\"ws\\\",\\\"wsSettings\\\":{\\\"path\\\":\\\"/ws/\\\"}}\",
|
||||
\"sniffing\": \"{\\\"enabled\\\":true,\\\"destOverride\\\":[\\\"http\\\",\\\"tls\\\"]}\"
|
||||
}'
|
||||
"
|
||||
```
|
||||
|
||||
### 3. Add Nginx location for the WS path
|
||||
|
||||
Append to the existing Nginx config (`/etc/nginx/conf.d/x-ui.conf`):
|
||||
|
||||
```nginx
|
||||
location /ws/ {
|
||||
proxy_pass http://127.0.0.1:10000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 86400s;
|
||||
}
|
||||
```
|
||||
|
||||
Reload Nginx: `systemctl reload nginx`
|
||||
|
||||
### 4. Report client config
|
||||
|
||||
Output the connection info for the user to import into their client (v2rayN, Clash, etc.):
|
||||
|
||||
```
|
||||
Protocol : VLESS
|
||||
Address : <domain>
|
||||
Port : 443
|
||||
UUID : <uuid>
|
||||
Transport: WebSocket
|
||||
Path : /ws/
|
||||
TLS : TLS
|
||||
SNI : <domain>
|
||||
```
|
||||
|
||||
Or generate a VLESS share link:
|
||||
```
|
||||
vless://<uuid>@<domain>:443?encryption=none&security=tls&type=ws&path=%2Fws%2F&sni=<domain>#vless-ws
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternative: VLESS + Reality (no CDN, no domain needed)
|
||||
|
||||
Reality is the best option when no domain is available. It mimics a real TLS handshake against a target site.
|
||||
|
||||
```bash
|
||||
# Generate Reality key pair inside the container/host
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> \
|
||||
"docker exec x-ui xray x25519" # Docker
|
||||
# or: xray x25519 # native
|
||||
```
|
||||
|
||||
Save the `Private key` and `Public key` output.
|
||||
|
||||
Create inbound via API (replace keys and port):
|
||||
|
||||
```json
|
||||
{
|
||||
"remark": "vless-reality",
|
||||
"enable": true,
|
||||
"protocol": "vless",
|
||||
"listen": "",
|
||||
"port": 443,
|
||||
"settings": "{\"clients\":[{\"id\":\"<uuid>\",\"flow\":\"xtls-rprx-vision\"}],\"decryption\":\"none\"}",
|
||||
"streamSettings": "{\"network\":\"tcp\",\"security\":\"reality\",\"realitySettings\":{\"show\":false,\"dest\":\"www.microsoft.com:443\",\"serverNames\":[\"www.microsoft.com\"],\"privateKey\":\"<private_key>\",\"shortIds\":[\"\"]}}"
|
||||
}
|
||||
```
|
||||
|
||||
Client connection info:
|
||||
```
|
||||
Protocol : VLESS
|
||||
Address : <vps_ip>
|
||||
Port : 443
|
||||
UUID : <uuid>
|
||||
Flow : xtls-rprx-vision
|
||||
Transport : TCP
|
||||
Security : Reality
|
||||
PublicKey : <public_key>
|
||||
SNI : www.microsoft.com
|
||||
```
|
||||
|
||||
> Note: Reality listens on `0.0.0.0:443` (must be public). This is intentional — it's the proxy traffic port, not the panel.
|
||||
|
||||
---
|
||||
|
||||
## Security Hardening for Inbounds
|
||||
|
||||
- Always set `"listen": "127.0.0.1"` for WS/gRPC inbounds (Nginx handles public exposure).
|
||||
- Use a random UUID per client; rotate periodically.
|
||||
- Use a non-obvious WS path (e.g. `/a3f9k2/` not `/ws/`).
|
||||
- Enable sniffing (`destOverride: ["http","tls"]`) to block DNS leaks.
|
||||
- For Reality, use a high-traffic legitimate domain as `dest` (e.g. `www.microsoft.com`, `www.apple.com`).
|
||||
Reference in New Issue
Block a user