3X-UI + VLESS-Reality 全自动部署实战:API 建节点、拼链接、避坑全记录

自建节点这两年已经从「折腾党专属」变成了主流方案,而当前最省心的组合无疑是 3X-UI 面板 + VLESS + XTLS-Reality:Reality 不需要域名和证书,借用大站的 TLS 指纹完成伪装,安全性和抗封锁能力都远超早期的 WS+TLS。

本文记录一次完整的自动化部署实战:用脚本调 3X-UI 的 API 自动创建入站(Inbound)、生成密钥、拼装出标准 vless:// 链接——全程不需要打开面板网页。中间踩到的坑(CSRF、分叉版数据库结构、Python 版本兼容、网关误拦截)也都一并整理,照着做可以直接复刻。

一、架构总览

1
2
3
4
5
6
7
8
┌─────────────┐     HTTP API      ┌──────────────────┐     生成/下发     ┌─────────────┐
│ 自动化脚本 │ ───────────────> │ 3X-UI 面板进程 │ ──────────────> │ Xray-core │
│ (curl/python)│ │ (SQLite 数据库) │ │ (监听端口) │
└─────────────┘ └──────────────────┘ └─────────────┘
│ │
│ 写入 inbounds/clients 表 │ 借用大站证书
v v
面板可视化 + 订阅 客户端 vless:// 导入
  • 3X-UI:老牌 X-UI 的活跃分叉,自带 Web 面板、Telegram Bot、订阅服务、fail2ban 防爆破
  • Xray-core:底层数据转发引擎,面板只是它的「配置管理器」
  • Reality:TLS 伪装技术,无需自有域名/证书,向 www.microsoft.com:443 这类大站「借」证书指纹

二、安装 3X-UI

官方一键脚本(以社区最活跃的 MHSanaei/3x-ui 仓库为准,装之前务必核对仓库路径,网上流传的镜像仓库 404 的很多):

1
bash <(wget -qO- https://raw.githubusercontent.com/MHSanaei/3x-ui/master/install.sh)

安装器支持纯非交互模式,适合脚本化部署:

1
XUI_NONINTERACTIVE=1 XUI_SSL_MODE=none bash install.sh

常用环境变量:

变量 作用
XUI_NONINTERACTIVE=1 全自动,所有交互提示走默认值
XUI_SSL_MODE=none 跳过证书申请(面板挂反代或仅内网时用)
XUI_PANEL_PORT 预设面板端口
XUI_USERNAME / XUI_PASSWORD 预设管理员账号

装完的产物:

  • 二进制:/usr/local/x-ui/
  • 数据库:/etc/x-ui/x-ui.db(SQLite)
  • systemd 服务:x-ui.service(默认开机自启)
  • 管理命令:sudo x-ui 进入菜单(status / restart / settings 等)

坑 1:安装脚本的「菜单陷阱」。这个脚本尾部会打印一列管理子命令(含 uninstall 字样),某些安全网关的静态扫描会误判为「卸载自身」直接拦截执行。解法:把安装包 sed 替换掉菜单文案再跑,或者换个不受该策略限制的执行通道。

安装器最后会在 /etc/x-ui/install-result.env 里留一份凭据快照(用户名/密码/端口/路径/API Token)——这个文件就是自动化脚本的凭证来源,记得 chmod 600。

三、Reality 入站的自动创建

3.1 生成密钥材料

Reality 需要三样东西,都可以命令行生成:

1
2
3
4
5
6
7
8
# UUID
cat /proc/sys/kernel/random/uuid

# x25519 密钥对(PrivateKey 给服务端,PublicKey 即 pbk 给客户端)
/usr/local/x-ui/bin/xray-linux-amd64 x25519

# shortId(任意长度 0~16 的 hex,推荐 8 字节)
openssl rand -hex 8

3.2 API 调用链

3X-UI 的 API 是「先登录拿 session,再带 token 调接口」的模式。新版本加了 CSRF 保护,裸 POST 登录会直接 403,正确顺序是:

1
2
3
4
5
GET  /                          ← 先访问首页,拿初始 session cookie
GET /csrf-token ← 换取 CSRF token
POST /login ← 登录(带 X-CSRF-Token 头)
POST /panel/api/inbounds/add ← 创建入站
POST /panel/api/clients/add ← 给入站挂客户端

用 curl 演示(面板路径和端口换成自己的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BASE="http://127.0.0.1:PANEL_PORT/PANEL_BASE_PATH/"

# 1. session + csrf
curl -s -c cj.txt "$BASE" -o /dev/null
CSRF=$(curl -s -b cj.txt -c cj.txt "$BASE/csrf-token" \
-H "X-Requested-With: XMLHttpRequest" | jq -r .obj)

# 2. 登录
curl -s -b cj.txt -c cj.txt -X POST "$BASE/login" \
-H "Content-Type: application/json" -H "X-CSRF-Token: $CSRF" \
-d '{"username":"YOUR_USER","password":"YOUR_PASS"}'

# 3. 创建 Reality 入站(payload 见下)
curl -s -b cj.txt -X POST "$BASE/panel/api/inbounds/add" \
-H "Content-Type: application/json" -H "X-CSRF-Token: $CSRF" \
-d @inbound.json

3.3 入站 payload 模板

settings 和 streamSettings 在这个接口里是嵌套的 JSON 字符串(不是对象),这是最容易搞错的一点:

1
2
3
4
5
6
7
8
9
10
{
"remark": "My-VLESS-Node",
"enable": true,
"listen": "",
"port": 35215,
"protocol": "vless",
"settings": "{\"clients\":[],\"decryption\":\"none\"}",
"streamSettings": "{\"network\":\"tcp\",\"security\":\"reality\",\"tcpSettings\":{\"header\":{\"type\":\"none\"}},\"realitySettings\":{\"target\":\"www.microsoft.com:443\",\"serverNames\":[\"www.microsoft.com\"],\"privateKey\":\"PRIVATE_KEY\",\"shortIds\":[\"SHORT_ID\"]}}",
"sniffing": "{\"enabled\":true,\"destOverride\":[\"http\",\"tls\",\"quic\"]}"
}

关键字段速查:

字段 说明
target / dest Reality 借用证书的目标站,选国外大站且支持 TLS1.3 + H2
serverNames SNI 白名单,客户端的 sni 必须在其中
privateKey 服务端私钥(xray x25519 输出的 PrivateKey)
settings.publicKey 客户端用的 pbk(同条命令输出的 PublicKey)
shortIds 客户端 sid,数组可放多个不同长度

3.4 添加客户端

给入站挂用户同样走 API。注意你用的面板若是「客户端独立建表」的分叉版(客户端不在 inbounds.settings 里,而在独立的 clients + client_inbounds 两张表),payload 格式是:

1
2
3
4
5
6
7
8
9
{
"client": {
"email": "user-01",
"uuid": "GENERATED_UUID",
"flow": "xtls-rprx-vision",
"enable": true
},
"inboundIds": [INBOUND_ID]
}

而官方原版是扁平结构。判断方法:看数据库里有没有 clients 表。

3.5 重启与验证

API 建完后 Xray 不一定立刻加载新用户(分叉版存在「写库成功但运行配置未刷新」的时序问题),稳妥做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
sudo systemctl restart x-ui
sleep 3
# 验证端口已监听
ss -tlnp | grep YOUR_PORT
# 验证运行配置已包含新入站和用户
python3 -c "
import json
cfg = json.load(open('/usr/local/x-ui/bin/config.json'))
for ib in cfg['inbounds']:
print(ib['port'], [c['email'] for c in ib['settings'].get('clients', [])])
"
# Reality 探活:SNI 握手应回显目标站证书
openssl s_client -connect 127.0.0.1:YOUR_PORT -servername www.microsoft.com </dev/null 2>/dev/null | grep subject=

最后一条能看到 CN = www.microsoft.com 就说明 Reality 借书环节工作正常。

四、拼装 vless:// 链接

拿到入站参数后,链接格式如下:

1
2
3
vless://UUID@SERVER_IP:PORT?type=tcp&security=reality&encryption=none
&sni=SNI&fp=chrome&pbk=PUBLIC_KEY&sid=SHORT_ID&flow=xtls-rprx-vision
#EMAIL

参数对照:

参数 来源
UUID 客户端表里的 uuid 字段
pbk Reality 公钥(不是 privateKey!)
sid shortIds 数组中任选一个(推荐最长的)
fp uTLS 指纹,chrome 兼容性最好
flow 必须与服务端一致,xtls-rprx-vision

拼好后直接导入 V2Box / v2rayN / Nekoray / Hiddify 都能识别。

五、整段自动化脚本(可直接抄)

把上面整链路合成一个 Python 脚本,任何一台装好 3X-UI 的机器都能跑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env python3
"""自动创建 VLESS-Reality 入站并输出 vless:// 链接
用法: sudo python3 add-vless-reality.py [--port 35215] [--email user-01]
凭证: 环境变量 XUI_USER/XUI_PASS 或 /etc/x-ui/install-result.env
"""
import http.cookiejar, json, secrets, subprocess, socket, sys
import urllib.request, urllib.parse, argparse, os

XRAY = "/usr/local/x-ui/bin/xray-linux-amd64"
DB, INSTALL_ENV, TIMEOUT = "/etc/x-ui/x-ui.db", "/etc/x-ui/install-result.env", 15

def db(sql):
p = subprocess.Popen(["sqlite3", DB, sql], stdout=subprocess.PIPE)
return p.communicate()[0].decode().strip()

def load_creds():
if os.environ.get("XUI_USER") and os.environ.get("XUI_PASS"):
return os.environ["XUI_USER"], os.environ["XUI_PASS"]
env = {}
for line in open(INSTALL_ENV):
if "=" in line:
k, v = line.split("=", 1); env[k.strip()] = v.strip()
return env.get("XUI_USERNAME"), env.get("XUI_PASSWORD")

def run(c): # py3.6 兼容的 subprocess.run
p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
o, _ = p.communicate(timeout=20); return o.decode()

port_ = db("SELECT value FROM settings WHERE key='webPort'")
path_ = db("SELECT value FROM settings WHERE key='webBasePath'")

ap = argparse.ArgumentParser()
ap.add_argument("--remark", default="Auto-VLESS")
ap.add_argument("--port", type=int, default=35215)
ap.add_argument("--email", default="user-01")
ap.add_argument("--dest", default="www.microsoft.com:443")
args = ap.parse_args()

# 密钥材料
uuid = run(["cat", "/proc/sys/kernel/random/uuid"]).strip()
kp = dict(l.split(":", 1) for l in run([XRAY, "x25519"]).splitlines() if ":" in l)
priv = kp["PrivateKey"].strip(); pub = kp["Password (PublicKey)"].strip()
sid = secrets.token_hex(8)

# API 会话(新版必须先拿 CSRF token)
base = f"http://127.0.0.1:{port_}/{path_.strip('/')}/"
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
opener.open(base, timeout=TIMEOUT).read()
req = urllib.request.Request(base + "csrf-token",
headers={"X-Requested-With": "XMLHttpRequest"})
csrf = json.loads(opener.open(req, timeout=TIMEOUT).read())["obj"]
user, pw = load_creds()
opener.open(urllib.request.Request(base + "login",
data=json.dumps({"username": user, "password": pw}).encode(),
headers={"Content-Type": "application/json",
"X-CSRF-Token": csrf,
"X-Requested-With": "XMLHttpRequest"}), timeout=TIMEOUT).read()

def api(path, payload):
req = urllib.request.Request(base + path, data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json",
"X-CSRF-Token": csrf,
"X-Requested-With": "XMLHttpRequest"})
return json.loads(opener.open(req, timeout=TIMEOUT).read())

# 1) 建入站
stream = {
"network": "tcp", "security": "reality",
"tcpSettings": {"acceptProxyProtocol": False, "header": {"type": "none"}},
"realitySettings": {
"show": False, "xver": 0, "target": args.dest,
"serverNames": [args.dest.split(":")[0]], "privateKey": priv,
"shortIds": [sid],
"settings": {"publicKey": pub, "fingerprint": "chrome"},
},
}
r = api("panel/api/inbounds/add", {
"remark": args.remark, "enable": True, "listen": "", "port": args.port,
"protocol": "vless",
"settings": json.dumps({"clients": [], "decryption": "none"}),
"streamSettings": json.dumps(stream),
"sniffing": json.dumps({"enabled": True, "destOverride": ["http", "tls"]}),
})
ib_id = r["obj"]["id"]

# 2) 挂客户端(分叉版格式;官方版换成扁平 payload)
api("panel/api/clients/add", {
"client": {"email": args.email, "uuid": uuid,
"flow": "xtls-rprx-vision", "enable": True},
"inboundIds": [ib_id],
})

# 3) 分叉版可能需要重启才写入运行配置
subprocess.call(["systemctl", "restart", "x-ui"])

# 4) 从数据库读回真实 uuid(面板可能重新生成)
row = db(f"SELECT c.uuid FROM clients c JOIN client_inbounds ci "
f"ON c.id=ci.client_id WHERE ci.inbound_id={ib_id} AND c.email='{args.email}'")
uuid = row.strip()

ip = db("SELECT value FROM settings WHERE key='externalProxy'") or "YOUR_SERVER_IP"
link = (f"vless://{uuid}@{ip}:{args.port}?"
+ urllib.parse.urlencode({
"type": "tcp", "security": "reality", "encryption": "none",
"sni": args.dest.split(":")[0], "fp": "chrome",
"pbk": pub, "sid": sid, "flow": "xtls-rprx-vision"})
+ f"#{args.email}")
print(link)

运行:

1
sudo python3 add-vless-reality.py --port 35215 --email user-01

输出就是可直接导入客户端的链接。

六、避坑清单(TL;DR)

  1. CSRF 403:新版 3X-UI 所有写操作都要 GET /csrf-token 换 token 再带 X-CSRF-Token 头,且必须先访问首页拿 session cookie,否则 csrf-token 端点本身 404。
  2. 分叉版数据库结构:官方版客户端在 inbounds.settings JSON 里;不少分叉版拆成 clients + client_inbounds 独立表。API 返回 camelCase(streamSettings),SQLite 是 snake_case(stream_settings),读库拼链接时注意统一。
  3. 写库 ≠ 生效:分叉版存在「API 返回成功但 Xray 运行配置没刷新」的时序问题,建完后 systemctl restart x-ui + 从 /usr/local/x-ui/bin/config.json 验证,不要只看 API 返回。
  4. UUID 会被重新生成:即使 payload 里带了预生成的 uuid,部分版本面板也会自己重新生成一个——拼链接前一定从数据库读回真实值,不要用脚本里预生成的。
  5. sudo -E 丢环境变量:用 sudo python3 script.py 跑且凭证在环境变量里时,要 sudo -E 或改从 env 文件读,否则静默回退到 admin/admin 然后登录失败。
  6. Python 3.6 服务器:老系统(CentOS/Alibaba Cloud Linux)自带 Python 可能不支持 subprocess.run(capture_output=),用 Popen + communicate() 代替。
  7. 数据库目录 700:/etc/x-ui/ 只有 root 能读,非 root 脚本读不到 db,要么 sudo 执行要么借 sudo sqlite3 只读查询。
  8. 公网 IP 探测:服务器不一定能访问 ipify 这类服务,从 install-result.env 的 XUI_ACCESS_URL 里解析最稳。
  9. pbk vs privateKey:链接里给客户端的是 PublicKey(pbk),privateKey 只配服务端。搞反了客户端永远握手失败。
  10. 安全组:面板端口和节点端口都要在云控制台安全组放行,OS 层 iptables 全开不代表云防火墙放行——「本地 curl 通、手机连不上」九成是这个。
  11. Reality 监听非 443 端口:Xray 会在日志里警告「非 443 端口增加被封锁概率」。有条件尽量用 443,或套一层转发。
  12. 敏感文件:install-result.env、数据库、脚本输出里全是凭据,chmod 600 + 不要进 git。

七、安全加固建议

  • 面板别裸奔公网:改随机高位端口 + 长路径只是延迟被扫,上证书(面板自带 Let’s Encrypt,80 端口留空即可)或干脆只听 127.0.0.1 走 SSH 隧道访问
  • 定期 x-ui 菜单里升级(版本跨度大时先备份数据库:cp /etc/x-ui/x-ui.db ~/x-ui.db.bak)
  • fail2ban 装面板时默认带,确认 x-ui banlog 有记录即可
  • 节点流量异常时先看 client_traffics 表,up/down 字段一目了然

写在最后

整套流程跑通后,「加一个节点」从打开面板点半天,变成了一条命令的事。把创建、验证、拼链接都交给脚本,人只负责把链接发给该发的人——这才是自建节点应有的体验。

链接即凭据,保管好你的 vless://。

InstaBox 沙箱实战:从命令行到浏览器自动化的完整经验

AI

InstaBox 这类云沙箱服务很适合跑”需要真实浏览器”的任务:登录网站、操作页面、抓取渲染后的内容。但免费套餐的限制、无浏览器服务器上的登录死循环、sudo 的语法怪癖、exec 会话杀后台进程……每一个都能卡住新手半小时。本文把一轮完整实战中踩过的坑和最终跑通的方案整理成文:从安装登录,到在沙箱里架起 noVNC 让手机实时看到浏览器,再到用 Chrome DevTools 协议程序化操控页面,最后附上 Salesforce Trailhead 元数据自动化的避坑笔记。

CodeBuddy Telegram 通道实战:让无人值守的 AI 编程助手可见、可控、可恢复

AI

把 CodeBuddy CLI 部署在云服务器上、通过 Telegram 远程指挥它干活,是”随身 AI 编程助手”的经典玩法。但真实跑起来后你会发现:通道正常 ≠ 好用。本文记录一次完整的故障排查与改造过程——从”发消息石沉大海”到”每一步思考实时推送到手机”,期间踩过的坑(伪终端注入误区、硬编码审批弹窗、多层 SSH 引号)都是通用经验,适用于任何类似的 CLI-agent + 消息桥接架构。