diff --git a/docs/components/tools/subscription-builder.tsx b/docs/components/tools/subscription-builder.tsx index 23bfe54a6..5e1e5b012 100644 --- a/docs/components/tools/subscription-builder.tsx +++ b/docs/components/tools/subscription-builder.tsx @@ -69,8 +69,8 @@ export function SubscriptionBuilder() { const [scheme, setScheme] = useState<'http' | 'https'>('https'); const [host, setHost] = useState('sub.example.com'); const [port, setPort] = useState('2096'); - const [subPath, setSubPath] = useState('/sub/'); - const [jsonPath, setJsonPath] = useState('/json/'); + const [subPath, setSubPath] = useState('/your-sub-path/'); + const [jsonPath, setJsonPath] = useState('/your-json-path/'); const [subId, setSubId] = useState('user-1'); const [behindProxy, setBehindProxy] = useState(false); const [clients, setClients] = useState(DEFAULT_CLIENTS); @@ -95,8 +95,8 @@ export function SubscriptionBuilder() { setScheme('https'); setHost('sub.example.com'); setPort('2096'); - setSubPath('/sub/'); - setJsonPath('/json/'); + setSubPath('/your-sub-path/'); + setJsonPath('/your-json-path/'); setSubId('user-1'); setBehindProxy(false); setClients(DEFAULT_CLIENTS); diff --git a/docs/content/docs/en/config/subscription.mdx b/docs/content/docs/en/config/subscription.mdx index 2e89452d9..77679d95f 100644 --- a/docs/content/docs/en/config/subscription.mdx +++ b/docs/content/docs/en/config/subscription.mdx @@ -18,7 +18,7 @@ panel's subscription settings: | ------------- | ------- | --------------------------------------------------------------- | | `subPort` | `2096` | Listen port (separate from the panel). | | `subListen` | _(all)_ | Bind address. | -| `subPath` | `/sub/` | Base path for raw subscription URLs. | +| `subPath` | _(random per panel)_ | Base path for raw subscription URLs. | | `subDomain` | _(none)_| Public host; if set, the server only answers for that Host. | | `subCertFile` / `subKeyFile` | _(none)_ | TLS cert + key — when set, the server serves **HTTPS**. | | `subEncrypt` | `true` | Base64-encode the raw subscription body. | @@ -27,7 +27,7 @@ panel's subscription settings: A subscription URL looks like: ```text -https://:/sub/ +https://:// ``` where `` is the client's **Sub ID**. @@ -44,12 +44,12 @@ The **format is chosen by path**, each with its own enable toggle: | Format | Path | Enabled by | Output | | --------------------- | --------- | ---------------- | --------------------------------------------------- | -| **Raw links** | `/sub/` | always (if on) | A list of `vless://`, `vmess://`, … links (base64-encoded when `subEncrypt` is on). | -| **JSON** | `/json/` | `subJsonEnable` | Full Xray client config(s). | -| **Clash / Mihomo** | `/clash/` | `subClashEnable` | YAML profile. | +| **Raw links** | `subPath` | always (if on) | A list of `vless://`, `vmess://`, … links (base64-encoded when `subEncrypt` is on). | +| **JSON** | `subJsonPath` | `subJsonEnable` | Full Xray client config(s). | +| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | YAML profile. | Only enabled inbounds using **VLESS, VMess, Trojan, Shadowsocks, or Hysteria2** -appear in a subscription, ordered by their sub-sort index. Requesting `/sub/` +appear in a subscription, ordered by their sub-sort index. Requesting `subPath` with an `Accept: text/html` header (or `?html=1`) returns a human-readable info page instead of the raw body. diff --git a/docs/content/docs/en/reference/api/clients.mdx b/docs/content/docs/en/reference/api/clients.mdx index b91035dc2..77bd6c1f7 100644 --- a/docs/content/docs/en/reference/api/clients.mdx +++ b/docs/content/docs/en/reference/api/clients.mdx @@ -264,10 +264,11 @@ _openapi: - depth: 2 title: Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, hy2://) for clients matching the subscription ID. Same - result set as /sub/, 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' + 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-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 strings the Copy URL button copies in the panel UI. Supported protocols: @@ -496,10 +497,11 @@ _openapi: id: traffic-counters-for-a-client-identified-by-email - content: Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, hy2://) for clients matching the subscription ID. Same - result set as /sub/, 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 + 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-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 protocols: vmess, vless, trojan, shadowsocks, hysteria. If diff --git a/docs/content/docs/en/reference/api/subscription-server.mdx b/docs/content/docs/en/reference/api/subscription-server.mdx index ee6aef0e4..d5a7540f3 100644 --- a/docs/content/docs/en/reference/api/subscription-server.mdx +++ b/docs/content/docs/en/reference/api/subscription-server.mdx @@ -2,9 +2,10 @@ title: Subscription Server description: A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port - (default 10882) and is configured in Settings → Subscription. Paths are - configurable; defaults are shown below. All subscription endpoints set - response headers for client apps to read traffic/expiry info. + (default 2096) and is configured in Settings → Subscription. Fresh panels + generate random path prefixes for each format; all paths remain configurable. + Every subscription endpoint sets response headers for client apps to read + traffic/expiry info. full: true _openapi: preload: @@ -15,34 +16,36 @@ _openapi: matching the subscription ID. When the request has an Accept: text/html header or ?html=1, renders a styled info page instead. With ?format=info, returns the page view-model as JSON (traffic, expiry, - online status; no links) for live polling. Default path: /sub/:subid.' - 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-with-formatinfo-returns-the-page-view-model-as-json-traffic-expiry-online-status-no-links-for-live-polling-default-path-subsubid' + online status; no links) for live polling. 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-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.' - 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' + title: 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. + url: '#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 + 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.' - 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' + 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-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. With ?format=info, returns the page view-model as JSON (traffic, expiry, - online status; no links) for live polling. Default path: /sub/:subid.' - 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-with-formatinfo-returns-the-page-view-model-as-json-traffic-expiry-online-status-no-links-for-live-polling-default-path-subsubid - - 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.' - 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 - - 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.' - 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 + online status; no links) for live polling. 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-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. 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-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. 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-the-path-prefix-is-configured-by-subclashpath contents: [] --- diff --git a/docs/content/docs/fa/config/subscription.mdx b/docs/content/docs/fa/config/subscription.mdx index b6a2f79a4..2269c5d91 100644 --- a/docs/content/docs/fa/config/subscription.mdx +++ b/docs/content/docs/fa/config/subscription.mdx @@ -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/ +https://:// ``` که در آن `` همان **Sub ID** کلاینت است. @@ -44,13 +44,13 @@ https://:/sub/ | 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 diff --git a/docs/content/docs/fa/reference/api/clients.mdx b/docs/content/docs/fa/reference/api/clients.mdx index d945e333d..8c3e73c83 100644 --- a/docs/content/docs/fa/reference/api/clients.mdx +++ b/docs/content/docs/fa/reference/api/clients.mdx @@ -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/, 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/, 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) { ); -} \ No newline at end of file +} diff --git a/docs/content/docs/fa/reference/api/subscription-server.mdx b/docs/content/docs/fa/reference/api/subscription-server.mdx index ac36c16e0..133f6ffca 100644 --- a/docs/content/docs/fa/reference/api/subscription-server.mdx +++ b/docs/content/docs/fa/reference/api/subscription-server.mdx @@ -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) { ); -} \ No newline at end of file +} diff --git a/docs/content/docs/ru/config/subscription.mdx b/docs/content/docs/ru/config/subscription.mdx index 05f2c0ca0..9c05dd33d 100644 --- a/docs/content/docs/ru/config/subscription.mdx +++ b/docs/content/docs/ru/config/subscription.mdx @@ -18,7 +18,7 @@ icon: Rss | ------------- | ------- | --------------------------------------------------------------- | | `subPort` | `2096` | Порт прослушивания (отдельный от панели). | | `subListen` | _(все)_ | Адрес привязки. | -| `subPath` | `/sub/` | Базовый путь для необработанных URL подписок. | +| `subPath` | _(случайный для каждой панели)_ | Базовый путь для необработанных URL подписок. | | `subDomain` | _(нет)_ | Публичный хост; если задан, сервер отвечает только для этого Host. | | `subCertFile` / `subKeyFile` | _(нет)_ | Сертификат + ключ TLS — когда заданы, сервер работает по **HTTPS**. | | `subEncrypt` | `true` | Кодировать тело необработанной подписки в base64. | @@ -27,7 +27,7 @@ icon: Rss URL подписки выглядит так: ```text -https://:/sub/ +https://:// ``` где `` — это **Sub ID** клиента. @@ -44,13 +44,13 @@ https://:/sub/ | Формат | Путь | Включается | Вывод | | --------------------- | --------- | ---------------- | --------------------------------------------------- | -| **Необработанные ссылки** | `/sub/` | всегда (если включён) | Список ссылок `vless://`, `vmess://`, … (закодированных в base64, когда включён `subEncrypt`). | -| **JSON** | `/json/` | `subJsonEnable` | Полные клиентские конфигурации Xray. | -| **Clash / Mihomo** | `/clash/` | `subClashEnable` | YAML-профиль. | +| **Необработанные ссылки** | `subPath` | всегда (если включён) | Список ссылок `vless://`, `vmess://`, … (закодированных в base64, когда включён `subEncrypt`). | +| **JSON** | `subJsonPath` | `subJsonEnable` | Полные клиентские конфигурации Xray. | +| **Clash / Mihomo** | `subClashPath` | `subClashEnable` | YAML-профиль. | В подписке появляются только включённые входящие соединения, использующие **VLESS, VMess, Trojan, Shadowsocks или Hysteria2**, упорядоченные по их индексу -сортировки подписки. Запрос `/sub/` с заголовком `Accept: text/html` (или +сортировки подписки. Запрос `subPath` с заголовком `Accept: text/html` (или `?html=1`) возвращает удобочитаемую информационную страницу вместо необработанного тела. diff --git a/docs/content/docs/ru/reference/api/clients.mdx b/docs/content/docs/ru/reference/api/clients.mdx index 6aeaa0444..b8cd10682 100644 --- a/docs/content/docs/ru/reference/api/clients.mdx +++ b/docs/content/docs/ru/reference/api/clients.mdx @@ -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/, 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/, 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) { ); -} \ No newline at end of file +} diff --git a/docs/content/docs/ru/reference/api/subscription-server.mdx b/docs/content/docs/ru/reference/api/subscription-server.mdx index 42b8d2624..e03dffe45 100644 --- a/docs/content/docs/ru/reference/api/subscription-server.mdx +++ b/docs/content/docs/ru/reference/api/subscription-server.mdx @@ -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) { ); -} \ No newline at end of file +} diff --git a/docs/content/docs/zh/config/subscription.mdx b/docs/content/docs/zh/config/subscription.mdx index 5a33a8441..380a77bb1 100644 --- a/docs/content/docs/zh/config/subscription.mdx +++ b/docs/content/docs/zh/config/subscription.mdx @@ -14,7 +14,7 @@ icon: Rss | ------------- | ------- | --------------------------------------------------------------- | | `subPort` | `2096` | 监听端口(与面板分开)。 | | `subListen` | _(全部)_ | 绑定地址。 | -| `subPath` | `/sub/` | 原始订阅 URL 的基础路径。 | +| `subPath` | _(每个面板随机生成)_ | 原始订阅 URL 的基础路径。 | | `subDomain` | _(无)_ | 公开主机名;若设置,服务器仅响应该 Host。 | | `subCertFile` / `subKeyFile` | _(无)_ | TLS 证书 + 密钥 —— 设置后,服务器以 **HTTPS** 提供服务。 | | `subEncrypt` | `true` | 对原始订阅内容进行 base64 编码。 | @@ -23,7 +23,7 @@ icon: Rss 一个订阅 URL 形如: ```text -https://:/sub/ +https://:// ``` 其中 `` 是客户端的 **Sub ID**。 @@ -38,11 +38,11 @@ https://:/sub/ | 格式 | 路径 | 启用方式 | 输出 | | --------------------- | --------- | ---------------- | --------------------------------------------------- | -| **原始链接** | `/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** 的已启用入站才会出现在订阅中,并按其订阅排序索引排列。使用 `Accept: text/html` 头(或 `?html=1`)请求 `/sub/` 会返回一个人类可读的信息页面,而非原始内容。 +只有使用 **VLESS、VMess、Trojan、Shadowsocks 或 Hysteria2** 的已启用入站才会出现在订阅中,并按其订阅排序索引排列。使用 `Accept: text/html` 头(或 `?html=1`)请求 `subPath` 会返回一个人类可读的信息页面,而非原始内容。 ### Base64 与 JSON diff --git a/docs/content/docs/zh/reference/api/clients.mdx b/docs/content/docs/zh/reference/api/clients.mdx index 830cdf605..52da456f9 100644 --- a/docs/content/docs/zh/reference/api/clients.mdx +++ b/docs/content/docs/zh/reference/api/clients.mdx @@ -255,11 +255,11 @@ _openapi: - depth: 2 title: >- 返回与该订阅 ID 匹配的客户端的每个协议 URL(vless://、vmess://、trojan://、ss://、 - hysteria://、hy2://)。结果集与 /sub/ 相同,但以 JSON 数组形式返回——不含 + hysteria://、hy2://)。结果集与配置的 subPath 端点相同,但以 JSON 数组形式返回——不含 base64。当某入站设置了 streamSettings.externalProxy 时,每个外部代理会发出一条 URL。 当该 subId 没有已启用的客户端时返回空数组。 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: >- 返回单个客户端在所有挂载入站上的每个 URL——与面板 UI 中“复制 URL”按钮所复制的字符串 @@ -477,11 +477,11 @@ _openapi: id: traffic-counters-for-a-client-identified-by-email - content: >- 返回与该订阅 ID 匹配的客户端的每个协议 URL(vless://、vmess://、trojan://、ss://、 - hysteria://、hy2://)。结果集与 /sub/ 相同,但以 JSON 数组形式返回——不含 + hysteria://、hy2://)。结果集与配置的 subPath 端点相同,但以 JSON 数组形式返回——不含 base64。当某入站设置了 streamSettings.externalProxy 时,每个外部代理会发出一条 URL。 当该 subId 没有已启用的客户端时返回空数组。 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: >- 返回单个客户端在所有挂载入站上的每个 URL——与面板 UI 中“复制 URL”按钮所复制的字符串 相同。支持的协议:vmess、vless、trojan、shadowsocks、hysteria。若设置了 @@ -529,4 +529,4 @@ export default function Layout(props) { ); -} \ No newline at end of file +} diff --git a/docs/content/docs/zh/reference/api/subscription-server.mdx b/docs/content/docs/zh/reference/api/subscription-server.mdx index b0e2ca573..f92bb18c1 100644 --- a/docs/content/docs/zh/reference/api/subscription-server.mdx +++ b/docs/content/docs/zh/reference/api/subscription-server.mdx @@ -2,7 +2,7 @@ title: 订阅服务器 description: >- 一个独立的 HTTP/HTTPS 服务器,用于向客户端提供代理订阅链接(标准、JSON 和 Clash)。该服务器监听自己的端口(默认 - 10882),并在“设置 → 订阅”中进行配置。路径可自定义;下方展示的是默认值。所有订阅端点都会设置响应头,供客户端应用读取流量/到期信息。 + 2096),并在“设置 → 订阅”中进行配置。新面板会为每种格式生成随机路径前缀,所有路径仍可自定义。所有订阅端点都会设置响应头,供客户端应用读取流量/到期信息。 full: true _openapi: preload: @@ -11,36 +11,36 @@ _openapi: - depth: 2 title: >- 返回与该订阅 ID 匹配的所有已启用客户端的 base64 编码订阅链接。当请求带有 Accept: text/html - 头或 ?html=1 时,改为渲染一个带样式的信息页面。默认路径:/sub/:subid。 + 头或 ?html=1 时,改为渲染一个带样式的信息页面。路径前缀由 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: >- - 以代理配置的 JSON 数组形式返回订阅(每个已启用客户端一项)。仅在设置中启用 JSON 订阅时可用。默认路径:/json/:subid。 + 以代理配置的 JSON 数组形式返回订阅(每个已启用客户端一项)。仅在设置中启用 JSON 订阅时可用。路径前缀由 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: >- 以兼容 Clash/Mihomo 的 YAML 配置形式返回订阅,其中包含已配置的全局 Clash 路由规则。仅在设置中启用 Clash - 订阅时可用。默认路径:/clash/:subid。 + 订阅时可用。路径前缀由 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: >- 返回与该订阅 ID 匹配的所有已启用客户端的 base64 编码订阅链接。当请求带有 Accept: - text/html 头或 ?html=1 时,改为渲染一个带样式的信息页面。默认路径:/sub/:subid。 + text/html 头或 ?html=1 时,改为渲染一个带样式的信息页面。路径前缀由 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: >- - 以代理配置的 JSON 数组形式返回订阅(每个已启用客户端一项)。仅在设置中启用 JSON 订阅时可用。默认路径:/json/:subid。 + 以代理配置的 JSON 数组形式返回订阅(每个已启用客户端一项)。仅在设置中启用 JSON 订阅时可用。路径前缀由 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: >- 以兼容 Clash/Mihomo 的 YAML 配置形式返回订阅,其中包含已配置的全局 Clash 路由规则。仅在设置中启用 Clash - 订阅时可用。默认路径:/clash/:subid。 + 订阅时可用。路径前缀由 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: [] --- @@ -56,4 +56,4 @@ export default function Layout(props) { ); -} \ No newline at end of file +} diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 909f0ce79..0d43ce0e9 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -3512,7 +3512,7 @@ }, { "name": "Subscription Server", - "description": "A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port (default 10882) and is configured in Settings → Subscription. Paths are configurable; defaults are shown below. All subscription endpoints set response headers for client apps to read traffic/expiry info." + "description": "A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port (default 2096) and is configured in Settings → Subscription. Fresh panels generate random path prefixes for each format; all paths remain configurable. Every subscription endpoint sets response headers for client apps to read traffic/expiry info." }, { "name": "WebSocket", @@ -8731,7 +8731,7 @@ "tags": [ "Clients" ], - "summary": "Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, hy2://) for clients matching the subscription ID. Same result set as /sub/, 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.", + "summary": "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 streamSettings.externalProxy set, one URL is emitted per external proxy. Empty array when the subId has no enabled clients.", "operationId": "get_panel_api_clients_subLinks_subId", "parameters": [ { @@ -12639,7 +12639,7 @@ "tags": [ "Subscription Server" ], - "summary": "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. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. Default path: /sub/:subid.", + "summary": "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. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. The path prefix is configured by subPath.", "operationId": "get_subPath_subid", "parameters": [ { @@ -12698,7 +12698,7 @@ "tags": [ "Subscription Server" ], - "summary": "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.", + "summary": "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.", "operationId": "get_jsonPath_subid", "parameters": [ { @@ -12748,7 +12748,7 @@ "tags": [ "Subscription Server" ], - "summary": "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.", + "summary": "Return subscription as a Clash/Mihomo-compatible YAML config, including configured global Clash routing rules. Only when Clash subscription is enabled in settings. The path prefix is configured by subClashPath.", "operationId": "get_clashPath_subid", "parameters": [ { diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 909f0ce79..0d43ce0e9 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -3512,7 +3512,7 @@ }, { "name": "Subscription Server", - "description": "A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port (default 10882) and is configured in Settings → Subscription. Paths are configurable; defaults are shown below. All subscription endpoints set response headers for client apps to read traffic/expiry info." + "description": "A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port (default 2096) and is configured in Settings → Subscription. Fresh panels generate random path prefixes for each format; all paths remain configurable. Every subscription endpoint sets response headers for client apps to read traffic/expiry info." }, { "name": "WebSocket", @@ -8731,7 +8731,7 @@ "tags": [ "Clients" ], - "summary": "Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, hy2://) for clients matching the subscription ID. Same result set as /sub/, 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.", + "summary": "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 streamSettings.externalProxy set, one URL is emitted per external proxy. Empty array when the subId has no enabled clients.", "operationId": "get_panel_api_clients_subLinks_subId", "parameters": [ { @@ -12639,7 +12639,7 @@ "tags": [ "Subscription Server" ], - "summary": "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. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. Default path: /sub/:subid.", + "summary": "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. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. The path prefix is configured by subPath.", "operationId": "get_subPath_subid", "parameters": [ { @@ -12698,7 +12698,7 @@ "tags": [ "Subscription Server" ], - "summary": "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.", + "summary": "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.", "operationId": "get_jsonPath_subid", "parameters": [ { @@ -12748,7 +12748,7 @@ "tags": [ "Subscription Server" ], - "summary": "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.", + "summary": "Return subscription as a Clash/Mihomo-compatible YAML config, including configured global Clash routing rules. Only when Clash subscription is enabled in settings. The path prefix is configured by subClashPath.", "operationId": "get_clashPath_subid", "parameters": [ { diff --git a/frontend/src/pages/api-docs/endpoints.ts b/frontend/src/pages/api-docs/endpoints.ts index f5217191b..c8f3b960e 100644 --- a/frontend/src/pages/api-docs/endpoints.ts +++ b/frontend/src/pages/api-docs/endpoints.ts @@ -1279,7 +1279,7 @@ export const sections: readonly Section[] = [ method: 'GET', path: '/panel/api/clients/subLinks/:subId', summary: - 'Return every protocol URL (vless://, vmess://, trojan://, ss://, hysteria://, hy2://) for clients matching the subscription ID. Same result set as /sub/, 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.', + '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 streamSettings.externalProxy set, one URL is emitted per external proxy. Empty array when the subId has no enabled clients.', params: [ { name: 'subId', @@ -2299,7 +2299,7 @@ export const sections: readonly Section[] = [ id: 'subscription', title: 'Subscription Server', description: - 'A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port (default 10882) and is configured in Settings → Subscription. Paths are configurable; defaults are shown below. All subscription endpoints set response headers for client apps to read traffic/expiry info.', + 'A separate HTTP/HTTPS server that serves proxy subscription links (standard, JSON, and Clash) to clients. The server listens on its own port (default 2096) and is configured in Settings → Subscription. Fresh panels generate random path prefixes for each format; all paths remain configurable. Every subscription endpoint sets response headers for client apps to read traffic/expiry info.', subHeader: [ { name: 'Subscription-Userinfo', @@ -2327,7 +2327,7 @@ export const sections: readonly Section[] = [ method: 'GET', path: '/{subPath}:subid', summary: - '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. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. Default path: /sub/:subid.', + '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. With ?format=info, returns the page view-model as JSON (traffic, expiry, online status; no links) for live polling. The path prefix is configured by subPath.', params: [ { name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }, { @@ -2343,14 +2343,14 @@ export const sections: readonly Section[] = [ method: 'GET', path: '/{jsonPath}:subid', summary: - '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.', + '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.', params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }], }, { method: 'GET', path: '/{clashPath}:subid', summary: - '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.', + 'Return subscription as a Clash/Mihomo-compatible YAML config, including configured global Clash routing rules. Only when Clash subscription is enabled in settings. The path prefix is configured by subClashPath.', params: [{ name: 'subid', in: 'path', type: 'string', desc: 'Client subscription ID.' }], }, ], diff --git a/internal/database/db.go b/internal/database/db.go index 5dec87142..285fa429a 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -1169,6 +1169,22 @@ func initUser() error { return nil } +func seedRandomSubscriptionPaths() error { + settings := []model.Setting{ + {Key: "subPath", Value: "/" + random.NumLower(16) + "/"}, + {Key: "subJsonPath", Value: "/" + random.NumLower(16) + "/"}, + {Key: "subClashPath", Value: "/" + random.NumLower(16) + "/"}, + } + return db.Transaction(func(tx *gorm.DB) error { + for i := range settings { + if err := tx.Where("key = ?", settings[i].Key).FirstOrCreate(&settings[i]).Error; err != nil { + return err + } + } + return nil + }) +} + func runSeeders(isUsersEmpty bool) error { empty, err := isTableEmpty("history_of_seeders") if err != nil { @@ -2138,6 +2154,11 @@ func InitDB(dbPath string) error { if err != nil { return err } + if isUsersEmpty { + if err := seedRandomSubscriptionPaths(); err != nil { + return err + } + } if err := initUser(); err != nil { return err diff --git a/internal/database/db_seed_test.go b/internal/database/db_seed_test.go index 2464211ad..a7b740362 100644 --- a/internal/database/db_seed_test.go +++ b/internal/database/db_seed_test.go @@ -9,6 +9,54 @@ import ( "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) +func TestInitDB_GeneratesPerPanelSubscriptionPaths(t *testing.T) { + pathPattern := regexp.MustCompile(`^/[0-9a-z]{16}/$`) + loadPaths := func(dbPath string) map[string]string { + t.Helper() + if err := InitDB(dbPath); err != nil { + t.Fatalf("InitDB failed: %v", err) + } + defer func() { + if err := CloseDB(); err != nil { + t.Errorf("CloseDB failed: %v", err) + } + }() + + keys := []string{"subPath", "subJsonPath", "subClashPath"} + paths := make(map[string]string, len(keys)) + for _, key := range keys { + var setting model.Setting + if err := db.Where("key = ?", key).First(&setting).Error; err != nil { + t.Fatalf("read %s: %v", key, err) + } + if !pathPattern.MatchString(setting.Value) { + t.Fatalf("%s = %q, want /<16 lowercase alphanumeric characters>/", key, setting.Value) + } + paths[key] = setting.Value + } + if paths["subPath"] == paths["subJsonPath"] || paths["subPath"] == paths["subClashPath"] || paths["subJsonPath"] == paths["subClashPath"] { + t.Fatalf("subscription paths must be distinct: %v", paths) + } + return paths + } + + firstDB := filepath.Join(t.TempDir(), "x-ui.db") + first := loadPaths(firstDB) + reloaded := loadPaths(firstDB) + for key, firstPath := range first { + if firstPath != reloaded[key] { + t.Fatalf("%s changed after restart: %q, then %q", key, firstPath, reloaded[key]) + } + } + + second := loadPaths(filepath.Join(t.TempDir(), "x-ui.db")) + for key, firstPath := range first { + if firstPath == second[key] { + t.Fatalf("%s reused across panels: %q", key, firstPath) + } + } +} + func TestSeedClientsFromInboundJSON_IsIdempotentAgainstExistingClients(t *testing.T) { dbDir := t.TempDir() t.Setenv("XUI_DB_FOLDER", dbDir) @@ -168,6 +216,9 @@ func TestNormalizeSettingPaths_RepairsLegacyValues(t *testing.T) { {Key: "subClashPath", Value: "clash/"}, {Key: "webBasePath", Value: "/panel/"}, } + if err := db.Where("key IN ?", []string{"subPath", "subJsonPath", "subClashPath"}).Delete(&model.Setting{}).Error; err != nil { + t.Fatalf("clear generated subscription paths: %v", err) + } for i := range seed { if err := db.Create(&seed[i]).Error; err != nil { t.Fatalf("seed setting %s: %v", seed[i].Key, err) diff --git a/internal/web/service/setting.go b/internal/web/service/setting.go index 19caedbba..1ad240a90 100644 --- a/internal/web/service/setting.go +++ b/internal/web/service/setting.go @@ -309,12 +309,17 @@ func getEnv(key, fallback string) string { func (s *SettingService) ResetSettings() error { db := database.GetDB() - err := db.Where("1 = 1").Delete(model.Setting{}).Error - if err != nil { - return err - } - return db.Model(model.User{}). - Where("1 = 1").Error + return db.Transaction(func(tx *gorm.DB) error { + if err := tx.Where("1 = 1").Delete(model.Setting{}).Error; err != nil { + return err + } + paths := []model.Setting{ + {Key: "subPath", Value: "/" + random.NumLower(16) + "/"}, + {Key: "subJsonPath", Value: "/" + random.NumLower(16) + "/"}, + {Key: "subClashPath", Value: "/" + random.NumLower(16) + "/"}, + } + return tx.Create(&paths).Error + }) } func (s *SettingService) getSetting(key string) (*model.Setting, error) { diff --git a/internal/web/service/setting_security_test.go b/internal/web/service/setting_security_test.go index d65894cf9..01060c36b 100644 --- a/internal/web/service/setting_security_test.go +++ b/internal/web/service/setting_security_test.go @@ -2,6 +2,7 @@ package service import ( "path/filepath" + "regexp" "testing" "github.com/xlzd/gotp" @@ -10,6 +11,44 @@ import ( "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) +func TestResetSettingsRegeneratesSubscriptionPaths(t *testing.T) { + setupSettingTestDB(t) + s := &SettingService{} + for key, value := range map[string]string{ + "subPath": "/sub/", + "subJsonPath": "/json/", + "subClashPath": "/clash/", + "webPort": "8443", + } { + if err := s.saveSetting(key, value); err != nil { + t.Fatalf("save %s: %v", key, err) + } + } + + if err := s.ResetSettings(); err != nil { + t.Fatalf("ResetSettings: %v", err) + } + + pathPattern := regexp.MustCompile(`^/[0-9a-z]{16}/$`) + paths := map[string]string{} + for _, key := range []string{"subPath", "subJsonPath", "subClashPath"} { + value, err := s.getString(key) + if err != nil { + t.Fatalf("read %s: %v", key, err) + } + if !pathPattern.MatchString(value) { + t.Errorf("%s = %q, want /<16 lowercase alphanumeric characters>/", key, value) + } + paths[key] = value + } + if paths["subPath"] == paths["subJsonPath"] || paths["subPath"] == paths["subClashPath"] || paths["subJsonPath"] == paths["subClashPath"] { + t.Fatalf("subscription paths must be distinct: %v", paths) + } + if port, err := s.GetPort(); err != nil || port != 2053 { + t.Fatalf("web port after reset = %d, %v; want 2053", port, err) + } +} + func setupSettingTestDB(t *testing.T) { t.Helper() if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {