Serve per-client clash.yaml dynamically via mtr-backend
- clash.yaml template: proxy-provider URL uses ${SUB_ID} placeholder
- install_clash_sub() saves to clash.yaml.tpl (DOMAIN/SUB_PATH substituted, SUB_ID left)
- mtr-backend GET /api/clash?sub_id=xxx reads .tpl, fills in subscription ID, returns YAML
- nginx regex ^/sub_path/(?<clash_sub_id>[^/]*)$ proxies Clash UA to
mtr-backend /api/clash?sub_id=$clash_sub_id; regular clients pass through to x-ui
- Removed static /__clash_sub internal location
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d9a51d7199
commit
9bb6ed5d5c
@@ -19,7 +19,7 @@ lgbm-url: "https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/
|
|||||||
proxy-providers:
|
proxy-providers:
|
||||||
sub:
|
sub:
|
||||||
type: http
|
type: http
|
||||||
url: https://${DOMAIN}/${SUB_PATH}/
|
url: https://${DOMAIN}/${SUB_PATH}/${SUB_ID}
|
||||||
path: ./proxy_providers/base64.yml
|
path: ./proxy_providers/base64.yml
|
||||||
interval: 3600
|
interval: 3600
|
||||||
health-check:
|
health-check:
|
||||||
|
|||||||
@@ -180,10 +180,38 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
return ip
|
return ip
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == "/health":
|
parsed = urlparse(self.path)
|
||||||
|
|
||||||
|
if parsed.path == "/health":
|
||||||
self._send_json(200, {"ok": True})
|
self._send_json(200, {"ok": True})
|
||||||
else:
|
return
|
||||||
self._send_json(404, {"error": "not found"})
|
|
||||||
|
# ── Clash subscription generator ───────────────────────────────────────
|
||||||
|
if parsed.path == "/api/clash" or parsed.path.endswith("/api/clash"):
|
||||||
|
tpl_path = "/var/www/subpage/clash.yaml.tpl"
|
||||||
|
if not os.path.isfile(tpl_path):
|
||||||
|
self._send_json(404, {"error": "clash template not found"})
|
||||||
|
return
|
||||||
|
params = parse_qs(parsed.query)
|
||||||
|
sub_id = params.get("sub_id", [""])[0].strip()
|
||||||
|
try:
|
||||||
|
with open(tpl_path, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
content = content.replace("${SUB_ID}", sub_id)
|
||||||
|
payload = content.encode("utf-8")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/yaml; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(payload)))
|
||||||
|
self.send_header("Content-Disposition", "attachment; filename=clash.yaml")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(payload)
|
||||||
|
except Exception as exc:
|
||||||
|
log.error("clash template error: %s", exc)
|
||||||
|
self._send_json(500, {"error": "internal error"})
|
||||||
|
return
|
||||||
|
|
||||||
|
self._send_json(404, {"error": "not found"})
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
parsed = urlparse(self.path)
|
parsed = urlparse(self.path)
|
||||||
|
|||||||
+17
-20
@@ -307,19 +307,25 @@ EOF
|
|||||||
|
|
||||||
# Shared proxy locations for xray inbounds (included by both vhosts)
|
# Shared proxy locations for xray inbounds (included by both vhosts)
|
||||||
cat > /etc/nginx/snippets/includes.conf <<EOF
|
cat > /etc/nginx/snippets/includes.conf <<EOF
|
||||||
#Subscription (plain/encode) — Clash/Mihomo clients get static clash.yaml by UA
|
#Subscription (plain/encode) — Clash/Mihomo clients get per-email dynamic clash.yaml
|
||||||
location /${sub_path} {
|
location = /${sub_path} {
|
||||||
if (\$hack = 1) { return 404; }
|
if (\$hack = 1) { return 404; }
|
||||||
if (\$is_clash_client = 1) { rewrite ^ /__clash_sub last; }
|
if (\$is_clash_client = 1) {
|
||||||
|
proxy_pass http://127.0.0.1:${mtr_backend_port}/api/clash;
|
||||||
|
break;
|
||||||
|
}
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_set_header Host \$host;
|
proxy_set_header Host \$host;
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||||
proxy_pass https://127.0.0.1:${sub_port};
|
proxy_pass https://127.0.0.1:${sub_port};
|
||||||
}
|
}
|
||||||
location /${sub_path}/ {
|
location ~ ^/${sub_path}/(?<clash_sub_id>[^/]*)$ {
|
||||||
if (\$hack = 1) { return 404; }
|
if (\$hack = 1) { return 404; }
|
||||||
if (\$is_clash_client = 1) { rewrite ^ /__clash_sub last; }
|
if (\$is_clash_client = 1) {
|
||||||
|
proxy_pass http://127.0.0.1:${mtr_backend_port}/api/clash?sub_id=\$clash_sub_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_set_header Host \$host;
|
proxy_set_header Host \$host;
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
proxy_set_header X-Real-IP \$remote_addr;
|
||||||
@@ -501,16 +507,6 @@ server {
|
|||||||
add_header Content-Disposition "attachment" always;
|
add_header Content-Disposition "attachment" always;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Clash subscription — internal static file served by UA routing ──────────
|
|
||||||
location = /__clash_sub {
|
|
||||||
internal;
|
|
||||||
default_type text/plain;
|
|
||||||
alias /var/www/subpage/clash.yaml;
|
|
||||||
add_header Content-Type "text/yaml; charset=utf-8" always;
|
|
||||||
add_header Content-Disposition "attachment; filename=clash.yaml" always;
|
|
||||||
add_header Cache-Control "no-store" always;
|
|
||||||
}
|
|
||||||
|
|
||||||
include /etc/nginx/snippets/includes.conf;
|
include /etc/nginx/snippets/includes.conf;
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
@@ -875,12 +871,13 @@ EOF
|
|||||||
install_clash_sub() {
|
install_clash_sub() {
|
||||||
local clash_dir="/var/www/subpage"
|
local clash_dir="/var/www/subpage"
|
||||||
mkdir -p "${clash_dir}"
|
mkdir -p "${clash_dir}"
|
||||||
if curl -fsSL "${GITHUB_RAW}/assets/clash/clash.yaml" -o "${clash_dir}/clash.yaml"; then
|
if curl -fsSL "${GITHUB_RAW}/assets/clash/clash.yaml" -o "${clash_dir}/clash.yaml.tpl"; then
|
||||||
sed -i "s|\${DOMAIN}|${domain}|g" "${clash_dir}/clash.yaml"
|
# Substitute domain and sub_path; leave ${EMAIL} for mtr-backend to fill per-request
|
||||||
sed -i "s|\${SUB_PATH}|${sub_path}|g" "${clash_dir}/clash.yaml"
|
sed -i "s|\${DOMAIN}|${domain}|g" "${clash_dir}/clash.yaml.tpl"
|
||||||
|
sed -i "s|\${SUB_PATH}|${sub_path}|g" "${clash_dir}/clash.yaml.tpl"
|
||||||
chown -R www-data:www-data "${clash_dir}" 2>/dev/null || true
|
chown -R www-data:www-data "${clash_dir}" 2>/dev/null || true
|
||||||
chmod 644 "${clash_dir}/clash.yaml"
|
chmod 644 "${clash_dir}/clash.yaml.tpl"
|
||||||
msg_ok "Clash subscription config installed."
|
msg_ok "Clash subscription template installed."
|
||||||
else
|
else
|
||||||
msg_err "Failed to download clash.yaml from GitHub."
|
msg_err "Failed to download clash.yaml from GitHub."
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user