迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。 完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。 验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
84 lines
3.3 KiB
Python
Executable File
84 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Runs ON the box. Adds a password-authenticated SOCKS5 inbound to 3x-ui.
|
|
|
|
Reuses the existing Xray instance -- no new daemon. Xray implements SOCKS5 with
|
|
RFC 1929 username/password auth natively, so this is purely a new inbound row.
|
|
|
|
Additive and idempotent: skips if the port already exists, touches no other
|
|
inbound, writes nothing to nginx or sing-box.
|
|
|
|
Two things worth knowing about this inbound versus the other ten on this node:
|
|
|
|
* It is NOT wrapped in TLS. SOCKS5 auth per RFC 1929 sends the username and
|
|
password in CLEARTEXT, and the tunnelled traffic is unencrypted too. Anyone
|
|
on the path can read both. Raw SOCKS5 is also trivially fingerprinted. Treat
|
|
the credentials as disposable and prefer binding to 127.0.0.1 + an SSH tunnel
|
|
if the client can be pointed at a local port (see LISTEN below).
|
|
|
|
* `socks` is not one of the protocols 3x-ui projects through its clients /
|
|
client_inbounds tables (that set is vmess/vless/trojan/shadowsocks/hysteria),
|
|
so the accounts live directly in this inbound's settings JSON and the
|
|
settings.clients migration hazard documented in 08-xui-bootstrap.py fix #5
|
|
does not apply here.
|
|
|
|
Env in: SOCKS_PORT, SOCKS_USER, SOCKS_PASS, SERVER_IP
|
|
LISTEN (default 0.0.0.0; set 127.0.0.1 for SSH-tunnel-only access)
|
|
REMARK (default socks5-direct), XUI_DB
|
|
"""
|
|
import json, os, sqlite3, subprocess, sys, time
|
|
|
|
DB = os.environ.get("XUI_DB", "/etc/x-ui/x-ui.db")
|
|
PORT = int(os.environ["SOCKS_PORT"])
|
|
USER = os.environ["SOCKS_USER"]
|
|
PASS = os.environ["SOCKS_PASS"]
|
|
LISTEN = os.environ.get("LISTEN", "0.0.0.0")
|
|
REMARK = os.environ.get("REMARK", "socks5-direct")
|
|
# Xray announces this address in the UDP ASSOCIATE reply. For a remote server it
|
|
# must be the public IP, or UDP-over-SOCKS5 breaks (TCP still works either way).
|
|
SERVER_IP = os.environ.get("SERVER_IP", "127.0.0.1")
|
|
TAG = "inbound-" + REMARK
|
|
|
|
|
|
def cols(cur, table):
|
|
return {r[1] for r in cur.execute("PRAGMA table_info(%s)" % table).fetchall()}
|
|
|
|
|
|
def main():
|
|
con = sqlite3.connect(DB)
|
|
cur = con.cursor()
|
|
|
|
if cur.execute("SELECT 1 FROM inbounds WHERE port=?", (PORT,)).fetchone():
|
|
print(" inbound :%d already exists, nothing to do" % PORT)
|
|
return
|
|
|
|
row = {
|
|
"user_id": 1, # NULL user_id -> proxies fine but INVISIBLE in the panel
|
|
"up": 0, "down": 0, "total": 0, "remark": REMARK,
|
|
"enable": 1, "expiry_time": 0,
|
|
"listen": LISTEN, "port": PORT, "protocol": "socks",
|
|
"settings": json.dumps({
|
|
"auth": "password",
|
|
"accounts": [{"user": USER, "pass": PASS}],
|
|
"udp": True,
|
|
"ip": SERVER_IP,
|
|
}, indent=2),
|
|
"stream_settings": json.dumps({"network": "tcp", "security": "none"}, indent=2),
|
|
"sniffing": json.dumps({"enabled": True, "destOverride": ["http", "tls"]}),
|
|
"tag": TAG,
|
|
}
|
|
ic = cols(cur, "inbounds")
|
|
keys = [k for k in row if k in ic]
|
|
cur.execute("INSERT INTO inbounds (%s) VALUES (%s)" % (",".join(keys), ",".join("?" * len(keys))),
|
|
[row[k] for k in keys])
|
|
con.commit()
|
|
con.close()
|
|
print(" inbound :%d socks5 added (listen=%s, auth=password, udp=on)" % (PORT, LISTEN))
|
|
|
|
subprocess.run(["systemctl", "restart", "x-ui"], check=True)
|
|
time.sleep(6)
|
|
print(" x-ui restarted")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|