fix(sub): randomize fresh panel subscription paths (#6375)

* fix(sub): randomize fresh panel subscription paths

Seed distinct cryptographically random paths for base64, JSON, and Clash subscriptions when a panel database is first created. Persist them so restarts keep published URLs stable while upgrades preserve existing settings.

Generated-by: OpenCode:gpt-5.6-sol

* fix(sub): regenerate paths on settings reset

Keep subscription paths unpredictable after a factory reset, close the test database on failure, and update the builder, OpenAPI, and localized docs to describe panel-specific paths instead of obsolete fixed defaults.

Generated-by: OpenCode:gpt-5.6-sol
This commit is contained in:
ilyusha
2026-09-03 17:34:37 +03:00
committed by GitHub
parent ded2aa150c
commit f9898e0b24
20 changed files with 285 additions and 158 deletions
+6 -6
View File
@@ -18,7 +18,7 @@ icon: Rss
| ------------- | ------- | --------------------------------------------------------------- |
| `subPort` | `2096` | پورت گوش‌دادن (جدا از پنل). |
| `subListen` | _(همه)_ | آدرس اتصال (bind). |
| `subPath` | `/sub/` | مسیر پایه برای URLهای خام اشتراک. |
| `subPath` | _(تصادفی برای هر پنل)_ | مسیر پایه برای URLهای خام اشتراک. |
| `subDomain` | _(هیچ)_ | میزبان عمومی؛ اگر تنظیم شود، سرور فقط به همان Host پاسخ می‌دهد. |
| `subCertFile` / `subKeyFile` | _(هیچ)_ | گواهی و کلید TLS — هنگام تنظیم، سرور **HTTPS** ارائه می‌دهد. |
| `subEncrypt` | `true` | بدنه‌ی خام اشتراک را با base64 رمزگذاری می‌کند. |
@@ -27,7 +27,7 @@ icon: Rss
یک URL اشتراک به این شکل است:
```text
https://<sub-host>:<sub-port>/sub/<sub-id>
https://<sub-host>:<sub-port>/<sub-path>/<sub-id>
```
که در آن `<sub-id>` همان **Sub ID** کلاینت است.
@@ -44,13 +44,13 @@ https://<sub-host>:<sub-port>/sub/<sub-id>
| Format | Path | Enabled by | Output |
| --------------------- | --------- | ---------------- | --------------------------------------------------- |
| **لینک‌های خام** | `/sub/` | همیشه (اگر روشن باشد) | فهرستی از لینک‌های `vless://`، `vmess://`، … (هنگام فعال‌بودن `subEncrypt` با base64 رمزگذاری می‌شود). |
| **JSON** | `/json/` | `subJsonEnable` | پیکربندی(های) کامل کلاینت Xray. |
| **Clash / Mihomo** | `/clash/` | `subClashEnable` | پروفایل YAML. |
| **لینک‌های خام** | `subPath` | همیشه (اگر روشن باشد) | فهرستی از لینک‌های `vless://`، `vmess://`، … (هنگام فعال‌بودن `subEncrypt` با base64 رمزگذاری می‌شود). |
| **JSON** | `subJsonPath` | `subJsonEnable` | پیکربندی(های) کامل کلاینت Xray. |
| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | پروفایل YAML. |
فقط ورودی‌های فعالی که از **VLESS، VMess، Trojan، Shadowsocks یا Hysteria2**
استفاده می‌کنند در یک اشتراک ظاهر می‌شوند و بر اساس شاخص sub-sort آن‌ها مرتب می‌شوند.
درخواست `/sub/` همراه با هدر `Accept: text/html` (یا `?html=1`) به‌جای بدنه‌ی خام،
درخواست `subPath` همراه با هدر `Accept: text/html` (یا `?html=1`) به‌جای بدنه‌ی خام،
یک صفحه‌ی اطلاعات خوانا برای انسان برمی‌گرداند.
### Base64 vs JSON
+11 -9
View File
@@ -311,11 +311,12 @@ _openapi:
title: >-
Return every protocol URL (vless://, vmess://, trojan://, ss://,
hysteria://, hy2://) for clients matching the subscription ID. Same
result set as /sub/<subId>, but as a JSON array — no base64. When an
inbound has streamSettings.externalProxy set, one URL is emitted per
external proxy. Empty array when the subId has no enabled clients.
result set as the configured subPath endpoint, but as a JSON array — no
base64. When an inbound has streamSettings.externalProxy set, one URL is
emitted per external proxy. Empty array when the subId has no enabled
clients.
url: >-
#return-every-protocol-url-vless-vmess-trojan-ss-hysteria-hy2-for-clients-matching-the-subscription-id-same-result-set-as-subsubid-but-as-a-json-array--no-base64-when-an-inbound-has-streamsettingsexternalproxy-set-one-url-is-emitted-per-external-proxy-empty-array-when-the-subid-has-no-enabled-clients
#return-every-protocol-url-vless-vmess-trojan-ss-hysteria-hy2-for-clients-matching-the-subscription-id-same-result-set-as-the-configured-subpath-endpoint-but-as-a-json-array--no-base64-when-an-inbound-has-streamsettingsexternalproxy-set-one-url-is-emitted-per-external-proxy-empty-array-when-the-subid-has-no-enabled-clients
- depth: 2
title: >-
Return every URL for one client across all attached inbounds — the same
@@ -593,11 +594,12 @@ _openapi:
- content: >-
Return every protocol URL (vless://, vmess://, trojan://, ss://,
hysteria://, hy2://) for clients matching the subscription ID. Same
result set as /sub/<subId>, but as a JSON array — no base64. When an
inbound has streamSettings.externalProxy set, one URL is emitted per
external proxy. Empty array when the subId has no enabled clients.
result set as the configured subPath endpoint, but as a JSON array —
no base64. When an inbound has streamSettings.externalProxy set, one
URL is emitted per external proxy. Empty array when the subId has no
enabled clients.
id: >-
return-every-protocol-url-vless-vmess-trojan-ss-hysteria-hy2-for-clients-matching-the-subscription-id-same-result-set-as-subsubid-but-as-a-json-array--no-base64-when-an-inbound-has-streamsettingsexternalproxy-set-one-url-is-emitted-per-external-proxy-empty-array-when-the-subid-has-no-enabled-clients
return-every-protocol-url-vless-vmess-trojan-ss-hysteria-hy2-for-clients-matching-the-subscription-id-same-result-set-as-the-configured-subpath-endpoint-but-as-a-json-array--no-base64-when-an-inbound-has-streamsettingsexternalproxy-set-one-url-is-emitted-per-external-proxy-empty-array-when-the-subid-has-no-enabled-clients
- content: >-
Return every URL for one client across all attached inbounds — the
same strings the Copy URL button copies in the panel UI. Supported
@@ -672,4 +674,4 @@ export default function Layout(props) {
<Comp document="./public/openapi.json" webhooks={[]} operations={[{"path":"/panel/api/clients/list","method":"get"},{"path":"/panel/api/clients/list/paged","method":"get"},{"path":"/panel/api/clients/get/{email}","method":"get"},{"path":"/panel/api/clients/add","method":"post"},{"path":"/panel/api/clients/update/{email}","method":"post"},{"path":"/panel/api/clients/del/{email}","method":"post"},{"path":"/panel/api/clients/{email}/attach","method":"post"},{"path":"/panel/api/clients/{email}/detach","method":"post"},{"path":"/panel/api/clients/{email}/externalLinks","method":"post"},{"path":"/panel/api/clients/resetAllTraffics","method":"post"},{"path":"/panel/api/clients/delDepleted","method":"post"},{"path":"/panel/api/clients/delOrphans","method":"post"},{"path":"/panel/api/clients/export","method":"get"},{"path":"/panel/api/clients/import","method":"post"},{"path":"/panel/api/clients/bulkAdjust","method":"post"},{"path":"/panel/api/clients/bulkEnable","method":"post"},{"path":"/panel/api/clients/bulkDisable","method":"post"},{"path":"/panel/api/clients/bulkDel","method":"post"},{"path":"/panel/api/clients/bulkCreate","method":"post"},{"path":"/panel/api/clients/groups/bulkAdd","method":"post"},{"path":"/panel/api/clients/groups/bulkRemove","method":"post"},{"path":"/panel/api/clients/bulkAttach","method":"post"},{"path":"/panel/api/clients/bulkDetach","method":"post"},{"path":"/panel/api/clients/bulkResetTraffic","method":"post"},{"path":"/panel/api/clients/groups","method":"get"},{"path":"/panel/api/clients/groups/{name}/emails","method":"get"},{"path":"/panel/api/clients/groups/create","method":"post"},{"path":"/panel/api/clients/groups/rename","method":"post"},{"path":"/panel/api/clients/groups/delete","method":"post"},{"path":"/panel/api/clients/resetTraffic/{email}","method":"post"},{"path":"/panel/api/clients/updateTraffic/{email}","method":"post"},{"path":"/panel/api/clients/ips/{email}","method":"post"},{"path":"/panel/api/clients/clearIps/{email}","method":"post"},{"path":"/panel/api/clients/onlines","method":"post"},{"path":"/panel/api/clients/onlinesByGuid","method":"post"},{"path":"/panel/api/clients/clientIpsByGuid","method":"post"},{"path":"/panel/api/clients/activeInbounds","method":"post"},{"path":"/panel/api/clients/lastOnline","method":"post"},{"path":"/panel/api/clients/traffic/{email}","method":"get"},{"path":"/panel/api/clients/subLinks/{subId}","method":"get"},{"path":"/panel/api/clients/links/{email}","method":"get"}]} showTitle />
</>
);
}
}
@@ -3,10 +3,10 @@ title: سرور اشتراک
description: >-
یک سرور HTTP/HTTPS جداگانه که لینک‌های اشتراک پراکسی (استاندارد، JSON و Clash)
را به کلاینت‌ها ارائه می‌دهد. این سرور روی پورت اختصاصی خودش (به‌صورت پیش‌فرض
10882) گوش می‌دهد و در بخش Settings ← Subscription پیکربندی می‌شود. مسیرها قابل
پیکربندی هستند؛ مقادیر پیش‌فرض در ادامه نشان داده شده‌اند. همه‌ی نقاط پایانی
اشتراک، هدرهای پاسخ را برای خواندن اطلاعات ترافیک/انقضا توسط برنامه‌های کلاینت
تنظیم می‌کنند.
2096) گوش می‌دهد و در بخش Settings ← Subscription پیکربندی می‌شود. پنل‌های جدید
برای هر قالب پیشوند مسیر تصادفی تولید می‌کنند و همه‌ی مسیرها قابل پیکربندی
می‌مانند. همه‌ی نقاط پایانی اشتراک، هدرهای پاسخ را برای خواندن اطلاعات
ترافیک/انقضا توسط برنامه‌های کلاینت تنظیم می‌کنند.
full: true
_openapi:
preload:
@@ -16,45 +16,46 @@ _openapi:
title: >-
Return base64-encoded subscription links for all enabled clients
matching the subscription ID. When the request has an Accept: text/html
header or ?html=1, renders a styled info page instead. Default path:
/sub/:subid.
header or ?html=1, renders a styled info page instead. The path prefix is
configured by subPath.
url: >-
#return-base64-encoded-subscription-links-for-all-enabled-clients-matching-the-subscription-id-when-the-request-has-an-accept-texthtml-header-or-html1-renders-a-styled-info-page-instead-default-path-subsubid
#return-base64-encoded-subscription-links-for-all-enabled-clients-matching-the-subscription-id-when-the-request-has-an-accept-texthtml-header-or-html1-renders-a-styled-info-page-instead-with-formatinfo-returns-the-page-view-model-as-json-traffic-expiry-online-status-no-links-for-live-polling-the-path-prefix-is-configured-by-subpath
- depth: 2
title: >-
Return subscription as a JSON array of proxy configs (one per enabled
client). Only when JSON subscription is enabled in settings. Default
path: /json/:subid.
client). Only when JSON subscription is enabled in settings. The path
prefix is configured by subJsonPath.
url: >-
#return-subscription-as-a-json-array-of-proxy-configs-one-per-enabled-client-only-when-json-subscription-is-enabled-in-settings-default-path-jsonsubid
#return-subscription-as-a-json-array-of-proxy-configs-one-per-enabled-client-only-when-json-subscription-is-enabled-in-settings-the-path-prefix-is-configured-by-subjsonpath
- depth: 2
title: >-
Return subscription as a Clash/Mihomo-compatible YAML config, including
configured global Clash routing rules. Only when Clash subscription is
enabled in settings. Default path: /clash/:subid.
enabled in settings. The path prefix is configured by subClashPath.
url: >-
#return-subscription-as-a-clashmihomo-compatible-yaml-config-including-configured-global-clash-routing-rules-only-when-clash-subscription-is-enabled-in-settings-default-path-clashsubid
#return-subscription-as-a-clashmihomo-compatible-yaml-config-including-configured-global-clash-routing-rules-only-when-clash-subscription-is-enabled-in-settings-the-path-prefix-is-configured-by-subclashpath
structuredData:
headings:
- content: >-
Return base64-encoded subscription links for all enabled clients
matching the subscription ID. When the request has an Accept:
text/html header or ?html=1, renders a styled info page instead.
Default path: /sub/:subid.
text/html header or ?html=1, renders a styled info page instead. The
path prefix is configured by subPath.
id: >-
return-base64-encoded-subscription-links-for-all-enabled-clients-matching-the-subscription-id-when-the-request-has-an-accept-texthtml-header-or-html1-renders-a-styled-info-page-instead-default-path-subsubid
return-base64-encoded-subscription-links-for-all-enabled-clients-matching-the-subscription-id-when-the-request-has-an-accept-texthtml-header-or-html1-renders-a-styled-info-page-instead-with-formatinfo-returns-the-page-view-model-as-json-traffic-expiry-online-status-no-links-for-live-polling-the-path-prefix-is-configured-by-subpath
- content: >-
Return subscription as a JSON array of proxy configs (one per enabled
client). Only when JSON subscription is enabled in settings. Default
path: /json/:subid.
client). Only when JSON subscription is enabled in settings. The path
prefix is configured by subJsonPath.
id: >-
return-subscription-as-a-json-array-of-proxy-configs-one-per-enabled-client-only-when-json-subscription-is-enabled-in-settings-default-path-jsonsubid
return-subscription-as-a-json-array-of-proxy-configs-one-per-enabled-client-only-when-json-subscription-is-enabled-in-settings-the-path-prefix-is-configured-by-subjsonpath
- content: >-
Return subscription as a Clash/Mihomo-compatible YAML config,
including configured global Clash routing rules. Only when Clash
subscription is enabled in settings. Default path: /clash/:subid.
subscription is enabled in settings. The path prefix is configured by
subClashPath.
id: >-
return-subscription-as-a-clashmihomo-compatible-yaml-config-including-configured-global-clash-routing-rules-only-when-clash-subscription-is-enabled-in-settings-default-path-clashsubid
return-subscription-as-a-clashmihomo-compatible-yaml-config-including-configured-global-clash-routing-rules-only-when-clash-subscription-is-enabled-in-settings-the-path-prefix-is-configured-by-subclashpath
contents: []
---
@@ -70,4 +71,4 @@ export default function Layout(props) {
<Comp document="./public/openapi.json" webhooks={[]} operations={[{"path":"/{subPath}{subid}","method":"get"},{"path":"/{jsonPath}{subid}","method":"get"},{"path":"/{clashPath}{subid}","method":"get"}]} showTitle />
</>
);
}
}