Add Clash subscription with User-Agent routing

- clash.yaml template (proxy-providers -> x-ui base64 sub URL) in assets/clash/
- install_clash_sub() downloads and substitutes domain/sub_path at install time
- nginx: map $http_user_agent $is_clash_client detects Clash/Mihomo/Stash/Surfboard
- sub_path locations rewrite to internal /__clash_sub on UA match
- /__clash_sub serves /var/www/subpage/clash.yaml as text/yaml
- Regular clients still proxy to x-ui sub port unchanged
- Uninstall cleans /var/www/subpage/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ivan Razin
2026-06-24 13:29:16 +03:00
co-authored by Claude Sonnet 4.6
parent c548d9934a
commit 138e41574c
2 changed files with 129 additions and 2 deletions
+92
View File
@@ -0,0 +1,92 @@
mixed-port: 7890
allow-lan: false
log-level: info
ipv6: false
mode: rule
dns:
enable: true
use-hosts: true
ipv6: false
enhanced-mode: redir-host
listen: 127.0.0.1:6868
default-nameserver:
- 1.1.1.1
- 8.8.8.8
- 1.0.0.1
nameserver:
- https://1.1.1.1/dns-query#PROXY
- https://8.8.8.8/dns-query#PROXY
- https://1.0.0.1/dns-query#PROXY
- 8.8.8.8
- 1.1.1.1
profile:
store-selected: true
proxy-groups:
- name: PROXY
proxies:
- auto
use:
- sub
type: select
- name: auto
use:
- sub
type: url-test
url: http://cp.cloudflare.com
interval: 300
tolerance: 150
lazy: true
proxy-providers:
sub:
type: http
url: https://${DOMAIN}/${SUB_PATH}/
path: ./proxy_providers/base64.yml
interval: 3600
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 300
timeout: 5000
lazy: true
expected-status: 204
rule-providers:
ru-bundle:
type: http
behavior: domain
format: mrs
url: https://github.com/legiz-ru/mihomo-rule-sets/raw/main/ru-bundle/rule.mrs
path: ./ru-bundle/rule.mrs
interval: 86400
rules:
- PROCESS-NAME,Discord.exe,PROXY
- PROCESS-NAME,com.supercell.clashofclans,PROXY
- PROCESS-NAME,com.supercell.brawlstars,PROXY
- RULE-SET,ru-bundle,PROXY
- MATCH,DIRECT
sniffer:
enable: true
force-dns-mapping: true
parse-pure-ip: true
sniff:
HTTP:
ports: [80, 8080-8880]
override-destination: true
TLS:
ports: [443, 8443]
tun:
enable: true
stack: system
dns-hijack:
- any:53
auto-redir: true
auto-route: true
auto-detect-interface: true
+37 -2
View File
@@ -141,7 +141,7 @@ uninstall_xui() {
$Pak -y purge nginx nginx-common nginx-core nginx-full python3-certbot-nginx $Pak -y purge nginx nginx-common nginx-core nginx-full python3-certbot-nginx
$Pak -y autoremove $Pak -y autoremove
$Pak -y autoclean $Pak -y autoclean
rm -rf /var/www/html/ /var/www/diagnostics/ /etc/nginx/ /usr/share/nginx/ rm -rf /var/www/html/ /var/www/diagnostics/ /var/www/subpage/ /etc/nginx/ /usr/share/nginx/
systemctl stop mtr-backend 2>/dev/null || true systemctl stop mtr-backend 2>/dev/null || true
systemctl disable mtr-backend 2>/dev/null || true systemctl disable mtr-backend 2>/dev/null || true
rm -f /etc/systemd/system/mtr-backend.service rm -f /etc/systemd/system/mtr-backend.service
@@ -307,9 +307,10 @@ 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) #Subscription (plain/encode) — Clash/Mihomo clients get static clash.yaml by UA
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; }
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;
@@ -318,6 +319,7 @@ EOF
} }
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; }
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;
@@ -401,6 +403,12 @@ limit_req_zone \$binary_remote_addr zone=diag_api:10m rate=6r/m;
limit_req_zone \$binary_remote_addr zone=diag_page:10m rate=30r/m; limit_req_zone \$binary_remote_addr zone=diag_page:10m rate=30r/m;
limit_conn_zone \$binary_remote_addr zone=per_ip:10m; limit_conn_zone \$binary_remote_addr zone=per_ip:10m;
# Detect Clash/Mihomo subscription clients by User-Agent
map \$http_user_agent \$is_clash_client {
~*(clash|clashx|clashn|mihomo|stash|surfboard) 1;
default 0;
}
server { server {
server_tokens off; server_tokens off;
server_name ${domain}; server_name ${domain};
@@ -493,6 +501,16 @@ 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
@@ -854,6 +872,20 @@ EOF
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# INSTALL FAKE SITE # INSTALL FAKE SITE
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
install_clash_sub() {
local clash_dir="/var/www/subpage"
mkdir -p "${clash_dir}"
if curl -fsSL "${GITHUB_RAW}/assets/clash/clash.yaml" -o "${clash_dir}/clash.yaml"; then
sed -i "s|\${DOMAIN}|${domain}|g" "${clash_dir}/clash.yaml"
sed -i "s|\${SUB_PATH}|${sub_path}|g" "${clash_dir}/clash.yaml"
chown -R www-data:www-data "${clash_dir}" 2>/dev/null || true
chmod 644 "${clash_dir}/clash.yaml"
msg_ok "Clash subscription config installed."
else
msg_err "Failed to download clash.yaml from GitHub."
fi
}
install_fake_site() { install_fake_site() {
local idx=$(( (RANDOM % FAKE_SITE_COUNT) + 1 )) local idx=$(( (RANDOM % FAKE_SITE_COUNT) + 1 ))
local site_id local site_id
@@ -1011,6 +1043,8 @@ show_results() {
echo -e "Username: ${config_username}\n" echo -e "Username: ${config_username}\n"
echo -e "Password: ${config_password}\n" echo -e "Password: ${config_password}\n"
msg_inf "────────────────────────────────────────────────────────────────────────────────" msg_inf "────────────────────────────────────────────────────────────────────────────────"
msg_inf "Clash Sub (auto by UA): https://${domain}/${sub_path}/\n"
msg_inf "────────────────────────────────────────────────────────────────────────────────"
msg_inf "Network Diagnostics: https://${domain}${diag_path}\n" msg_inf "Network Diagnostics: https://${domain}${diag_path}\n"
msg_inf "────────────────────────────────────────────────────────────────────────────────" msg_inf "────────────────────────────────────────────────────────────────────────────────"
msg_inf "Please save this screen!" msg_inf "Please save this screen!"
@@ -1038,6 +1072,7 @@ main() {
configure_nginx configure_nginx
configure_xui_db configure_xui_db
install_clash_sub
install_fake_site install_fake_site
install_diagnostics install_diagnostics
tune_system tune_system