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,30 @@
|
||||
You are a deployment specialist for x-ui (3x-ui Xray panel) on VPS servers. When the user wants to deploy or configure x-ui, activate the `xui-deploy` skill and follow its instructions exactly.
|
||||
|
||||
Always collect required inputs before executing any commands: VPS host, SSH user, SSH key path, and panel password. Never guess credentials. Never print passwords in command output.
|
||||
|
||||
When the user sends a greeting or help request (e.g., "hi", "hello", "help", "你好", "帮助", "?"), respond with:
|
||||
|
||||
---
|
||||
👋 **x-ui Deploy** — 自动化在 VPS 上部署 x-ui 代理面板
|
||||
|
||||
**功能:**
|
||||
- 通过 SSH 在 VPS 上一键安装 3x-ui(Xray 面板)
|
||||
- 自动配置面板端口、用户名、密码和 Web 路径
|
||||
- 支持 Debian / Ubuntu / CentOS / AlmaLinux
|
||||
- 自动开放防火墙端口(ufw / firewalld)
|
||||
- 检测已有安装并支持更新
|
||||
|
||||
**执行步骤:**
|
||||
1. 收集 VPS 信息(host、SSH 用户、密钥路径、面板密码)
|
||||
2. 验证 SSH 连通性,检查 OS 兼容性
|
||||
3. 运行 3x-ui 官方一键安装脚本
|
||||
4. 通过 `x-ui setting` CLI 配置端口/账号/密码/路径
|
||||
5. 重启服务并验证运行状态
|
||||
6. 开放防火墙端口
|
||||
7. 输出面板 URL 和登录信息
|
||||
|
||||
**使用示例:**
|
||||
- `在 198.51.100.42 上部署 x-ui,root 用户,密钥 ~/.ssh/id_rsa`
|
||||
- `Install 3x-ui on my VPS at vps.example.com, SSH port 2222, user ubuntu`
|
||||
- `我的 VPS 已经装了 x-ui,帮我更新到最新版本`
|
||||
---
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "xui-deploy",
|
||||
"description": "Automates installing and configuring x-ui (3x-ui Xray panel) on a VPS via SSH. Use when you want to deploy x-ui, install 3x-ui, set up an Xray proxy panel, or configure inbound proxies on a VPS.",
|
||||
"prompt": "file://prompts/xui-deploy.md",
|
||||
"tools": ["execute_bash", "fs_read"],
|
||||
"allowedTools": ["fs_read"],
|
||||
"resources": [
|
||||
"skill://skills/xui-deploy/SKILL.md"
|
||||
],
|
||||
"welcomeMessage": "Ready to deploy x-ui on your VPS. Tell me your VPS host, SSH user, and panel password to get started."
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# xui-deploy
|
||||
|
||||
Automates installing and configuring [3x-ui](https://github.com/MHSanaei/3x-ui) (Xray panel) on a remote VPS over SSH.
|
||||
|
||||
## Architecture
|
||||

|
||||
|
||||
## Workflow
|
||||

|
||||
|
||||
## When to Use
|
||||
|
||||
Activate this skill when the user wants to:
|
||||
- Deploy or install x-ui / 3x-ui on a VPS
|
||||
- Set up an Xray proxy panel
|
||||
- Update an existing x-ui installation
|
||||
- Configure panel port, credentials, or base path
|
||||
|
||||
Trigger phrases: `deploy x-ui`, `install x-ui`, `setup xui`, `install 3x-ui`, `xray panel`, `部署x-ui`, `安装x-ui`, `搭建xui`, `xray面板`
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Collects VPS host, SSH credentials, and panel settings from the user
|
||||
2. Tests SSH connectivity and checks OS compatibility
|
||||
3. Runs the official 3x-ui one-line installer (handles fresh install and updates)
|
||||
4. Configures panel port, username, password, and web base path via `x-ui` CLI
|
||||
5. Restarts the service and verifies it is running
|
||||
6. Opens the panel port in the system firewall (ufw or firewalld)
|
||||
7. Reports the panel URL and credentials
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
xui-deploy/
|
||||
├── SKILL.md # Instructions for the agent
|
||||
├── evals/
|
||||
│ └── evals.json # 4 test cases
|
||||
└── assets/
|
||||
├── architecture.puml
|
||||
├── xui-deploy-architecture.svg
|
||||
├── workflow.puml
|
||||
└── xui-deploy-workflow.svg
|
||||
```
|
||||
|
||||
## Evals
|
||||
|
||||
```bash
|
||||
python scripts/run_evals.py xui-deploy
|
||||
```
|
||||
@@ -0,0 +1,382 @@
|
||||
---
|
||||
name: xui-deploy
|
||||
description: Automates installing and configuring x-ui (Xray panel) on a VPS via SSH. Use when the user wants to deploy x-ui, set up a proxy panel, install 3x-ui, configure Xray on a VPS, or manage inbound proxies. Triggers on phrases like "deploy x-ui", "install x-ui", "setup xui", "install 3x-ui", "xray panel", "部署x-ui", "安装x-ui", "搭建xui", "安装3x-ui", "xray面板", "代理面板".
|
||||
compatibility: Requires SSH access to a Linux VPS (Debian/Ubuntu/CentOS). curl must be available on the VPS.
|
||||
metadata:
|
||||
author: common-skills
|
||||
version: "1.1"
|
||||
---
|
||||
|
||||
# x-ui Deploy
|
||||
|
||||
Automate installing and configuring [x-ui](https://github.com/MHSanaei/3x-ui) (3x-ui fork) on a remote VPS over SSH.
|
||||
|
||||
For Xray inbound protocol recommendations and configuration details, see [references/xray-inbound-config.md](references/xray-inbound-config.md).
|
||||
|
||||
Accumulated experience, known issues, and proven configurations are in [references/lessons-learned.md](references/lessons-learned.md).
|
||||
|
||||
## Experience Base
|
||||
|
||||
### Reading (before starting)
|
||||
|
||||
Always read `references/lessons-learned.md` before executing any workflow step. Check:
|
||||
- **VPS 环境备注**: if the target host is listed, apply any noted special handling upfront and skip redundant steps
|
||||
- **已知问题**: pre-empt known failure modes for the detected OS and install method
|
||||
- **推荐配置**: offer the user any saved configurations that match their scenario
|
||||
|
||||
### Writing (after resolving issues or finding good configs)
|
||||
|
||||
After any session where a problem was solved or a good configuration was validated, ask the user:
|
||||
|
||||
> "要把这个经验/配置记录到经验沉淀吗?下次部署可以直接复用。"
|
||||
|
||||
If yes, append to the appropriate section in `references/lessons-learned.md` following the format in that file. Include: date, environment, symptom, cause, and solution (for issues) or scenario, parameters, and notes (for configs).
|
||||
|
||||
## Inputs
|
||||
|
||||
Collect from the user before starting:
|
||||
|
||||
| Field | Example | Default |
|
||||
|---|---|---|
|
||||
| VPS host | `123.45.67.89` or `vps.example.com` | — |
|
||||
| SSH user | `root` | `root` |
|
||||
| SSH private key path | `~/.ssh/id_ed25519` | `~/.ssh/id_ed25519` |
|
||||
| SSH port | `22` | `22` |
|
||||
| x-ui panel port | `54321` | `54321` |
|
||||
| x-ui username | `admin` | `admin` |
|
||||
| x-ui password | (from KeePass or user provides) | — |
|
||||
| x-ui web base path | `/xui/` | `/xui/` |
|
||||
|
||||
**Sensitive credentials (password, username) must be retrieved via `kp-get.sh`**, not typed inline or guessed. Ask the user for the KeePass entry title if not provided.
|
||||
|
||||
```bash
|
||||
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)
|
||||
```
|
||||
|
||||
If the user does not have a KeePass entry for this VPS, ask them to provide the credentials directly and remind them to store them in KeePass afterward. Never hardcode or echo credentials in output.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Test SSH Connectivity
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> -o StrictHostKeyChecking=no -o ConnectTimeout=10 \
|
||||
<user>@<host> "echo OK && uname -a"
|
||||
```
|
||||
|
||||
If this fails, stop and report the error. Do not proceed.
|
||||
|
||||
### 2. Check OS Compatibility
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> \
|
||||
"cat /etc/os-release | grep -E '^(ID|VERSION_ID)'"
|
||||
```
|
||||
|
||||
Supported: Debian 9+, Ubuntu 18.04+, CentOS 7+, AlmaLinux 8+. Warn the user if the OS is unsupported but continue if they confirm.
|
||||
|
||||
### 3. Detect Existing x-ui Installation
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'x-ui|3x-ui'; then
|
||||
echo 'DOCKER' && docker ps -a --format '{{.Names}}\t{{.Status}}' | grep -E 'x-ui|3x-ui'
|
||||
elif command -v x-ui &>/dev/null; then
|
||||
echo 'NATIVE' && x-ui version
|
||||
else
|
||||
echo 'NOT_INSTALLED'
|
||||
fi
|
||||
"
|
||||
```
|
||||
|
||||
Based on the result, **ask the user what to do**:
|
||||
|
||||
- **`DOCKER`** — Options: 1) Update config (Step 5) 2) Redeploy from scratch 3) Cancel
|
||||
- **`NATIVE`** — Options: 1) Update config (Step 5) 2) Redeploy from scratch 3) Cancel
|
||||
- **`NOT_INSTALLED`** — Proceed to Step 4.
|
||||
|
||||
If **Redeploy**, clean up first:
|
||||
|
||||
```bash
|
||||
# Docker cleanup
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
docker stop x-ui 2>/dev/null || true
|
||||
docker rm x-ui 2>/dev/null || true
|
||||
docker rmi ghcr.io/mhsanaei/3x-ui:latest 2>/dev/null || true
|
||||
rm -rf ~/x-ui
|
||||
"
|
||||
|
||||
# Native cleanup
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
x-ui stop 2>/dev/null || true
|
||||
systemctl disable x-ui 2>/dev/null || true
|
||||
rm -f /usr/local/bin/x-ui /usr/local/x-ui/x-ui.db
|
||||
rm -f /etc/systemd/system/x-ui.service
|
||||
systemctl daemon-reload
|
||||
"
|
||||
```
|
||||
|
||||
### 4. Install x-ui
|
||||
|
||||
**Prefer Docker**. Fall back to native only if Docker is unavailable or fails.
|
||||
|
||||
#### 4a. Docker install (preferred)
|
||||
|
||||
> ⚠️ Always use `-p 127.0.0.1:<port>:<port>` — never `--network host`. Host networking exposes the panel port publicly and requires iptables workarounds.
|
||||
|
||||
```bash
|
||||
# Install Docker if missing
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> \
|
||||
"docker --version 2>/dev/null || curl -fsSL https://get.docker.com | sh"
|
||||
|
||||
# Run container with localhost-only port binding
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
mkdir -p ~/x-ui/db ~/x-ui/cert
|
||||
docker run -d \
|
||||
--name x-ui \
|
||||
--restart unless-stopped \
|
||||
-p 127.0.0.1:<panel_port>:<panel_port> \
|
||||
-v ~/x-ui/db:/etc/x-ui \
|
||||
-v ~/x-ui/cert:/root/cert \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
"
|
||||
```
|
||||
|
||||
#### 4b. Native install (fallback)
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> \
|
||||
"bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/master/install.sh) <<< $'\n'"
|
||||
```
|
||||
|
||||
If CentOS 7 fails, run `yum install -y epel-release` first and retry.
|
||||
|
||||
### 5. Configure Panel Settings
|
||||
|
||||
> ⚠️ For Docker: use `/app/x-ui` (the binary), not `x-ui` (the shell wrapper which ignores subcommand args).
|
||||
> ⚠️ Username and password must be set in a single command — setting them separately fails.
|
||||
|
||||
```bash
|
||||
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)
|
||||
|
||||
# Docker
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "docker exec x-ui /app/x-ui setting -port <panel_port>"
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "docker exec x-ui /app/x-ui setting -username '$PANEL_USER' -password '$PANEL_PASS'"
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "docker exec x-ui /app/x-ui setting -webBasePath <base_path>"
|
||||
|
||||
# Native
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "x-ui setting -port <panel_port>"
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "x-ui setting -username '$PANEL_USER' -password '$PANEL_PASS'"
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "x-ui setting -webBasePath <base_path>"
|
||||
```
|
||||
|
||||
### 6. Set Random Subscription Path
|
||||
|
||||
Do this immediately after panel config — eliminates the "subscription URI insecure" security warning.
|
||||
|
||||
> ⚠️ Only write `subPath`. Do NOT write `subURI` — it causes the settings page to go blank.
|
||||
|
||||
```bash
|
||||
RAND=$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 12)
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
docker exec x-ui sqlite3 /etc/x-ui/x-ui.db \
|
||||
\"INSERT OR REPLACE INTO settings (key, value) VALUES ('subPath', '/$RAND/');\"
|
||||
"
|
||||
echo "Subscription path: /$RAND/"
|
||||
```
|
||||
|
||||
### 7. Restart & Verify
|
||||
|
||||
```bash
|
||||
# Docker
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "docker restart x-ui && sleep 3 && docker ps --filter name=x-ui --format '{{.Status}}'"
|
||||
|
||||
# Native
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "x-ui restart && x-ui status"
|
||||
```
|
||||
|
||||
If not running: `docker logs x-ui --tail 30` or `x-ui log`.
|
||||
|
||||
### 8. Restrict Panel Port to Localhost
|
||||
|
||||
For **Docker with `-p 127.0.0.1:port:port`**: already localhost-only, skip this step.
|
||||
|
||||
For **Docker with `--network host`** or **native install**:
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
sudo iptables -I INPUT -p tcp --dport <panel_port> ! -s 127.0.0.1 -j DROP
|
||||
# Persist rules
|
||||
if command -v netfilter-persistent &>/dev/null; then
|
||||
sudo netfilter-persistent save
|
||||
else
|
||||
echo 'WARNING: netfilter-persistent not installed — iptables rule will not survive reboot'
|
||||
echo 'Install with: sudo apt-get install -y iptables-persistent'
|
||||
fi
|
||||
"
|
||||
```
|
||||
|
||||
To access the panel remotely via SSH tunnel:
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> -L <panel_port>:127.0.0.1:<panel_port> <user>@<host> -N
|
||||
```
|
||||
|
||||
### 9. Set Up Nginx Reverse Proxy
|
||||
|
||||
Ask the user if they have a domain. If yes, set up HTTPS. If no, set up HTTP-only.
|
||||
|
||||
#### 9a. Install Nginx if missing
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
command -v nginx &>/dev/null || (apt-get install -y nginx 2>/dev/null || yum install -y nginx)
|
||||
sudo systemctl enable --now nginx
|
||||
"
|
||||
```
|
||||
|
||||
#### 9b. Write config (HTTP first, for certbot validation)
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "sudo tee /etc/nginx/sites-available/<domain> > /dev/null << 'EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name <domain>;
|
||||
|
||||
location <base_path> {
|
||||
proxy_pass http://127.0.0.1:<panel_port><base_path>;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
sudo ln -sf /etc/nginx/sites-available/<domain> /etc/nginx/sites-enabled/
|
||||
sudo nginx -t && sudo systemctl reload nginx"
|
||||
```
|
||||
|
||||
#### 9c. HTTPS with Let's Encrypt
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
command -v certbot &>/dev/null || apt-get install -y certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d <domain> --non-interactive --agree-tos -m <admin_email>
|
||||
"
|
||||
```
|
||||
|
||||
#### 9d. Final Nginx config (HTTPS + panel + WebSocket)
|
||||
|
||||
After certbot, update the config to include the WS inbound location:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name <domain>;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name <domain>;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location <base_path> {
|
||||
proxy_pass http://127.0.0.1:<panel_port><base_path>;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location <ws_path> {
|
||||
proxy_pass http://127.0.0.1:<inbound_port>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Verify certbot auto-renewal: `sudo certbot renew --dry-run`
|
||||
|
||||
### 10. Post-Deploy Security Check
|
||||
|
||||
```bash
|
||||
ssh -i <key_path> -p <ssh_port> <user>@<host> "
|
||||
echo '=== Panel port exposure ==='
|
||||
ss -tlnp | grep ':<panel_port>' | grep -v '127.0.0.1' && echo 'WARNING: panel port publicly exposed!' || echo 'OK: localhost-only'
|
||||
|
||||
echo '=== Root login ==='
|
||||
grep '^PermitRootLogin' /etc/ssh/sshd_config || echo 'not explicitly set'
|
||||
|
||||
echo '=== Password auth ==='
|
||||
grep '^PasswordAuthentication' /etc/ssh/sshd_config || echo 'not explicitly set'
|
||||
"
|
||||
```
|
||||
|
||||
Flag as risks:
|
||||
- Panel port on `0.0.0.0`/`*` → **CRITICAL**: apply iptables rule from Step 8
|
||||
- `PasswordAuthentication yes` → recommend disabling (key-only auth)
|
||||
- `PermitRootLogin yes` → recommend `prohibit-password`
|
||||
|
||||
### 11. Configure Xray Inbound
|
||||
|
||||
Ask the user:
|
||||
> "要设置 Xray 入站协议吗?推荐 VLESS+WebSocket+TLS(有域名/CDN)或 VLESS+Reality(无域名)。"
|
||||
|
||||
Follow [references/xray-inbound-config.md](references/xray-inbound-config.md) for full steps.
|
||||
|
||||
**Key notes for this version's API** (confirmed working):
|
||||
- Login endpoint: `POST <base_path>login`
|
||||
- Add inbound: `POST <base_path>panel/api/inbounds/add`
|
||||
- List inbounds: `GET <base_path>panel/api/inbounds/list`
|
||||
- Use `/app/x-ui` binary inside Docker container, not the `x-ui` shell wrapper
|
||||
|
||||
**WS path must be randomized** — never use `/ws/`. Generate a random path:
|
||||
```bash
|
||||
WS_PATH="/$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 8)/"
|
||||
```
|
||||
|
||||
After creating the inbound, output the VLESS share link and optionally generate a QR code:
|
||||
```bash
|
||||
# Generate QR in terminal (requires qrencode)
|
||||
echo "vless://..." | qrencode -t ansiutf8
|
||||
```
|
||||
|
||||
### 12. Report Results
|
||||
|
||||
- Panel URL: `https://<domain><base_path>`
|
||||
- SSH tunnel (direct): `ssh -L <panel_port>:127.0.0.1:<panel_port> <user>@<host> -p <ssh_port> -N`
|
||||
- VLESS share link + QR code
|
||||
- Certbot renewal status
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **Docker `--network host` (legacy/existing)**: Panel port will be on `0.0.0.0`. Apply iptables rule in Step 8. For new deployments always use `-p 127.0.0.1:port:port`.
|
||||
- **`x-ui setting` shows help instead of applying**: Use `/app/x-ui setting` directly inside Docker container.
|
||||
- **Username/password setting fails**: Must pass `-username` and `-password` together in one command.
|
||||
- **Settings page blank after DB edit**: Check for invalid keys — only valid keys are `secret`, `webPort`, `webBasePath`, `subPath`. Delete any others and restart.
|
||||
- **API 404 on `/xui/API/inbounds`**: Correct path is `<base_path>panel/api/inbounds/add`.
|
||||
- **Port already in use**: Step 7 shows bind error. Choose different port, re-run Steps 5–7.
|
||||
- **CentOS 7**: Run `yum install -y epel-release` before native install.
|
||||
- **Non-root SSH user**: Prefix commands with `sudo`.
|
||||
- **Cloud provider security groups (Oracle/AWS/GCP)**: Do not use ufw/firewalld for panel port — manage via cloud console. Use iptables only for localhost restriction.
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Always retrieve credentials via `kp-get.sh`. Never echo passwords.
|
||||
- Docker: use `-p 127.0.0.1:port:port` to enforce localhost binding at container level.
|
||||
- Native: use iptables to block external panel access. Install `iptables-persistent` to survive reboots.
|
||||
- Randomize WS path and subscription path — never use defaults.
|
||||
- Enable HTTPS (Step 9) — direct port access only via SSH tunnel.
|
||||
- `PasswordAuthentication yes` in sshd_config is a risk — recommend key-only.
|
||||
- Panel port ≠ SSH port.
|
||||
@@ -0,0 +1,40 @@
|
||||
@startuml xui-deploy-architecture
|
||||
skinparam componentStyle rectangle
|
||||
skinparam defaultFontName Arial
|
||||
skinparam backgroundColor #FAFAFA
|
||||
|
||||
package "xui-deploy Skill" {
|
||||
component "SKILL.md\n(instructions)" as SKILL
|
||||
component "evals/evals.json" as EVALS
|
||||
}
|
||||
|
||||
package "Local Machine" {
|
||||
component "SSH private key" as KEY
|
||||
actor Developer
|
||||
}
|
||||
|
||||
package "VPS (Linux)" {
|
||||
component "curl / bash" as CURL
|
||||
component "x-ui CLI\n(x-ui setting / restart)" as XUCLI
|
||||
component "3x-ui Service\n(systemd)" as SERVICE
|
||||
database "x-ui Data\n(/etc/x-ui/)" as DATA
|
||||
component "Panel Web UI\n(:<panel_port>)" as PANEL
|
||||
component "Firewall\n(ufw / firewalld)" as FW
|
||||
}
|
||||
|
||||
cloud "GitHub" {
|
||||
component "install.sh\n(MHSanaei/3x-ui)" as INSTALLER
|
||||
}
|
||||
|
||||
Developer --> SKILL : triggers skill
|
||||
SKILL --> KEY : SSH auth
|
||||
SKILL --> CURL : runs installer via SSH
|
||||
CURL --> INSTALLER : downloads install.sh
|
||||
INSTALLER --> SERVICE : installs & starts
|
||||
SKILL --> XUCLI : configure port/user/pass/path
|
||||
XUCLI --> SERVICE : restart
|
||||
SERVICE --> DATA : persists config
|
||||
SERVICE --> PANEL : serves web UI
|
||||
SKILL --> FW : opens panel port
|
||||
SKILL --> Developer : reports panel URL
|
||||
@enduml
|
||||
@@ -0,0 +1,43 @@
|
||||
@startuml xui-deploy-workflow
|
||||
skinparam defaultFontName Arial
|
||||
skinparam backgroundColor #FAFAFA
|
||||
|
||||
actor Developer
|
||||
participant "xui-deploy\nSkill" as SKILL
|
||||
participant "VPS (SSH)" as VPS
|
||||
participant "GitHub\n(install.sh)" as GH
|
||||
|
||||
Developer -> SKILL : deploy x-ui on VPS
|
||||
SKILL -> Developer : collect host, SSH user, key, panel password
|
||||
|
||||
SKILL -> VPS : ssh echo OK (connectivity test)
|
||||
alt SSH fails
|
||||
SKILL -> Developer : report error, stop
|
||||
end
|
||||
|
||||
SKILL -> VPS : cat /etc/os-release
|
||||
alt unsupported OS
|
||||
SKILL -> Developer : warn, ask to confirm
|
||||
end
|
||||
|
||||
SKILL -> VPS : curl install.sh | bash
|
||||
VPS -> GH : download install.sh
|
||||
alt x-ui already installed
|
||||
VPS -> VPS : update to latest version
|
||||
else fresh install
|
||||
VPS -> VPS : install 3x-ui + systemd service
|
||||
end
|
||||
|
||||
SKILL -> VPS : x-ui setting -port / -username / -password / -webBasePath
|
||||
SKILL -> VPS : x-ui restart
|
||||
SKILL -> VPS : x-ui status
|
||||
|
||||
alt service not running
|
||||
SKILL -> VPS : x-ui log (last 30 lines)
|
||||
SKILL -> Developer : report error
|
||||
end
|
||||
|
||||
SKILL -> VPS : ufw allow <port> OR firewall-cmd --add-port
|
||||
|
||||
SKILL -> Developer : panel URL + username + version
|
||||
@enduml
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 16 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"skill_name": "xui-deploy",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "帮我在 VPS 上部署 x-ui,IP 是 198.51.100.42,SSH 用 root 登录,密钥在 ~/.ssh/id_rsa,KeePass 条目名是 'VPS-xui'",
|
||||
"expected_output": "Agent uses kp-get.sh to retrieve username and password from KeePass entry 'VPS-xui' before running any commands. Tests SSH connectivity, runs the 3x-ui installer, configures panel settings using the retrieved credentials (never echoing the password), restarts the service, opens the firewall port, and reports the panel URL."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "I want to install 3x-ui on my Ubuntu 22.04 VPS at vps.example.com. SSH port is 2222, user is ubuntu, key at ~/.ssh/vps_key. Set panel port to 8443 and base path to /panel/. KeePass entry: 'vps.example.com'",
|
||||
"expected_output": "Agent calls kp-get.sh with entry 'vps.example.com' to get UserName and Password. Uses non-default SSH port 2222, prefixes x-ui commands with sudo (non-root user), sets panel port to 8443 and base path to /panel/, then reports the panel URL as http://vps.example.com:8443/panel/."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "部署 x-ui,但我的 VPS 已经装过了,想更新到最新版本",
|
||||
"expected_output": "Agent asks for VPS host, SSH credentials, and KeePass entry title if not provided. Runs the installer which detects the existing installation and updates it. Retrieves credentials via kp-get.sh, restarts the service, verifies it is running, and reports the updated version."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"prompt": "在 CentOS 7 的 VPS 上安装 x-ui,IP 192.0.2.10,root 用户,密钥 ~/.ssh/id_ed25519。我没有 KeePass 条目,密码是 S3cr3t!",
|
||||
"expected_output": "Agent detects no KeePass entry is available, accepts the password directly, reminds the user to save it to KeePass afterward, and never echoes the password in output. Detects CentOS 7, handles epel-release if needed, uses firewall-cmd to open the panel port, and reports the panel URL."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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