feat(dual-link-proxy): 一键安装脚本,空机器一条命令装完链路与面板
- 新增 scripts/install-dual-link.sh:装 3x-ui → 建两条 VLESS+WS 入站(绑 127.0.0.1) → 80 vhost 完成 ACME → 签证书 → 切 443 → 可选面板子域 + Basic Auth → 实测 101 并打印链接; 支持 --dry-run 干跑、--yes 无人值守、已有 3x-ui 时复用而不重置密码 - 3xui-api.py 补 uuid / link / del-inbound 三个子命令,add 的 --uuid 改为可选 - verify-links.sh 统计实际检查项,空跑以退出码 2 报错,避免假通过 - 避坑清单补三条脚本类实测:ss 端口判定的字段误用、dry-run 仍落盘、验收空跑
This commit is contained in:
1 parent
cc86680e05
commit
b2016af916
5 files changed
+655
-5
No files matched your search
@@ -84,6 +84,7 @@ def api_headers(token):
|
||||
|
||||
def build_inbound(remark, port, path, uuid, listen="127.0.0.1", email=None):
|
||||
email = email or remark
|
||||
uuid = uuid or str(uuidlib.uuid4())
|
||||
return {
|
||||
"up": 0,
|
||||
"down": 0,
|
||||
@@ -167,8 +168,10 @@ def cmd_add(op, token, base, args):
|
||||
if not data.get("success"):
|
||||
sys.exit("建入站失败:%s" % data.get("msg"))
|
||||
obj = data.get("obj") or {}
|
||||
print("已创建 id=%s 备注=%s 端口=%s 路径=%s"
|
||||
% (obj.get("id"), args.remark, args.port, args.path))
|
||||
# 服务端可能自行生成 UUID,回读一次保证打印的就是实际的
|
||||
ib, uid = find_inbound_uuid(fetch_inbounds(op, base), args.remark, args.email or args.remark)
|
||||
print("已创建 id=%s 备注=%s 端口=%s 路径=%s UUID=%s"
|
||||
% (obj.get("id") or (ib or {}).get("id"), args.remark, args.port, args.path, uid or "(回读失败)"))
|
||||
|
||||
|
||||
def fetch_inbounds(op, base):
|
||||
@@ -233,6 +236,57 @@ def cmd_add_user(op, token, base, args):
|
||||
print("已挂到入站:" + "、".join(names))
|
||||
|
||||
|
||||
def find_inbound_uuid(items, remark, email):
|
||||
for ib in items:
|
||||
if remark and ib.get("remark") != remark:
|
||||
continue
|
||||
for c in (as_obj(ib.get("settings")).get("clients") or []):
|
||||
if c.get("email") == email:
|
||||
return ib, (c.get("id") or "")
|
||||
return None, ""
|
||||
|
||||
|
||||
def cmd_uuid(op, token, base, args):
|
||||
ib, uid = find_inbound_uuid(fetch_inbounds(op, base), args.inbound, args.email)
|
||||
if not ib or not uid:
|
||||
sys.exit("没找到 %s 在入站 %s 里的客户端" % (args.email, args.inbound))
|
||||
print(uid)
|
||||
|
||||
|
||||
def cmd_link(op, token, base, args):
|
||||
ib, uid = find_inbound_uuid(fetch_inbounds(op, base), args.inbound, args.email)
|
||||
if not ib or not uid:
|
||||
sys.exit("没找到 %s 在入站 %s 里的客户端" % (args.email, args.inbound))
|
||||
path = (as_obj(ib.get("streamSettings")).get("wsSettings") or {}).get("path") or "/"
|
||||
query = urllib.parse.urlencode(
|
||||
[
|
||||
("encryption", "none"),
|
||||
("security", "tls"),
|
||||
("sni", args.domain),
|
||||
("alpn", "http/1.1"),
|
||||
("type", "ws"),
|
||||
("host", args.domain),
|
||||
("path", path),
|
||||
("fp", "chrome"),
|
||||
],
|
||||
quote_via=urllib.parse.quote,
|
||||
)
|
||||
frag = args.fragment or ib.get("remark") or "link"
|
||||
print("vless://%s@%s:%s?%s#%s" % (uid, args.domain, args.port, query, frag))
|
||||
|
||||
|
||||
def cmd_del_inbound(op, token, base, args):
|
||||
items = [ib for ib in fetch_inbounds(op, base) if ib.get("remark") == args.inbound]
|
||||
if not items:
|
||||
sys.exit("没有备注为 %s 的入站" % args.inbound)
|
||||
for ib in items:
|
||||
data = api_post(op, token, base, "/panel/api/inbounds/del/%s" % ib.get("id"), {},
|
||||
referer="/panel/inbounds")
|
||||
if not data.get("success"):
|
||||
sys.exit("删除入站 %s 失败:%s" % (ib.get("remark"), data.get("msg")))
|
||||
print("已删除入站 id=%s 备注=%s 端口=%s" % (ib.get("id"), ib.get("remark"), ib.get("port")))
|
||||
|
||||
|
||||
def cmd_del_user(op, token, base, args):
|
||||
data = api_post(
|
||||
op, token, base,
|
||||
@@ -258,10 +312,13 @@ def main():
|
||||
a.add_argument("--remark", required=True)
|
||||
a.add_argument("--port", required=True, type=int)
|
||||
a.add_argument("--path", required=True)
|
||||
a.add_argument("--uuid", required=True)
|
||||
a.add_argument("--uuid", default=None, help="不填则自动生成")
|
||||
a.add_argument("--listen", default="127.0.0.1")
|
||||
a.add_argument("--email", default=None)
|
||||
|
||||
u = sub.add_parser("del-inbound", help="按备注删掉整条入站")
|
||||
u.add_argument("--inbound", required=True)
|
||||
|
||||
u = sub.add_parser("add-user", help="给所有入站加同一个用户,一人两条链路")
|
||||
u.add_argument("--email", required=True)
|
||||
u.add_argument("--uuid", default=None)
|
||||
@@ -271,6 +328,17 @@ def main():
|
||||
d.add_argument("--email", required=True)
|
||||
d.add_argument("--inbound", default=None)
|
||||
|
||||
g = sub.add_parser("uuid", help="打印某入站某账号的真实 UUID")
|
||||
g.add_argument("--inbound", required=True)
|
||||
g.add_argument("--email", required=True)
|
||||
|
||||
k = sub.add_parser("link", help="直接拼出可导入客户端的 vless 链接")
|
||||
k.add_argument("--inbound", required=True, help="入站备注名")
|
||||
k.add_argument("--email", required=True, help="账号标识")
|
||||
k.add_argument("--domain", required=True)
|
||||
k.add_argument("--port", default="443")
|
||||
k.add_argument("--fragment", default=None, help="客户端里显示的名字")
|
||||
|
||||
args = p.parse_args()
|
||||
if not args.cmd:
|
||||
p.error("缺少子命令:ping / list / add")
|
||||
@@ -285,6 +353,12 @@ def main():
|
||||
cmd_add_user(op, token, args.base, args)
|
||||
elif args.cmd == "del-user":
|
||||
cmd_del_user(op, token, args.base, args)
|
||||
elif args.cmd == "del-inbound":
|
||||
cmd_del_inbound(op, token, args.base, args)
|
||||
elif args.cmd == "uuid":
|
||||
cmd_uuid(op, token, args.base, args)
|
||||
elif args.cmd == "link":
|
||||
cmd_link(op, token, args.base, args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user