Skip to documentation
OrbitMeshDocs
Browse documentation
Docs/Platform Systems

PLATFORM SYSTEMS

Subscription Distribution

Subscription Distribution 负责 Traffic 客户端配置分发。
View source

1. 定位

Subscription Distribution 负责 Traffic 客户端配置分发。

它不是简单的链接生成器,而是租户、用户、订阅凭证、套餐、Traffic Policy 和 Endpoint readiness 的客户端呈现层。

2. 目标

  • 为用户生成可用 Traffic subscription。
  • 支持多客户端格式。
  • 支持 token 生命周期。
  • 支持吊销和轮换。
  • 支持使用量与配额状态。
  • 在用户或租户不可用后停止配置分发。

3. 核心对象

Subscription

表示用户可使用的一组客户端访问凭证和输出格式。

字段:

  • id。
  • tenant_id。
  • user_id。
  • name。
  • status。
  • token_hash。
  • format。
  • endpoint_selection。
  • credential_ref。
  • quota_policy_ref。
  • last_used_at。

Subscription Endpoint

表示 subscription 可以输出的 ready Traffic Entry endpoint。

字段:

  • endpoint_id。
  • capability_role。
  • runtime_binding_id。
  • protocol。
  • domain。
  • port。
  • certificate_ref。
  • readiness。

Client Access Policy

Client Access Policy 是租户级自动订阅分配策略,用于 first-party client 调用 ensure-client-access 时生成或刷新订阅。

策略字段:

  • scope_type / scope_id:支持 tenant、role、user。
  • client_type:支持具体端类型或 *
  • priority:数字越小优先级越高。
  • target:订阅输出格式,例如 clash、surge、shadowrocket。
  • profile:目标格式的 profile,例如 standard。
  • endpoint_strategy:当前支持 all_ready_entries。
  • node_name_strategy:节点命名策略,推荐值为 region。
  • endpoint_selection_json:对 ready Traffic Entry endpoint 的二次过滤。
  • auto_rotate_on_ensure:客户端 ensure 时是否自动轮换订阅 token。

策略命中顺序:

  1. 只选择 active 策略。
  2. 按 priority 升序匹配。
  3. 同优先级按 created_at 倒序匹配。
  4. scope、client_type 同时匹配后生效。

endpoint_selection_json 支持:

{
  "endpoint_ids": ["..."],
  "node_ids": ["..."],
  "providers": ["aws"],
  "regions": ["tokyo"],
  "node_types": ["traffic_entry"],
  "exclude_regions": ["hong-kong"],
  "limit": 1
}

支持的排除键包括 exclude_endpoint_idsexclude_node_idsexclude_providersexclude_regionsexclude_node_types。 空对象 {} 表示使用所有 ready Traffic Entry endpoint。

Dynamic endpoint reconciliation

通过 ensure-client-access 创建的 Client Access Subscription 是动态订阅。它必须同时具有 device_installation_idendpoint_strategy,其 Endpoint 关联由 Control Plane 按当前租户 Client Access Policy 持续对账,而不是创建时的一次性快照。

对账规则:

  1. Control Plane 启动后立即执行一次,此后每分钟执行。
  2. 多副本通过 Redis lease 保证同一轮只有一个 worker 扫描。
  3. 按 subscription id 游标分批扫描,避免固定窗口导致后续订阅无法处理。
  4. 使用当前生效的 Client Access Policy、endpoint_strategyendpoint_selection_json 重新计算合格的 Traffic Entry Endpoint。
  5. Endpoint 关联发生变化时,在同一数据库事务内替换 subscription_endpoints,并为旧、新 Endpoint 所属 Node 发布新的 Desired State。
  6. 新增 Endpoint 会获得该订阅的 Runtime 凭证;移除 Endpoint 会撤下旧 Runtime 凭证。
  7. 暂无合格 Endpoint 时,订阅资源和公开 URL 保留,但 Endpoint 关联为空;节点恢复资格后 自动重新加入。

不具有上述动态绑定字段的手工订阅不参与自动对账,只有显式的动态订阅才会随节点与策略变化。

Endpoint 是否进入动态订阅由资源资格决定,包括租户归属、Traffic Entry role、Node online、 Runtime installed/running,以及域名、DNS、TLS 配置可用。readiness.serving 表示当前承载状态, 不用于制造“必须先有订阅才能通过探测、必须先通过探测才能加入订阅”的循环依赖。

4. 支持格式

MVP already supports target rendering through subscription_target_catalog:

  • Trojan URI。
  • sing-box client JSON。
  • Clash-compatible YAML。
  • Shadowrocket。
  • Surge v4。
  • Surge v5。

The Control Plane stores client target metadata in subscription_target_catalog_entries. Each target declares:

  • renderer kind.
  • content kind.
  • supported protocols.
  • JSON parameter schema.
  • profile presets.

Profile presets are exposed by /api/v1/subscription-target-catalog and rendered by the Console. The Console must not hard-code client profile variants such as Surge v4, Surge v5 Standard, or Clash Standard.

后续:

  • Stash。
  • 企业 MDM profile。

5. 生成流程

GET /subscription/{token}
  |
validate subscription token
  |
load user / tenant / entitlement
  |
load ready traffic.entry endpoints
  |
load subscription quota state
  |
render target format
  |
return config

The public path is subscription-oriented. New client integrations must use /subscription/{token} and its target-format variants.

6. Trojan URI 示例

trojan://PASSWORD@dev.example.com:443?security=tls&sni=dev.example.com#OrbitMesh-Tokyo

7. Client Targets

Current MVP targets:

sing-box
  JSON client profile.

clash
  Clash-compatible YAML profile. Supports minimal and standard templates.

surge
  Surge profile. Supports Surge version and minimal or standard templates.

shadowrocket
  Shadowrocket output. Supports a refreshable URI-list node subscription and a separate full config mode.

trojan-uri
  Raw Trojan URI-list for clients that import trojan:// links directly.

Shadowrocket has two different import flows:

Node subscription
  Expects one proxy URI per line, such as trojan://...

Full config
  Expects a Shadowrocket/Surge-style config file with [General], [Proxy Group], and [Rule].
  Shadowrocket imports this as a config profile, not as a refreshable node subscription.

OrbitMesh maps them through target parameters:

/shadowrocket
/shadowrocket?node_name_strategy=region
  URI-list node subscription.
  This is the recommended Shadowrocket URL for node refresh and subscription-userinfo display.

/shadowrocket?template=standard&node_name_strategy=region
  Full Shadowrocket config file.
  This config does not inline proxy credentials in [Proxy]. Users should import the node subscription URL separately.
  Proxy groups bind to nodes through the target adapter's external-node selection mechanism.

Node display language is selected independently from the target format:

/clash?template=standard&node_name_strategy=region&locale=zh-CN
/surge?template=standard&node_name_strategy=region&locale=ja
/shadowrocket?node_name_strategy=region&locale=en

locale uses BCP 47 values. Supported values are en, zh-CN, zh-TW, ja, ko, es, fr, de, pt-BR, ru, ar, hi, id, th, vi, and tr. Language-region variants are normalized to this supported set.

locale is a presentation parameter on the public target fetch endpoint:

GET /subscription/{token}/{target}?locale=<bcp47-locale>

POST /api/v1/subscriptions/ensure-client-access does not accept or persist a locale. First-party clients must store the token-bearing URL returned by ensure-client-access without adding a device language to the durable binding. For each profile download, the client parses that URL and sets the current device locale in its query parameters before issuing the GET request. Changing language therefore does not rotate the subscription token or create a new subscription binding.

The explicit locale query parameter is authoritative for subscription rendering. When it is absent, Region Catalog labels default to English. Console region selectors use the separate authenticated endpoint GET /api/v1/region-catalog?locale=<bcp47-locale>.

The canonical OrbitMesh template must express node selection as a generic node group item, not as a client-specific syntax.

Generic template
  group item: nodes

Target adapter mapping
  Shadowrocket: policy-regex-filter over imported subscription node names.
  Clash/Mihomo: proxy-providers and provider filters where supported.
  Surge: external policy lists or policy group include mechanisms where supported.
  URI-list targets: direct node URI output.

Client-specific fields such as policy-regex-filter must stay inside the target adapter renderer. They must not leak into Control Plane product abstractions, catalog semantics, or user-facing capability names.

For Shadowrocket, a generic nodes group should select all imported subscription nodes:

手动切换 = select, policy-regex-filter=.*
自动选择 = url-test, policy-regex-filter=.*, url=http://www.gstatic.com/generate_204, interval=300

The standard MVP group model is layered:

Node source groups
  手动切换
    Selects all imported subscription nodes.

  自动选择
    Runs url-test over all imported subscription nodes.

Entry group
  节点选择
    References 自动选择, 手动切换, 全球直连, DIRECT.

Business groups
  OpenAi, 开发服务, 境外服务, YouTube, Netflix, 漏网之鱼.
  Reference 节点选择, 自动选择, 手动切换, DIRECT.
  They must not directly own node matching by default.

Region or provider groups are a future enhancement. They must be generated from generic node selectors and then referenced by 节点选择, not injected into every business group by default.

8. Node Display Names

Client node display names must not rely only on internal node.name.

node.name is the operator-facing resource name. It is stable for API and Console operations, but it may be too technical for client UI.

Client node naming strategy:

default
  orbitmesh-<sanitized node.name>

region
  Region Catalog flag + localized label
  else canonical region key
  else default

Provider metadata must not be included in generated client node names. Duplicate nodes in one region are deterministically suffixed as Region 01, Region 02, and so on.

Recommended public profiles should use node_name_strategy=region.

Console should list catalog default profile links before raw default links. For Shadowrocket this means the recommended URL is the node subscription with node_name_strategy=region. The full config URL with template=standard is an advanced profile for rules and proxy groups and must be displayed separately from the refreshable node subscription.

9. Provider And Region Semantics

provider and region are independent stable selector keys, not display-only labels.

provider
  Infrastructure or network provider.
  Examples: byoh, tencent, aws, oci, vultr, hetzner.

region
  Canonical deployment or network location key.
  Examples: hong-kong, tokyo, singapore, los-angeles, frankfurt.

They are used by:

  • Traffic Policy selectors.
  • Infrastructure placement and provider-specific filtering.
  • Region Catalog lookup for localized subscription node names.

Cloud provider region codes such as ap-northeast-1, asia-northeast1, and japaneast are aliases in Region Catalog. New Node and Deployment Token writes store the canonical key, such as tokyo.

10. Region Catalog

Region Catalog is a system-managed, table-backed taxonomy. Control Plane, Console, and subscription renderers consume the same records.

Each entry contains:

key
  Stable canonical region key.

country_code
  ISO 3166-1 alpha-2 code used to derive the flag.

region_group
  Private, Asia, Middle East, North America, Europe, Oceania, South America, or Africa.

aliases
  Cloud provider codes and historical operator inputs that resolve to the canonical key.

labels
  BCP 47 locale to localized display-name map.

sort_order
  Stable Console ordering independent from translated labels.

The initial catalog covers more than 80 mainstream domestic and international node regions and common AWS, Google Cloud, and Azure aliases. Adding a region or translation changes Catalog data only; subscription renderers and Console forms must not contain region-specific switches.

The same catalog drives Console node forms and labels, Traffic Policy region selectors, and future usage, cost, and regional health reporting. Node attributes_json must not duplicate region flags or localized names.

11. 权限检查

生成配置前必须检查:

  • token 未吊销。
  • subscription active。
  • 用户 active。
  • 租户 active。
  • 套餐未过期。
  • 配额未被阻断。
  • 至少一个 traffic.entry endpoint eligible。
  • runtime binding enabled。
  • runtime instance running。
  • certificate ready 且未过期。
  • 域名 ownership validated。

12. TLS 和域名依赖

Trojan subscription output 只能引用已就绪的 endpoint。

Subscription binding eligibility 与 serving readiness 必须分开判断。

  • endpoint.status 表示 Endpoint 资源前置条件是否就绪。
  • readiness.serving 表示协议监听状态,取值为 idlependinghealthydegradedunavailable
  • 无有效订阅凭证时,Endpoint 可以是 status=readyserving=idle
  • serving 探测失败不会把 Endpoint 资源状态改为 failed。

首个订阅绑定使用 eligibility 条件:

endpoint.admin_state = enabled
domain.status = active
endpoint.dns_status = synced
certificate.status = issued
certificate.not_after > now + renewal_window
node.status = online
runtime_binding.status = running
runtime_binding.install_state = installed

eligibility 不依赖公网协议端口的最近一次健康探测。否则会形成 Endpoint ready -> 创建订阅凭证 -> 下发 inbound -> 探测成功 -> Endpoint ready 的循环依赖。

ensure-client-access 选择 eligible Endpoint,创建 Subscription、协议凭证和 SubscriptionEndpoint 关联,然后发布受影响 Node 的 Desired State。Runtime Compiler 只有在存在有效协议凭证时才生成公网 inbound 和对应的本地健康探测;Edge Runtime 应用配置后,探测结果再驱动 serving readiness。

订阅输出仍应优先消费 serving-ready Endpoint。Runtime 故障不会删除订阅关联, Compiler 也不会因为一次 serving 探测失败而移除已授权 inbound,因此后续 reconcile 可以恢复服务。

系统级 endpoint 使用平台分配的 *.traffic.orbitmesh.dev 名称和平台证书。

租户级 endpoint 使用用户自定义域名。用户域名必须先完成 CNAME / DNS ownership validation,再进入 ACME issuance。

Subscription distribution 不申请证书、不发布 DNS 记录、不访问证书私钥。它消费 Control Plane 已完成 DNS/TLS 前置条件的 Endpoint,并把 endpoint domain 作为 server address 和 SNI。

13. API Direction

Target API names:

GET    /api/v1/subscriptions
POST   /api/v1/subscriptions
POST   /api/v1/subscriptions/ensure-client-access
POST   /api/v1/subscriptions/{id}/rotate
POST   /api/v1/subscriptions/{id}/revoke
DELETE /api/v1/subscriptions/{id}

GET /subscription/{token}
GET /subscription/{token}/sing-box
GET /subscription/{token}/clash
GET /subscription/{token}/surge
GET /subscription/{token}/shadowrocket
GET /subscription/{token}/{target}

Do not add new client-config public routes. Subscription is the public API term for client configuration distribution.

ensure-client-access is the recommended endpoint for first-party clients. It binds a managed subscription by tenant, user, device installation ID, target, and profile. Clients must not list subscriptions and match by device display name. If the local token-bearing URL is missing, the endpoint can rotate the existing binding and return a fresh target URL.

14. 安全

  • token 只存 hash。
  • Subscription URL 应可轮换。
  • 高频访问应限流。
  • 不在日志中记录完整 token。
  • 不在 subscription output 中暴露证书私钥或 ACME account key。

15. 验收标准

  • 用户能获取 Trojan URI。
  • Shadowrocket can render full config files and URI-list node subscriptions.
  • public profiles can use geo/provider node display names.
  • token 吊销后 URL 失效。
  • 用户过期后 subscription 不可用。
  • 修改 Traffic Entry endpoint 后 subscription output 更新。
  • 未签发证书的 endpoint 不进入输出。
  • 自定义域名证书续期后配置自动引用新证书状态。
OrbitMesh DocumentationContent follows the repository's main branch.