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` | _(все)_ | Адрес привязки. |
| `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>
| Формат | Путь | Включается | Вывод |
| --------------------- | --------- | ---------------- | --------------------------------------------------- |
| **Необработанные ссылки** | `/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`) возвращает удобочитаемую информационную страницу вместо
необработанного тела.
+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 />
</>
);
}
}