#!/usr/bin/env python3 """根据 data/nodes.db 与 .env 生成 Xray VLESS+Reality 配置(443 端口)。""" from __future__ import annotations import json import os import sqlite3 import subprocess import sys from pathlib import Path ROOT = Path(os.environ.get("JIEDIAN_ROOT", Path(__file__).resolve().parents[1])) ENV_FILE = ROOT / ".env" DB_FILE = ROOT / "data" / "nodes.db" OUT_FILE = Path("/usr/local/etc/xray/config.json") ACCESS_LOG = Path("/var/log/xray/access.log") def load_env(path: Path) -> dict[str, str]: env: dict[str, str] = {} if not path.exists(): raise SystemExit(f"缺少 .env: {path}") for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") env[key.strip()] = value.strip() return env def load_nodes(db_path: Path) -> list[dict]: if not db_path.exists(): raise SystemExit(f"缺少节点数据库: {db_path},请先运行 install.sh") conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row rows = conn.execute( "SELECT id, name, uuid, hy2_password FROM nodes WHERE enabled = 1 ORDER BY id" ).fetchall() conn.close() if not rows: raise SystemExit("没有可用节点,请在管理面板中添加节点") return [dict(row) for row in rows] def build_config(env: dict[str, str], nodes: list[dict]) -> dict: required = [ "REALITY_PRIVATE_KEY", "REALITY_SHORT_ID", "REALITY_SERVER_NAME", ] for key in required: if not env.get(key): raise SystemExit(f".env 缺少 {key}") short_id = env["REALITY_SHORT_ID"] clients = [ {"id": node["uuid"], "flow": "xtls-rprx-vision", "email": node["uuid"]} for node in nodes ] return { "log": { "access": str(ACCESS_LOG), "loglevel": "warning", }, "inbounds": [ { "listen": "0.0.0.0", "port": 443, "protocol": "vless", "settings": { "clients": clients, "decryption": "none", }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "show": False, "dest": f"{env['REALITY_SERVER_NAME']}:443", "xver": 0, "serverNames": [env["REALITY_SERVER_NAME"]], "privateKey": env["REALITY_PRIVATE_KEY"], "shortIds": ["", short_id], }, }, "sniffing": { "enabled": True, "destOverride": ["http", "tls", "quic"], }, } ], "outbounds": [ {"protocol": "freedom", "tag": "direct"}, {"protocol": "blackhole", "tag": "block"}, ], "routing": { "rules": [ { "type": "field", "ip": ["geoip:private"], "outboundTag": "block", } ] }, } def main() -> None: env = load_env(ENV_FILE) nodes = load_nodes(DB_FILE) config = build_config(env, nodes) OUT_FILE.parent.mkdir(parents=True, exist_ok=True) ACCESS_LOG.parent.mkdir(parents=True, exist_ok=True) OUT_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") xray = subprocess.run( ["xray", "run", "-test", "-c", str(OUT_FILE)], capture_output=True, text=True, ) if xray.returncode != 0: sys.stderr.write(xray.stderr or xray.stdout) raise SystemExit(xray.returncode) print(f"已生成 {OUT_FILE}({len(nodes)} 个 VLESS 用户)") if __name__ == "__main__": main()