@undefineds.co/xpod 0.2.13 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -130,8 +130,14 @@ function registerLocalServices(container) {
130
130
  // 托管式:有 Node Token,连接 Cloud
131
131
  // Cloud API endpoint 可以从 Token 解析或使用默认值
132
132
  const effectiveCloudApiEndpoint = cloudApiEndpoint || 'https://pods.undefineds.co';
133
+ const effectiveLocalPort = parseInt(process.env.XPOD_MAIN_PORT || process.env.CSS_PORT || '3000', 10);
133
134
  // 从 Node Token 解析用户名作为子域名 (格式: username:secret)
134
135
  const subdomain = parseSubdomainFromToken(nodeToken);
136
+ const tunnelProviderHint = cloudflareTunnelToken
137
+ ? 'cloudflare'
138
+ : sakuraTunnelToken
139
+ ? 'sakura_frp'
140
+ : 'none';
135
141
  container.register({
136
142
  subdomainClient: (0, awilix_1.asFunction)(() => {
137
143
  return new SubdomainClient_1.SubdomainClient({
@@ -148,15 +154,13 @@ function registerLocalServices(container) {
148
154
  }).singleton(),
149
155
  // DDNS Manager: 自动分配和更新 DDNS
150
156
  ddnsManager: (0, awilix_1.asFunction)(({ subdomainClient, capabilityDetector }) => {
151
- const tunnelProvider = cloudflareTunnelToken
152
- ? 'cloudflare'
153
- : (sakuraTunnelToken ? 'sakura_frp' : 'none');
154
157
  return new DdnsManager_1.DdnsManager({
155
158
  client: subdomainClient,
156
159
  detector: capabilityDetector,
157
160
  subdomain: subdomain || nodeId || 'auto',
161
+ localPort: effectiveLocalPort,
158
162
  autoAllocate: true,
159
- tunnelProvider,
163
+ tunnelProvider: tunnelProviderHint,
160
164
  });
161
165
  }).singleton(),
162
166
  });
@@ -1 +1 @@
1
- {"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/api/container/local.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local 模式服务注册\n *\n * Local 模式有两种配置:\n * - 托管式 (managed): 配置 XPOD_NODE_TOKEN,自动连接 Cloud 获取身份服务和 DDNS\n * - 独立式 (standalone): 不配置 XPOD_NODE_TOKEN,用户自己配置 CSS_BASE_URL 和 IdP\n */\n\nimport { asFunction, type AwilixContainer } from 'awilix';\nimport type { ApiContainerCradle, ApiContainerConfig } from './types';\n\nimport { SubdomainClient } from '../../subdomain/SubdomainClient';\nimport { LocalTunnelProvider } from '../../tunnel/LocalTunnelProvider';\nimport { SakuraFrpTunnelProvider } from '../../tunnel/SakuraFrpTunnelProvider';\nimport { CloudflareDnsProvider } from '../../dns/cloudflare/CloudflareDnsProvider';\nimport { SubdomainService } from '../../subdomain/SubdomainService';\nimport { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport { EdgeNodeCapabilityDetector } from '../../edge/EdgeNodeCapabilityDetector';\nimport { LocalNetworkManager } from '../../edge/LocalNetworkManager';\nimport { DdnsManager } from '../../edge/DdnsManager';\nimport type { TunnelProvider, TunnelStatus } from '../../tunnel/TunnelProvider';\n\n/**\n * 注册 Local 模式专属服务\n */\nexport function registerLocalServices(\n container: AwilixContainer<ApiContainerCradle>,\n): void {\n const config = container.resolve('config') as ApiContainerConfig;\n\n const {\n cloudApiEndpoint,\n nodeId,\n nodeToken,\n cloudflareTunnelToken,\n sakuraTunnelToken,\n subdomain: subdomainConfig,\n } = config;\n\n // 1. 注册 Tunnel Provider (优先 Cloudflare,其次 SakuraFRP)\n if (cloudflareTunnelToken) {\n container.register({\n localTunnelProvider: asFunction(() => {\n return new LocalTunnelProvider({\n tunnelToken: cloudflareTunnelToken,\n });\n }).singleton(),\n });\n console.log('[Local] Tunnel provider registered (CLOUDFLARE_TUNNEL_TOKEN configured)');\n } else if (sakuraTunnelToken) {\n container.register({\n localTunnelProvider: asFunction(() => {\n return new SakuraFrpTunnelProvider({\n token: sakuraTunnelToken,\n });\n }).singleton(),\n });\n console.log('[Local] Tunnel provider registered (SAKURA_TUNNEL_TOKEN configured)');\n }\n\n // 2. 自适应 DNS 管理 (Self-Hosted DNS)\n // 如果配置了 Cloudflare API Token 和 Base Domain,启用本地 DNS 管理\n const apiToken = subdomainConfig?.cloudflareApiToken;\n\n // 在 Local 模式下,强制使用 CSS_BASE_URL 作为域名来源\n // 简化用户配置心智\n let baseDomain: string | undefined;\n if (process.env.CSS_BASE_URL) {\n try {\n const url = new URL(process.env.CSS_BASE_URL);\n baseDomain = url.hostname;\n } catch {\n console.warn('[Local] Invalid CSS_BASE_URL, cannot derive domain for DNS management');\n }\n }\n\n // DEBUG: 打印变量状态\n console.log(`[Local] Debug: apiToken=${apiToken ? '***' : 'undefined'}, baseDomain=${baseDomain}, CSS_BASE_URL=${process.env.CSS_BASE_URL}`);\n\n if (apiToken && baseDomain) {\n console.log('[Local] Self-hosted DNS mode detected (IPv6 Ready)');\n\n container.register({\n // DNS Provider\n dnsProvider: asFunction(() => {\n return new CloudflareDnsProvider({\n apiToken: apiToken!,\n });\n }).singleton(),\n\n // DNS Coordinator (DnsMaintainer)\n dnsCoordinator: asFunction(({ dnsProvider }: ApiContainerCradle) => {\n return new EdgeNodeDnsCoordinator({\n provider: dnsProvider!,\n rootDomain: baseDomain,\n });\n }).singleton(),\n\n // Network Detector\n capabilityDetector: asFunction(() => {\n return new EdgeNodeCapabilityDetector({\n dynamicDetection: { enableNetworkDetection: true },\n });\n }).singleton(),\n\n // Local Network Manager (Orchestrator)\n localNetworkManager: asFunction(({ capabilityDetector, dnsCoordinator, localTunnelProvider }: ApiContainerCradle) => {\n // Tunnel 应该指向 Gateway 端口 (通常是 3000),而不是 API Server 端口 (3004)\n const mainPort = parseInt(process.env.XPOD_MAIN_PORT || '3000', 10);\n return new LocalNetworkManager({\n detector: capabilityDetector!,\n dnsCoordinator: dnsCoordinator!,\n tunnelProvider: localTunnelProvider,\n localPort: mainPort,\n });\n }).singleton(),\n\n // Subdomain Service (Keep for API support)\n subdomainService: asFunction(({ dnsProvider, localTunnelProvider, nodeRepo }: ApiContainerCradle) => {\n // 如果没有配置 Tunnel Token,使用一个 Mock Provider\n const tunnelProvider = localTunnelProvider ?? {\n name: 'noop',\n setup: async () => { throw new Error('Tunnel not configured'); },\n start: async () => { throw new Error('Tunnel not configured'); },\n stop: async () => {},\n cleanup: async () => {},\n getStatus: () => ({ running: false, connected: false } as TunnelStatus),\n getEndpoint: () => undefined,\n } as TunnelProvider;\n\n return new SubdomainService({\n baseDomain: baseDomain!,\n dnsProvider: dnsProvider!,\n tunnelProvider,\n edgeNodeRepo: nodeRepo,\n });\n }).singleton(),\n });\n console.log(`[Local] Local DNS maintenance services registered for: ${baseDomain}`);\n // 继续进行后续逻辑,不要 return,因为用户可能既用了自管 DNS 又开启了 Managed Client\n }\n\n // 独立式:没有配置 Node Token,用户自己管理域名和 IdP\n if (!nodeToken) {\n console.log('[Local] Standalone mode (no XPOD_NODE_TOKEN)');\n console.log('[Local] User manages DNS and IdP externally');\n if (cloudflareTunnelToken) {\n console.log('[Local] Will start cloudflared with provided CLOUDFLARE_TUNNEL_TOKEN');\n }\n return;\n }\n\n // 托管式:有 Node Token,连接 Cloud\n // Cloud API endpoint 可以从 Token 解析或使用默认值\n const effectiveCloudApiEndpoint = cloudApiEndpoint || 'https://pods.undefineds.co';\n\n // 从 Node Token 解析用户名作为子域名 (格式: username:secret)\n const subdomain = parseSubdomainFromToken(nodeToken);\n\n container.register({\n subdomainClient: asFunction(() => {\n return new SubdomainClient({\n cloudApiEndpoint: effectiveCloudApiEndpoint,\n nodeId: nodeId || 'auto', // 可以从 Token 解析\n nodeToken: nodeToken!,\n });\n }).singleton(),\n\n // 注册网络检测器 (如果尚未注册)\n capabilityDetector: asFunction(() => {\n return new EdgeNodeCapabilityDetector({\n dynamicDetection: { enableNetworkDetection: true },\n });\n }).singleton(),\n\n // DDNS Manager: 自动分配和更新 DDNS\n ddnsManager: asFunction(({ subdomainClient, capabilityDetector }: ApiContainerCradle) => {\n const tunnelProvider = cloudflareTunnelToken\n ? 'cloudflare'\n : (sakuraTunnelToken ? 'sakura_frp' : 'none');\n return new DdnsManager({\n client: subdomainClient!,\n detector: capabilityDetector!,\n subdomain: subdomain || nodeId || 'auto',\n autoAllocate: true,\n tunnelProvider,\n });\n }).singleton(),\n });\n\n console.log('[Local] Managed mode, SubdomainClient and DdnsManager registered');\n console.log(`[Local] Cloud API endpoint: ${effectiveCloudApiEndpoint}`);\n if (subdomain) {\n console.log(`[Local] DDNS subdomain: ${subdomain}`);\n }\n if (config.oidcIssuer) {\n console.log(`[Local] Using Cloud IdP: ${config.oidcIssuer}`);\n }\n\n if (!cloudflareTunnelToken && !sakuraTunnelToken) {\n console.log('[Local] Note: No tunnel token configured, assuming direct network access');\n }\n}\n\n/**\n * 从 Node Token 解析子域名/用户名\n * Token 格式: username:secret 或 base64 编码\n */\nfunction parseSubdomainFromToken(token: string): string | undefined {\n // 尝试直接解析 username:secret 格式\n if (token.includes(':')) {\n const [username] = token.split(':');\n if (username && /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/.test(username)) {\n return username;\n }\n }\n\n // 尝试 base64 解码\n try {\n const decoded = Buffer.from(token, 'base64').toString('utf8');\n if (decoded.includes(':')) {\n const [username] = decoded.split(':');\n if (username && /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/.test(username)) {\n return username;\n }\n }\n } catch {\n // ignore\n }\n\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../../src/api/container/local.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local 模式服务注册\n *\n * Local 模式有两种配置:\n * - 托管式 (managed): 配置 XPOD_NODE_TOKEN,自动连接 Cloud 获取身份服务和 DDNS\n * - 独立式 (standalone): 不配置 XPOD_NODE_TOKEN,用户自己配置 CSS_BASE_URL 和 IdP\n */\n\nimport { asFunction, type AwilixContainer } from 'awilix';\nimport type { ApiContainerCradle, ApiContainerConfig } from './types';\n\nimport { SubdomainClient } from '../../subdomain/SubdomainClient';\nimport { LocalTunnelProvider } from '../../tunnel/LocalTunnelProvider';\nimport { SakuraFrpTunnelProvider } from '../../tunnel/SakuraFrpTunnelProvider';\nimport { CloudflareDnsProvider } from '../../dns/cloudflare/CloudflareDnsProvider';\nimport { SubdomainService } from '../../subdomain/SubdomainService';\nimport { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport { EdgeNodeCapabilityDetector } from '../../edge/EdgeNodeCapabilityDetector';\nimport { LocalNetworkManager } from '../../edge/LocalNetworkManager';\nimport { DdnsManager } from '../../edge/DdnsManager';\nimport type { TunnelProvider, TunnelStatus } from '../../tunnel/TunnelProvider';\n\n/**\n * 注册 Local 模式专属服务\n */\nexport function registerLocalServices(\n container: AwilixContainer<ApiContainerCradle>,\n): void {\n const config = container.resolve('config') as ApiContainerConfig;\n\n const {\n cloudApiEndpoint,\n nodeId,\n nodeToken,\n cloudflareTunnelToken,\n sakuraTunnelToken,\n subdomain: subdomainConfig,\n } = config;\n\n // 1. 注册 Tunnel Provider (优先 Cloudflare,其次 SakuraFRP)\n if (cloudflareTunnelToken) {\n container.register({\n localTunnelProvider: asFunction(() => {\n return new LocalTunnelProvider({\n tunnelToken: cloudflareTunnelToken,\n });\n }).singleton(),\n });\n console.log('[Local] Tunnel provider registered (CLOUDFLARE_TUNNEL_TOKEN configured)');\n } else if (sakuraTunnelToken) {\n container.register({\n localTunnelProvider: asFunction(() => {\n return new SakuraFrpTunnelProvider({\n token: sakuraTunnelToken,\n });\n }).singleton(),\n });\n console.log('[Local] Tunnel provider registered (SAKURA_TUNNEL_TOKEN configured)');\n }\n\n // 2. 自适应 DNS 管理 (Self-Hosted DNS)\n // 如果配置了 Cloudflare API Token 和 Base Domain,启用本地 DNS 管理\n const apiToken = subdomainConfig?.cloudflareApiToken;\n\n // 在 Local 模式下,强制使用 CSS_BASE_URL 作为域名来源\n // 简化用户配置心智\n let baseDomain: string | undefined;\n if (process.env.CSS_BASE_URL) {\n try {\n const url = new URL(process.env.CSS_BASE_URL);\n baseDomain = url.hostname;\n } catch {\n console.warn('[Local] Invalid CSS_BASE_URL, cannot derive domain for DNS management');\n }\n }\n\n // DEBUG: 打印变量状态\n console.log(`[Local] Debug: apiToken=${apiToken ? '***' : 'undefined'}, baseDomain=${baseDomain}, CSS_BASE_URL=${process.env.CSS_BASE_URL}`);\n\n if (apiToken && baseDomain) {\n console.log('[Local] Self-hosted DNS mode detected (IPv6 Ready)');\n\n container.register({\n // DNS Provider\n dnsProvider: asFunction(() => {\n return new CloudflareDnsProvider({\n apiToken: apiToken!,\n });\n }).singleton(),\n\n // DNS Coordinator (DnsMaintainer)\n dnsCoordinator: asFunction(({ dnsProvider }: ApiContainerCradle) => {\n return new EdgeNodeDnsCoordinator({\n provider: dnsProvider!,\n rootDomain: baseDomain,\n });\n }).singleton(),\n\n // Network Detector\n capabilityDetector: asFunction(() => {\n return new EdgeNodeCapabilityDetector({\n dynamicDetection: { enableNetworkDetection: true },\n });\n }).singleton(),\n\n // Local Network Manager (Orchestrator)\n localNetworkManager: asFunction(({ capabilityDetector, dnsCoordinator, localTunnelProvider }: ApiContainerCradle) => {\n // Tunnel 应该指向 Gateway 端口 (通常是 3000),而不是 API Server 端口 (3004)\n const mainPort = parseInt(process.env.XPOD_MAIN_PORT || '3000', 10);\n return new LocalNetworkManager({\n detector: capabilityDetector!,\n dnsCoordinator: dnsCoordinator!,\n tunnelProvider: localTunnelProvider,\n localPort: mainPort,\n });\n }).singleton(),\n\n // Subdomain Service (Keep for API support)\n subdomainService: asFunction(({ dnsProvider, localTunnelProvider, nodeRepo }: ApiContainerCradle) => {\n // 如果没有配置 Tunnel Token,使用一个 Mock Provider\n const tunnelProvider = localTunnelProvider ?? {\n name: 'noop',\n setup: async () => { throw new Error('Tunnel not configured'); },\n start: async () => { throw new Error('Tunnel not configured'); },\n stop: async () => {},\n cleanup: async () => {},\n getStatus: () => ({ running: false, connected: false } as TunnelStatus),\n getEndpoint: () => undefined,\n } as TunnelProvider;\n\n return new SubdomainService({\n baseDomain: baseDomain!,\n dnsProvider: dnsProvider!,\n tunnelProvider,\n edgeNodeRepo: nodeRepo,\n });\n }).singleton(),\n });\n console.log(`[Local] Local DNS maintenance services registered for: ${baseDomain}`);\n // 继续进行后续逻辑,不要 return,因为用户可能既用了自管 DNS 又开启了 Managed Client\n }\n\n // 独立式:没有配置 Node Token,用户自己管理域名和 IdP\n if (!nodeToken) {\n console.log('[Local] Standalone mode (no XPOD_NODE_TOKEN)');\n console.log('[Local] User manages DNS and IdP externally');\n if (cloudflareTunnelToken) {\n console.log('[Local] Will start cloudflared with provided CLOUDFLARE_TUNNEL_TOKEN');\n }\n return;\n }\n\n // 托管式:有 Node Token,连接 Cloud\n // Cloud API endpoint 可以从 Token 解析或使用默认值\n const effectiveCloudApiEndpoint = cloudApiEndpoint || 'https://pods.undefineds.co';\n const effectiveLocalPort = parseInt(process.env.XPOD_MAIN_PORT || process.env.CSS_PORT || '3000', 10);\n\n // 从 Node Token 解析用户名作为子域名 (格式: username:secret)\n const subdomain = parseSubdomainFromToken(nodeToken);\n const tunnelProviderHint: 'cloudflare' | 'sakura_frp' | 'none' = cloudflareTunnelToken\n ? 'cloudflare'\n : sakuraTunnelToken\n ? 'sakura_frp'\n : 'none';\n\n container.register({\n subdomainClient: asFunction(() => {\n return new SubdomainClient({\n cloudApiEndpoint: effectiveCloudApiEndpoint,\n nodeId: nodeId || 'auto', // 可以从 Token 解析\n nodeToken: nodeToken!,\n });\n }).singleton(),\n\n // 注册网络检测器 (如果尚未注册)\n capabilityDetector: asFunction(() => {\n return new EdgeNodeCapabilityDetector({\n dynamicDetection: { enableNetworkDetection: true },\n });\n }).singleton(),\n\n // DDNS Manager: 自动分配和更新 DDNS\n ddnsManager: asFunction(({ subdomainClient, capabilityDetector }: ApiContainerCradle) => {\n return new DdnsManager({\n client: subdomainClient!,\n detector: capabilityDetector!,\n subdomain: subdomain || nodeId || 'auto',\n localPort: effectiveLocalPort,\n autoAllocate: true,\n tunnelProvider: tunnelProviderHint,\n });\n }).singleton(),\n });\n\n console.log('[Local] Managed mode, SubdomainClient and DdnsManager registered');\n console.log(`[Local] Cloud API endpoint: ${effectiveCloudApiEndpoint}`);\n if (subdomain) {\n console.log(`[Local] DDNS subdomain: ${subdomain}`);\n }\n if (config.oidcIssuer) {\n console.log(`[Local] Using Cloud IdP: ${config.oidcIssuer}`);\n }\n\n if (!cloudflareTunnelToken && !sakuraTunnelToken) {\n console.log('[Local] Note: No tunnel token configured, assuming direct network access');\n }\n}\n\n/**\n * 从 Node Token 解析子域名/用户名\n * Token 格式: username:secret 或 base64 编码\n */\nfunction parseSubdomainFromToken(token: string): string | undefined {\n // 尝试直接解析 username:secret 格式\n if (token.includes(':')) {\n const [username] = token.split(':');\n if (username && /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/.test(username)) {\n return username;\n }\n }\n\n // 尝试 base64 解码\n try {\n const decoded = Buffer.from(token, 'base64').toString('utf8');\n if (decoded.includes(':')) {\n const [username] = decoded.split(':');\n if (username && /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/.test(username)) {\n return username;\n }\n }\n } catch {\n // ignore\n }\n\n return undefined;\n}\n"]}
@@ -39,10 +39,10 @@ const WebIdProfileHandler_1 = require("../handlers/WebIdProfileHandler");
39
39
  const DdnsHandler_1 = require("../handlers/DdnsHandler");
40
40
  const ChatKitHandler_1 = require("../handlers/ChatKitHandler");
41
41
  const ChatKitV1Handler_1 = require("../handlers/ChatKitV1Handler");
42
- const VectorHandler_1 = require("../handlers/VectorHandler");
43
42
  const DashboardHandler_1 = require("../handlers/DashboardHandler");
44
43
  const AdminHandler_1 = require("../handlers/AdminHandler");
45
44
  const AdminDdnsHandler_1 = require("../handlers/AdminDdnsHandler");
45
+ const LinxCapabilitiesHandler_1 = require("../handlers/LinxCapabilitiesHandler");
46
46
  const ProvisionHandler_1 = require("../handlers/ProvisionHandler");
47
47
  const PodManagementHandler_1 = require("../handlers/PodManagementHandler");
48
48
  const QuotaHandler_1 = require("../handlers/QuotaHandler");
@@ -96,7 +96,6 @@ function registerSharedRoutes(container, server) {
96
96
  const chatService = container.resolve('chatService');
97
97
  const chatKitService = container.resolve('chatKitService');
98
98
  const chatKitStore = container.resolve('chatKitStore');
99
- const vectorService = container.resolve('vectorService');
100
99
  const config = container.resolve('config');
101
100
  (0, EdgeNodeSignalHandler_1.registerEdgeNodeSignalRoutes)(server, {
102
101
  repository: nodeRepo,
@@ -108,7 +107,6 @@ function registerSharedRoutes(container, server) {
108
107
  (0, ChatHandler_1.registerChatRoutes)(server, { chatService });
109
108
  (0, ChatKitHandler_1.registerChatKitRoutes)(server, { chatKitService });
110
109
  (0, ChatKitV1Handler_1.registerChatKitV1Routes)(server, { store: chatKitStore });
111
- (0, VectorHandler_1.registerVectorRoutes)(server, { vectorService });
112
110
  // Quota & Usage API (Business 对接)
113
111
  try {
114
112
  const quotaService = new DrizzleQuotaService_1.DrizzleQuotaService({ identityDbUrl: config.databaseUrl });
@@ -176,7 +174,15 @@ function registerCloudRoutes(container, server) {
176
174
  const config = container.resolve('config');
177
175
  const baseUrl = process.env.CSS_BASE_URL || 'http://localhost:3000/';
178
176
  const baseStorageDomain = config.subdomain?.baseStorageDomain;
179
- (0, ProvisionHandler_1.registerProvisionRoutes)(server, { repository: nodeRepo, baseUrl, baseStorageDomain });
177
+ const ddnsRepo = container.resolve('ddnsRepo', { allowUnregistered: true });
178
+ const tunnelProvider = container.resolve('tunnelProvider', { allowUnregistered: true });
179
+ (0, ProvisionHandler_1.registerProvisionRoutes)(server, {
180
+ repository: nodeRepo,
181
+ ddnsRepo,
182
+ tunnelProvider,
183
+ baseUrl,
184
+ baseStorageDomain,
185
+ });
180
186
  console.log(`[Cloud] Provision routes registered${baseStorageDomain ? ` (baseStorageDomain: ${baseStorageDomain})` : ''}`);
181
187
  }
182
188
  catch {
@@ -187,6 +193,7 @@ function registerCloudRoutes(container, server) {
187
193
  * Local 模式专属路由
188
194
  */
189
195
  function registerLocalRoutes(container, server) {
196
+ (0, LinxCapabilitiesHandler_1.registerLinxCapabilitiesRoutes)(server);
190
197
  // Admin API (配置管理、重启)
191
198
  (0, AdminHandler_1.registerAdminRoutes)(server);
192
199
  // DDNS status (托管式 Local 模式)
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/api/container/routes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 路由注册\n *\n * 根据容器中的服务注册 API 路由\n */\n\nimport type { AwilixContainer } from 'awilix';\nimport type { ApiContainerCradle, ApiContainerConfig } from './types';\nimport type { ApiServer } from '../ApiServer';\n\nimport { registerEdgeNodeSignalRoutes } from '../handlers/EdgeNodeSignalHandler';\nimport { registerNodeRoutes } from '../handlers/NodeHandler';\nimport { registerChatRoutes } from '../handlers/ChatHandler';\nimport { registerApiKeyRoutes } from '../handlers/ApiKeyHandler';\nimport { registerSubdomainRoutes } from '../handlers/SubdomainHandler';\nimport { registerSubdomainClientRoutes } from '../handlers/SubdomainClientHandler';\nimport { registerWebIdProfileRoutes } from '../handlers/WebIdProfileHandler';\nimport { registerDdnsRoutes } from '../handlers/DdnsHandler';\nimport { registerChatKitRoutes } from '../handlers/ChatKitHandler';\nimport { registerChatKitV1Routes } from '../handlers/ChatKitV1Handler';\nimport { registerVectorRoutes } from '../handlers/VectorHandler';\nimport { registerDashboardRoutes } from '../handlers/DashboardHandler';\nimport { registerAdminRoutes } from '../handlers/AdminHandler';\nimport { registerAdminDdnsRoutes } from '../handlers/AdminDdnsHandler';\nimport { registerProvisionRoutes, registerProvisionStatusRoute } from '../handlers/ProvisionHandler';\nimport { registerPodManagementRoutes } from '../handlers/PodManagementHandler';\nimport { registerQuotaRoutes } from '../handlers/QuotaHandler';\nimport { registerUsageRoutes } from '../handlers/UsageHandler';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { DrizzleClientCredentialsStore } from '../store/DrizzleClientCredentialsStore';\nimport { UsageRepository } from '../../storage/quota/UsageRepository';\nimport { DrizzleQuotaService } from '../../quota/DrizzleQuotaService';\nimport * as path from 'node:path';\nimport { PACKAGE_ROOT } from '../../runtime';\n\n/**\n * 注册所有 API 路由\n */\nexport function registerRoutes(container: AwilixContainer<ApiContainerCradle>): void {\n const server = container.resolve('apiServer') as ApiServer;\n const config = container.resolve('config') as ApiContainerConfig;\n\n // 公共健康检查端点\n registerHealthRoutes(server);\n\n // 共享路由\n registerSharedRoutes(container, server);\n\n // 根据 edition 注册专属路由\n if (config.edition === 'cloud') {\n registerCloudRoutes(container, server);\n } else {\n registerLocalRoutes(container, server);\n }\n}\n\n/**\n * 健康检查路由\n */\nfunction registerHealthRoutes(server: ApiServer): void {\n server.get('/health', async (_req, res) => {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'ok' }));\n }, { public: true });\n\n server.get('/ready', async (_req, res) => {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'ready' }));\n }, { public: true });\n\n // Dashboard 静态资源\n const staticDir = path.resolve(PACKAGE_ROOT, 'static/dashboard');\n registerDashboardRoutes(server, { staticDir });\n}\n\n/**\n * 共享路由 (cloud 和 local 都有)\n */\nfunction registerSharedRoutes(\n container: AwilixContainer<ApiContainerCradle>,\n server: ApiServer,\n): void {\n const nodeRepo = container.resolve('nodeRepo') as EdgeNodeRepository;\n const apiKeyStore = container.resolve('apiKeyStore') as DrizzleClientCredentialsStore;\n const chatService = container.resolve('chatService');\n const chatKitService = container.resolve('chatKitService');\n const chatKitStore = container.resolve('chatKitStore');\n const vectorService = container.resolve('vectorService');\n const config = container.resolve('config') as ApiContainerConfig;\n\n registerEdgeNodeSignalRoutes(server, {\n repository: nodeRepo,\n dnsCoordinator: container.resolve('dnsCoordinator', { allowUnregistered: true }) as any,\n healthProbeService: container.resolve('healthProbeService', { allowUnregistered: true }) as any,\n });\n registerNodeRoutes(server, { repository: nodeRepo });\n registerApiKeyRoutes(server, { store: apiKeyStore });\n registerChatRoutes(server, { chatService });\n registerChatKitRoutes(server, { chatKitService });\n registerChatKitV1Routes(server, { store: chatKitStore });\n registerVectorRoutes(server, { vectorService });\n\n // Quota & Usage API (Business 对接)\n try {\n const quotaService = new DrizzleQuotaService({ identityDbUrl: config.databaseUrl });\n const usageRepo = new UsageRepository(container.resolve('db'));\n registerQuotaRoutes(server, { quotaService, usageRepo });\n registerUsageRoutes(server, { usageRepo });\n console.log('[Shared] Quota & Usage routes registered');\n } catch (error) {\n console.log(`[Shared] Quota & Usage routes not registered: ${error}`);\n }\n}\n\n/**\n * Cloud 模式专属路由\n */\nfunction registerCloudRoutes(\n container: AwilixContainer<ApiContainerCradle>,\n server: ApiServer,\n): void {\n // 子域名管理 API (需要 SubdomainService)\n try {\n const subdomainService = container.resolve('subdomainService') as ApiContainerCradle['subdomainService'];\n if (subdomainService) {\n registerSubdomainRoutes(server, { subdomainService });\n console.log('[Cloud] Subdomain routes registered');\n }\n } catch {\n console.log('[Cloud] Subdomain routes not registered (service not available)');\n }\n\n // WebID Profile 托管服务\n try {\n const profileRepo = container.resolve('webIdProfileRepo', { allowUnregistered: true });\n if (profileRepo) {\n registerWebIdProfileRoutes(server, { profileRepo: profileRepo as any });\n console.log('[Cloud] WebID Profile routes registered');\n }\n } catch {\n console.log('[Cloud] WebID Profile routes not registered (repo not available)');\n }\n\n // DDNS 服务\n try {\n const ddnsRepo = container.resolve('ddnsRepo', { allowUnregistered: true });\n const dnsProvider = container.resolve('dnsProvider', { allowUnregistered: true });\n const config = container.resolve('config') as ApiContainerConfig;\n\n if (ddnsRepo) {\n const baseStorageDomain = config.subdomain?.baseStorageDomain;\n if (baseStorageDomain) {\n registerDdnsRoutes(server, {\n ddnsRepo: ddnsRepo as any,\n dnsProvider: dnsProvider as any,\n defaultDomain: baseStorageDomain,\n });\n console.log(`[Cloud] DDNS routes registered (domain: ${baseStorageDomain})`);\n } else {\n console.log('[Cloud] DDNS routes not registered (no CSS_BASE_STORAGE_DOMAIN)');\n }\n }\n } catch {\n console.log('[Cloud] DDNS routes not registered (repo not available)');\n }\n\n // SP Provision API (SP 注册)\n try {\n const nodeRepo = container.resolve('nodeRepo') as EdgeNodeRepository;\n const config = container.resolve('config') as ApiContainerConfig;\n const baseUrl = process.env.CSS_BASE_URL || 'http://localhost:3000/';\n const baseStorageDomain = config.subdomain?.baseStorageDomain;\n registerProvisionRoutes(server, { repository: nodeRepo, baseUrl, baseStorageDomain });\n console.log(`[Cloud] Provision routes registered${baseStorageDomain ? ` (baseStorageDomain: ${baseStorageDomain})` : ''}`);\n } catch {\n console.log('[Cloud] Provision routes not registered (dependencies not available)');\n }\n}\n\n/**\n * Local 模式专属路由\n */\nfunction registerLocalRoutes(\n container: AwilixContainer<ApiContainerCradle>,\n server: ApiServer,\n): void {\n // Admin API (配置管理、重启)\n registerAdminRoutes(server);\n\n // DDNS status (托管式 Local 模式)\n try {\n const ddnsManager = container.resolve('ddnsManager', { allowUnregistered: true }) as any;\n registerAdminDdnsRoutes(server, { ddnsManager });\n } catch {\n // ignore\n }\n\n // 子域名客户端 API (通过 SubdomainClient 调用 Cloud)\n try {\n const subdomainClient = container.resolve('subdomainClient') as ApiContainerCradle['subdomainClient'];\n if (subdomainClient) {\n registerSubdomainClientRoutes(server, { subdomainClient });\n console.log('[Local] Subdomain client routes registered');\n }\n } catch {\n console.log('[Local] Subdomain client routes not registered (client not available)');\n }\n\n // Pod Provision API (SP 端,供 Cloud 回调创建 Pod)\n try {\n // rootDir: CSS 数据目录,默认 ./data\n const rootDir = process.env.CSS_ROOT_FILE_PATH || './data';\n // serviceToken 验证:从 SP 配置中读取\n const expectedServiceToken = process.env.XPOD_SERVICE_TOKEN;\n\n if (expectedServiceToken) {\n registerPodManagementRoutes(server, {\n rootDir,\n verifyServiceToken: async (token: string) => token === expectedServiceToken,\n });\n console.log('[Local] Pod provision routes registered (/provision/pods)');\n } else {\n console.log('[Local] Pod provision routes not registered (XPOD_SERVICE_TOKEN not configured)');\n }\n } catch (error) {\n console.log(`[Local] Pod provision routes not registered: ${error}`);\n }\n\n // SP 状态查询 (供 Linx 查询 SP 配置状态)\n try {\n const config = container.resolve('config') as ApiContainerConfig;\n registerProvisionStatusRoute(server, {\n cloudUrl: config.cloudApiEndpoint,\n nodeId: config.nodeId,\n cloudBaseUrl: config.oidcIssuer || config.cloudApiEndpoint,\n });\n console.log('[Local] Provision status route registered (/provision/status)');\n } catch (error) {\n console.log(`[Local] Provision status route not registered: ${error}`);\n }\n}\n"]}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/api/container/routes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 路由注册\n *\n * 根据容器中的服务注册 API 路由\n */\n\nimport type { AwilixContainer } from 'awilix';\nimport type { ApiContainerCradle, ApiContainerConfig } from './types';\nimport type { ApiServer } from '../ApiServer';\n\nimport { registerEdgeNodeSignalRoutes } from '../handlers/EdgeNodeSignalHandler';\nimport { registerNodeRoutes } from '../handlers/NodeHandler';\nimport { registerChatRoutes } from '../handlers/ChatHandler';\nimport { registerApiKeyRoutes } from '../handlers/ApiKeyHandler';\nimport { registerSubdomainRoutes } from '../handlers/SubdomainHandler';\nimport { registerSubdomainClientRoutes } from '../handlers/SubdomainClientHandler';\nimport { registerWebIdProfileRoutes } from '../handlers/WebIdProfileHandler';\nimport { registerDdnsRoutes } from '../handlers/DdnsHandler';\nimport { registerChatKitRoutes } from '../handlers/ChatKitHandler';\nimport { registerChatKitV1Routes } from '../handlers/ChatKitV1Handler';\nimport { registerDashboardRoutes } from '../handlers/DashboardHandler';\nimport { registerAdminRoutes } from '../handlers/AdminHandler';\nimport { registerAdminDdnsRoutes } from '../handlers/AdminDdnsHandler';\nimport { registerLinxCapabilitiesRoutes } from '../handlers/LinxCapabilitiesHandler';\nimport { registerProvisionRoutes, registerProvisionStatusRoute } from '../handlers/ProvisionHandler';\nimport { registerPodManagementRoutes } from '../handlers/PodManagementHandler';\nimport { registerQuotaRoutes } from '../handlers/QuotaHandler';\nimport { registerUsageRoutes } from '../handlers/UsageHandler';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { DrizzleClientCredentialsStore } from '../store/DrizzleClientCredentialsStore';\nimport { UsageRepository } from '../../storage/quota/UsageRepository';\nimport { DrizzleQuotaService } from '../../quota/DrizzleQuotaService';\nimport * as path from 'node:path';\nimport { PACKAGE_ROOT } from '../../runtime';\n\n/**\n * 注册所有 API 路由\n */\nexport function registerRoutes(container: AwilixContainer<ApiContainerCradle>): void {\n const server = container.resolve('apiServer') as ApiServer;\n const config = container.resolve('config') as ApiContainerConfig;\n\n // 公共健康检查端点\n registerHealthRoutes(server);\n\n // 共享路由\n registerSharedRoutes(container, server);\n\n // 根据 edition 注册专属路由\n if (config.edition === 'cloud') {\n registerCloudRoutes(container, server);\n } else {\n registerLocalRoutes(container, server);\n }\n}\n\n/**\n * 健康检查路由\n */\nfunction registerHealthRoutes(server: ApiServer): void {\n server.get('/health', async (_req, res) => {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'ok' }));\n }, { public: true });\n\n server.get('/ready', async (_req, res) => {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ status: 'ready' }));\n }, { public: true });\n\n // Dashboard 静态资源\n const staticDir = path.resolve(PACKAGE_ROOT, 'static/dashboard');\n registerDashboardRoutes(server, { staticDir });\n}\n\n/**\n * 共享路由 (cloud 和 local 都有)\n */\nfunction registerSharedRoutes(\n container: AwilixContainer<ApiContainerCradle>,\n server: ApiServer,\n): void {\n const nodeRepo = container.resolve('nodeRepo') as EdgeNodeRepository;\n const apiKeyStore = container.resolve('apiKeyStore') as DrizzleClientCredentialsStore;\n const chatService = container.resolve('chatService');\n const chatKitService = container.resolve('chatKitService');\n const chatKitStore = container.resolve('chatKitStore');\n const config = container.resolve('config') as ApiContainerConfig;\n\n registerEdgeNodeSignalRoutes(server, {\n repository: nodeRepo,\n dnsCoordinator: container.resolve('dnsCoordinator', { allowUnregistered: true }) as any,\n healthProbeService: container.resolve('healthProbeService', { allowUnregistered: true }) as any,\n });\n registerNodeRoutes(server, { repository: nodeRepo });\n registerApiKeyRoutes(server, { store: apiKeyStore });\n registerChatRoutes(server, { chatService });\n registerChatKitRoutes(server, { chatKitService });\n registerChatKitV1Routes(server, { store: chatKitStore });\n\n // Quota & Usage API (Business 对接)\n try {\n const quotaService = new DrizzleQuotaService({ identityDbUrl: config.databaseUrl });\n const usageRepo = new UsageRepository(container.resolve('db'));\n registerQuotaRoutes(server, { quotaService, usageRepo });\n registerUsageRoutes(server, { usageRepo });\n console.log('[Shared] Quota & Usage routes registered');\n } catch (error) {\n console.log(`[Shared] Quota & Usage routes not registered: ${error}`);\n }\n}\n\n/**\n * Cloud 模式专属路由\n */\nfunction registerCloudRoutes(\n container: AwilixContainer<ApiContainerCradle>,\n server: ApiServer,\n): void {\n // 子域名管理 API (需要 SubdomainService)\n try {\n const subdomainService = container.resolve('subdomainService') as ApiContainerCradle['subdomainService'];\n if (subdomainService) {\n registerSubdomainRoutes(server, { subdomainService });\n console.log('[Cloud] Subdomain routes registered');\n }\n } catch {\n console.log('[Cloud] Subdomain routes not registered (service not available)');\n }\n\n // WebID Profile 托管服务\n try {\n const profileRepo = container.resolve('webIdProfileRepo', { allowUnregistered: true });\n if (profileRepo) {\n registerWebIdProfileRoutes(server, { profileRepo: profileRepo as any });\n console.log('[Cloud] WebID Profile routes registered');\n }\n } catch {\n console.log('[Cloud] WebID Profile routes not registered (repo not available)');\n }\n\n // DDNS 服务\n try {\n const ddnsRepo = container.resolve('ddnsRepo', { allowUnregistered: true });\n const dnsProvider = container.resolve('dnsProvider', { allowUnregistered: true });\n const config = container.resolve('config') as ApiContainerConfig;\n\n if (ddnsRepo) {\n const baseStorageDomain = config.subdomain?.baseStorageDomain;\n if (baseStorageDomain) {\n registerDdnsRoutes(server, {\n ddnsRepo: ddnsRepo as any,\n dnsProvider: dnsProvider as any,\n defaultDomain: baseStorageDomain,\n });\n console.log(`[Cloud] DDNS routes registered (domain: ${baseStorageDomain})`);\n } else {\n console.log('[Cloud] DDNS routes not registered (no CSS_BASE_STORAGE_DOMAIN)');\n }\n }\n } catch {\n console.log('[Cloud] DDNS routes not registered (repo not available)');\n }\n\n // SP Provision API (SP 注册)\n try {\n const nodeRepo = container.resolve('nodeRepo') as EdgeNodeRepository;\n const config = container.resolve('config') as ApiContainerConfig;\n const baseUrl = process.env.CSS_BASE_URL || 'http://localhost:3000/';\n const baseStorageDomain = config.subdomain?.baseStorageDomain;\n const ddnsRepo = container.resolve('ddnsRepo', { allowUnregistered: true }) as any;\n const tunnelProvider = container.resolve('tunnelProvider', { allowUnregistered: true }) as any;\n registerProvisionRoutes(server, {\n repository: nodeRepo,\n ddnsRepo,\n tunnelProvider,\n baseUrl,\n baseStorageDomain,\n });\n console.log(`[Cloud] Provision routes registered${baseStorageDomain ? ` (baseStorageDomain: ${baseStorageDomain})` : ''}`);\n } catch {\n console.log('[Cloud] Provision routes not registered (dependencies not available)');\n }\n}\n\n/**\n * Local 模式专属路由\n */\nfunction registerLocalRoutes(\n container: AwilixContainer<ApiContainerCradle>,\n server: ApiServer,\n): void {\n registerLinxCapabilitiesRoutes(server);\n\n // Admin API (配置管理、重启)\n registerAdminRoutes(server);\n\n // DDNS status (托管式 Local 模式)\n try {\n const ddnsManager = container.resolve('ddnsManager', { allowUnregistered: true }) as any;\n registerAdminDdnsRoutes(server, { ddnsManager });\n } catch {\n // ignore\n }\n\n // 子域名客户端 API (通过 SubdomainClient 调用 Cloud)\n try {\n const subdomainClient = container.resolve('subdomainClient') as ApiContainerCradle['subdomainClient'];\n if (subdomainClient) {\n registerSubdomainClientRoutes(server, { subdomainClient });\n console.log('[Local] Subdomain client routes registered');\n }\n } catch {\n console.log('[Local] Subdomain client routes not registered (client not available)');\n }\n\n // Pod Provision API (SP 端,供 Cloud 回调创建 Pod)\n try {\n // rootDir: CSS 数据目录,默认 ./data\n const rootDir = process.env.CSS_ROOT_FILE_PATH || './data';\n // serviceToken 验证:从 SP 配置中读取\n const expectedServiceToken = process.env.XPOD_SERVICE_TOKEN;\n\n if (expectedServiceToken) {\n registerPodManagementRoutes(server, {\n rootDir,\n verifyServiceToken: async (token: string) => token === expectedServiceToken,\n });\n console.log('[Local] Pod provision routes registered (/provision/pods)');\n } else {\n console.log('[Local] Pod provision routes not registered (XPOD_SERVICE_TOKEN not configured)');\n }\n } catch (error) {\n console.log(`[Local] Pod provision routes not registered: ${error}`);\n }\n\n // SP 状态查询 (供 Linx 查询 SP 配置状态)\n try {\n const config = container.resolve('config') as ApiContainerConfig;\n registerProvisionStatusRoute(server, {\n cloudUrl: config.cloudApiEndpoint,\n nodeId: config.nodeId,\n cloudBaseUrl: config.oidcIssuer || config.cloudApiEndpoint,\n });\n console.log('[Local] Provision status route registered (/provision/status)');\n } catch (error) {\n console.log(`[Local] Provision status route not registered: ${error}`);\n }\n}\n"]}
@@ -13,21 +13,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.registerDdnsRoutes = registerDdnsRoutes;
14
14
  const global_logger_factory_1 = require("global-logger-factory");
15
15
  const logger = (0, global_logger_factory_1.getLoggerFor)('DdnsHandler');
16
- function pickRecordType(options) {
17
- if (options.mode === 'tunnel') {
18
- return 'CNAME';
19
- }
20
- return options.ipv6Address ? 'AAAA' : 'A';
21
- }
22
- function pickRecordValue(options) {
23
- if (options.mode === 'tunnel') {
24
- if (options.tunnelProvider === 'cloudflare') {
25
- return `${options.subdomain}.cfargotunnel.com`;
26
- }
27
- return undefined;
28
- }
29
- return options.ipv6Address ?? options.ipAddress;
30
- }
31
16
  function registerDdnsRoutes(server, options) {
32
17
  const { ddnsRepo, dnsProvider, defaultDomain } = options;
33
18
  /**
@@ -61,38 +46,25 @@ function registerDdnsRoutes(server, options) {
61
46
  sendError(response, 409, 'Subdomain already allocated');
62
47
  return;
63
48
  }
64
- const mode = payload.mode === 'tunnel' ? 'tunnel' : 'direct';
65
- const recordType = pickRecordType({
66
- mode,
67
- ipAddress: payload.ipAddress,
68
- ipv6Address: payload.ipv6Address,
69
- });
70
- const recordValue = pickRecordValue({
71
- subdomain: payload.subdomain,
72
- mode,
73
- tunnelProvider: payload.tunnelProvider,
74
- ipAddress: payload.ipAddress,
75
- ipv6Address: payload.ipv6Address,
76
- });
77
49
  const record = await ddnsRepo.allocateSubdomain({
78
50
  subdomain: payload.subdomain,
79
51
  domain: defaultDomain,
80
52
  nodeId: payload.nodeId,
81
53
  username: payload.username,
82
- ipAddress: mode === 'direct' ? payload.ipAddress : undefined,
83
- ipv6Address: mode === 'direct' ? payload.ipv6Address : undefined,
84
- recordType,
54
+ ipAddress: payload.ipAddress,
55
+ ipv6Address: payload.ipv6Address,
85
56
  });
86
- if (dnsProvider && recordValue) {
57
+ // 如果有 DNS Provider 且有 IP,创建 DNS 记录
58
+ if (dnsProvider && (payload.ipAddress || payload.ipv6Address)) {
87
59
  try {
88
60
  await dnsProvider.upsertRecord({
89
61
  domain: defaultDomain,
90
62
  subdomain: payload.subdomain,
91
- type: recordType,
92
- value: recordValue,
63
+ type: payload.ipv6Address ? 'AAAA' : 'A',
64
+ value: payload.ipv6Address ?? payload.ipAddress,
93
65
  ttl: 60,
94
66
  });
95
- logger.info(`Created DNS record: ${payload.subdomain}.${defaultDomain} (${recordType})`);
67
+ logger.info(`Created DNS record: ${payload.subdomain}.${defaultDomain}`);
96
68
  }
97
69
  catch (dnsError) {
98
70
  logger.error(`Failed to create DNS record: ${dnsError}`);
@@ -107,6 +79,7 @@ function registerDdnsRoutes(server, options) {
107
79
  fqdn: `${record.subdomain}.${record.domain}`,
108
80
  ipAddress: record.ipAddress,
109
81
  ipv6Address: record.ipv6Address,
82
+ tunnelProvider: payload.tunnelProvider,
110
83
  createdAt: record.createdAt.toISOString(),
111
84
  });
112
85
  }
@@ -137,31 +110,43 @@ function registerDdnsRoutes(server, options) {
137
110
  const payload = body;
138
111
  const ipAddress = payload?.ip ?? payload?.ipAddress;
139
112
  const ipv6Address = payload?.ipv6Address;
140
- const mode = payload?.mode === 'tunnel' ? 'tunnel' : 'direct';
141
- if (mode === 'direct' && !ipAddress && !ipv6Address) {
113
+ const mode = payload?.mode ?? 'direct';
114
+ if (!ipAddress && !ipv6Address && mode !== 'tunnel') {
142
115
  sendError(response, 400, 'ip or ipv6Address is required');
143
116
  return;
144
117
  }
145
- const recordType = pickRecordType({ mode, ipAddress, ipv6Address });
146
- const recordValue = pickRecordValue({
147
- subdomain,
148
- mode,
149
- tunnelProvider: payload?.tunnelProvider,
150
- ipAddress,
151
- ipv6Address,
152
- });
118
+ if (mode === 'tunnel' && !ipAddress && !ipv6Address) {
119
+ const existing = await ddnsRepo.getRecord(subdomain);
120
+ if (!existing) {
121
+ sendError(response, 404, 'Subdomain not found');
122
+ return;
123
+ }
124
+ sendJson(response, 200, {
125
+ success: true,
126
+ subdomain: existing.subdomain,
127
+ domain: existing.domain,
128
+ fqdn: `${existing.subdomain}.${existing.domain}`,
129
+ ipAddress: existing.ipAddress,
130
+ ipv6Address: existing.ipv6Address,
131
+ tunnelProvider: payload?.tunnelProvider,
132
+ updatedAt: existing.updatedAt.toISOString(),
133
+ });
134
+ return;
135
+ }
153
136
  // 更新数据库记录
154
137
  const record = await ddnsRepo.updateRecordIp(subdomain, {
155
- ipAddress: mode === 'direct' ? (ipAddress ?? null) : null,
156
- ipv6Address: mode === 'direct' ? (ipv6Address ?? null) : null,
157
- recordType,
138
+ ipAddress,
139
+ ipv6Address,
158
140
  });
159
141
  if (!record) {
160
142
  sendError(response, 404, 'Subdomain not found');
161
143
  return;
162
144
  }
163
- if (dnsProvider && recordValue) {
145
+ // 更新 DNS 记录
146
+ if (dnsProvider) {
164
147
  try {
148
+ const recordType = ipv6Address ? 'AAAA' : 'A';
149
+ const recordValue = ipv6Address ?? ipAddress;
165
150
  await dnsProvider.upsertRecord({
166
151
  domain: record.domain,
167
152
  subdomain: record.subdomain,
@@ -183,6 +168,7 @@ function registerDdnsRoutes(server, options) {
183
168
  fqdn: `${record.subdomain}.${record.domain}`,
184
169
  ipAddress: record.ipAddress,
185
170
  ipv6Address: record.ipv6Address,
171
+ tunnelProvider: payload?.tunnelProvider,
186
172
  updatedAt: record.updatedAt.toISOString(),
187
173
  });
188
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DdnsHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/DdnsHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS API Handler\n *\n * 提供 DDNS 服务的 HTTP API\n *\n * POST /api/v1/ddns/{subdomain} - 更新 DNS 记录\n * GET /api/v1/ddns/{subdomain} - 查询 DNS 记录\n * POST /api/v1/ddns/allocate - 分配子域名\n * DELETE /api/v1/ddns/{subdomain} - 释放子域名\n */\n\nimport type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { DdnsRepository } from '../../identity/drizzle/DdnsRepository';\nimport type { DnsProvider } from '../../dns/DnsProvider';\n\nconst logger = getLoggerFor('DdnsHandler');\n\nexport interface DdnsHandlerOptions {\n ddnsRepo: DdnsRepository;\n dnsProvider?: DnsProvider;\n defaultDomain: string;\n}\n\ntype DdnsMode = 'direct' | 'tunnel';\ntype DdnsTunnelProvider = 'cloudflare' | 'sakura_frp' | 'none';\n\nfunction pickRecordType(options: {\n mode: DdnsMode;\n ipAddress?: string;\n ipv6Address?: string;\n}): 'A' | 'AAAA' | 'CNAME' {\n if (options.mode === 'tunnel') {\n return 'CNAME';\n }\n return options.ipv6Address ? 'AAAA' : 'A';\n}\n\nfunction pickRecordValue(options: {\n subdomain: string;\n mode: DdnsMode;\n tunnelProvider?: DdnsTunnelProvider;\n ipAddress?: string;\n ipv6Address?: string;\n}): string | undefined {\n if (options.mode === 'tunnel') {\n if (options.tunnelProvider === 'cloudflare') {\n return `${options.subdomain}.cfargotunnel.com`;\n }\n return undefined;\n }\n return options.ipv6Address ?? options.ipAddress;\n}\n\nexport function registerDdnsRoutes(\n server: ApiServer,\n options: DdnsHandlerOptions,\n): void {\n const { ddnsRepo, dnsProvider, defaultDomain } = options;\n\n /**\n * POST /api/v1/ddns/allocate\n *\n * 分配子域名\n *\n * Request body:\n * {\n * \"subdomain\": \"alice\",\n * \"nodeId\": \"node-xxx\", // optional\n * \"ipAddress\": \"1.2.3.4\" // optional\n * }\n */\n server.post('/api/v1/ddns/allocate', async (request, response, _params) => {\n try {\n const body = await readJsonBody(request);\n const payload = body as {\n subdomain?: string;\n nodeId?: string;\n username?: string;\n ipAddress?: string;\n ipv6Address?: string;\n mode?: DdnsMode;\n tunnelProvider?: DdnsTunnelProvider;\n } | undefined;\n\n if (!payload?.subdomain) {\n sendError(response, 400, 'subdomain is required');\n return;\n }\n\n // 验证子域名格式\n if (!/^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/.test(payload.subdomain)) {\n sendError(response, 400, 'Invalid subdomain format');\n return;\n }\n\n // 检查是否已存在\n const existing = await ddnsRepo.getRecord(payload.subdomain);\n if (existing) {\n sendError(response, 409, 'Subdomain already allocated');\n return;\n }\n\n const mode = payload.mode === 'tunnel' ? 'tunnel' : 'direct';\n const recordType = pickRecordType({\n mode,\n ipAddress: payload.ipAddress,\n ipv6Address: payload.ipv6Address,\n });\n const recordValue = pickRecordValue({\n subdomain: payload.subdomain,\n mode,\n tunnelProvider: payload.tunnelProvider,\n ipAddress: payload.ipAddress,\n ipv6Address: payload.ipv6Address,\n });\n\n const record = await ddnsRepo.allocateSubdomain({\n subdomain: payload.subdomain,\n domain: defaultDomain,\n nodeId: payload.nodeId,\n username: payload.username,\n ipAddress: mode === 'direct' ? payload.ipAddress : undefined,\n ipv6Address: mode === 'direct' ? payload.ipv6Address : undefined,\n recordType,\n });\n\n if (dnsProvider && recordValue) {\n try {\n await dnsProvider.upsertRecord({\n domain: defaultDomain,\n subdomain: payload.subdomain,\n type: recordType,\n value: recordValue,\n ttl: 60,\n });\n logger.info(`Created DNS record: ${payload.subdomain}.${defaultDomain} (${recordType})`);\n } catch (dnsError) {\n logger.error(`Failed to create DNS record: ${dnsError}`);\n // 不回滚数据库记录,DNS 可以稍后重试\n }\n }\n\n logger.info(`Allocated subdomain: ${payload.subdomain}.${defaultDomain}`);\n\n sendJson(response, 201, {\n success: true,\n subdomain: record.subdomain,\n domain: record.domain,\n fqdn: `${record.subdomain}.${record.domain}`,\n ipAddress: record.ipAddress,\n ipv6Address: record.ipv6Address,\n createdAt: record.createdAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to allocate subdomain: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * POST /api/v1/ddns/{subdomain}\n *\n * 更新 DNS 记录\n *\n * Request body:\n * {\n * \"ip\": \"1.2.3.4\",\n * \"type\": \"A\" // optional, default: \"A\"\n * }\n */\n server.post('/api/v1/ddns/:subdomain', async (request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n // 跳过 allocate 路由\n if (subdomain === 'allocate') {\n return;\n }\n\n try {\n const body = await readJsonBody(request);\n const payload = body as {\n ip?: string;\n ipAddress?: string;\n ipv6Address?: string;\n type?: string;\n mode?: DdnsMode;\n tunnelProvider?: DdnsTunnelProvider;\n } | undefined;\n\n const ipAddress = payload?.ip ?? payload?.ipAddress;\n const ipv6Address = payload?.ipv6Address;\n\n const mode = payload?.mode === 'tunnel' ? 'tunnel' : 'direct';\n if (mode === 'direct' && !ipAddress && !ipv6Address) {\n sendError(response, 400, 'ip or ipv6Address is required');\n return;\n }\n\n const recordType = pickRecordType({ mode, ipAddress, ipv6Address });\n const recordValue = pickRecordValue({\n subdomain,\n mode,\n tunnelProvider: payload?.tunnelProvider,\n ipAddress,\n ipv6Address,\n });\n\n // 更新数据库记录\n const record = await ddnsRepo.updateRecordIp(subdomain, {\n ipAddress: mode === 'direct' ? (ipAddress ?? null) : null,\n ipv6Address: mode === 'direct' ? (ipv6Address ?? null) : null,\n recordType,\n });\n\n if (!record) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n if (dnsProvider && recordValue) {\n try {\n await dnsProvider.upsertRecord({\n domain: record.domain,\n subdomain: record.subdomain,\n type: recordType,\n value: recordValue,\n ttl: record.ttl,\n });\n logger.info(`Updated DNS record: ${subdomain}.${record.domain} -> ${recordValue}`);\n } catch (dnsError) {\n logger.error(`Failed to update DNS record: ${dnsError}`);\n // 数据库已更新,DNS 更新失败不影响响应\n }\n }\n\n sendJson(response, 200, {\n success: true,\n subdomain: record.subdomain,\n domain: record.domain,\n fqdn: `${record.subdomain}.${record.domain}`,\n ipAddress: record.ipAddress,\n ipv6Address: record.ipv6Address,\n updatedAt: record.updatedAt.toISOString(),\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('banned')) {\n sendError(response, 403, error.message);\n return;\n }\n logger.error(`Failed to update DDNS record: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * GET /api/v1/ddns/{subdomain}\n *\n * 查询 DNS 记录\n */\n server.get('/api/v1/ddns/:subdomain', async (_request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n try {\n const record = await ddnsRepo.getRecord(subdomain);\n\n if (!record) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n sendJson(response, 200, {\n subdomain: record.subdomain,\n domain: record.domain,\n fqdn: `${record.subdomain}.${record.domain}`,\n ipAddress: record.ipAddress,\n ipv6Address: record.ipv6Address,\n recordType: record.recordType,\n status: record.status,\n ttl: record.ttl,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to get DDNS record: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * DELETE /api/v1/ddns/{subdomain}\n *\n * 释放子域名\n */\n server.delete('/api/v1/ddns/:subdomain', async (_request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n try {\n const record = await ddnsRepo.getRecord(subdomain);\n\n if (!record) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n // 删除 DNS 记录\n if (dnsProvider) {\n try {\n await dnsProvider.deleteRecord({\n domain: record.domain,\n subdomain: record.subdomain,\n type: record.recordType,\n });\n logger.info(`Deleted DNS record: ${subdomain}.${record.domain}`);\n } catch (dnsError) {\n logger.error(`Failed to delete DNS record: ${dnsError}`);\n }\n }\n\n // 删除数据库记录\n await ddnsRepo.releaseSubdomain(subdomain);\n\n logger.info(`Released subdomain: ${subdomain}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Subdomain ${subdomain} released`,\n });\n } catch (error) {\n logger.error(`Failed to release subdomain: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * POST /api/v1/ddns/{subdomain}/ban\n *\n * 封禁子域名 (管理员)\n */\n server.post('/api/v1/ddns/:subdomain/ban', async (request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n try {\n const body = await readJsonBody(request);\n const payload = body as { reason?: string } | undefined;\n\n const reason = payload?.reason ?? 'Banned by administrator';\n\n await ddnsRepo.banSubdomain(subdomain, reason);\n\n // 删除 DNS 记录\n if (dnsProvider) {\n const record = await ddnsRepo.getRecord(subdomain);\n if (record) {\n try {\n await dnsProvider.deleteRecord({\n domain: record.domain,\n subdomain: record.subdomain,\n type: record.recordType,\n });\n } catch (dnsError) {\n logger.error(`Failed to delete DNS record for banned subdomain: ${dnsError}`);\n }\n }\n }\n\n logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Subdomain ${subdomain} banned`,\n reason,\n });\n } catch (error) {\n logger.error(`Failed to ban subdomain: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n logger.info('DDNS routes registered');\n}\n\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction sendError(response: ServerResponse, status: number, message: string): void {\n sendJson(response, status, { error: message });\n}\n"]}
1
+ {"version":3,"file":"DdnsHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/DdnsHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS API Handler\n *\n * 提供 DDNS 服务的 HTTP API\n *\n * POST /api/v1/ddns/{subdomain} - 更新 DNS 记录\n * GET /api/v1/ddns/{subdomain} - 查询 DNS 记录\n * POST /api/v1/ddns/allocate - 分配子域名\n * DELETE /api/v1/ddns/{subdomain} - 释放子域名\n */\n\nimport type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { DdnsRepository } from '../../identity/drizzle/DdnsRepository';\nimport type { DnsProvider } from '../../dns/DnsProvider';\n\nconst logger = getLoggerFor('DdnsHandler');\n\nexport interface DdnsHandlerOptions {\n ddnsRepo: DdnsRepository;\n dnsProvider?: DnsProvider;\n defaultDomain: string;\n}\n\nexport function registerDdnsRoutes(\n server: ApiServer,\n options: DdnsHandlerOptions,\n): void {\n const { ddnsRepo, dnsProvider, defaultDomain } = options;\n\n /**\n * POST /api/v1/ddns/allocate\n *\n * 分配子域名\n *\n * Request body:\n * {\n * \"subdomain\": \"alice\",\n * \"nodeId\": \"node-xxx\", // optional\n * \"ipAddress\": \"1.2.3.4\" // optional\n * }\n */\n server.post('/api/v1/ddns/allocate', async (request, response, _params) => {\n try {\n const body = await readJsonBody(request);\n const payload = body as {\n subdomain?: string;\n nodeId?: string;\n username?: string;\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n } | undefined;\n\n if (!payload?.subdomain) {\n sendError(response, 400, 'subdomain is required');\n return;\n }\n\n // 验证子域名格式\n if (!/^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/.test(payload.subdomain)) {\n sendError(response, 400, 'Invalid subdomain format');\n return;\n }\n\n // 检查是否已存在\n const existing = await ddnsRepo.getRecord(payload.subdomain);\n if (existing) {\n sendError(response, 409, 'Subdomain already allocated');\n return;\n }\n\n const record = await ddnsRepo.allocateSubdomain({\n subdomain: payload.subdomain,\n domain: defaultDomain,\n nodeId: payload.nodeId,\n username: payload.username,\n ipAddress: payload.ipAddress,\n ipv6Address: payload.ipv6Address,\n });\n\n // 如果有 DNS Provider 且有 IP,创建 DNS 记录\n if (dnsProvider && (payload.ipAddress || payload.ipv6Address)) {\n try {\n await dnsProvider.upsertRecord({\n domain: defaultDomain,\n subdomain: payload.subdomain,\n type: payload.ipv6Address ? 'AAAA' : 'A',\n value: payload.ipv6Address ?? payload.ipAddress!,\n ttl: 60,\n });\n logger.info(`Created DNS record: ${payload.subdomain}.${defaultDomain}`);\n } catch (dnsError) {\n logger.error(`Failed to create DNS record: ${dnsError}`);\n // 不回滚数据库记录,DNS 可以稍后重试\n }\n }\n\n logger.info(`Allocated subdomain: ${payload.subdomain}.${defaultDomain}`);\n\n sendJson(response, 201, {\n success: true,\n subdomain: record.subdomain,\n domain: record.domain,\n fqdn: `${record.subdomain}.${record.domain}`,\n ipAddress: record.ipAddress,\n ipv6Address: record.ipv6Address,\n tunnelProvider: payload.tunnelProvider,\n createdAt: record.createdAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to allocate subdomain: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * POST /api/v1/ddns/{subdomain}\n *\n * 更新 DNS 记录\n *\n * Request body:\n * {\n * \"ip\": \"1.2.3.4\",\n * \"type\": \"A\" // optional, default: \"A\"\n * }\n */\n server.post('/api/v1/ddns/:subdomain', async (request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n // 跳过 allocate 路由\n if (subdomain === 'allocate') {\n return;\n }\n\n try {\n const body = await readJsonBody(request);\n const payload = body as {\n ip?: string;\n ipAddress?: string;\n ipv6Address?: string;\n type?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n } | undefined;\n\n const ipAddress = payload?.ip ?? payload?.ipAddress;\n const ipv6Address = payload?.ipv6Address;\n const mode = payload?.mode ?? 'direct';\n\n if (!ipAddress && !ipv6Address && mode !== 'tunnel') {\n sendError(response, 400, 'ip or ipv6Address is required');\n return;\n }\n\n if (mode === 'tunnel' && !ipAddress && !ipv6Address) {\n const existing = await ddnsRepo.getRecord(subdomain);\n\n if (!existing) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n sendJson(response, 200, {\n success: true,\n subdomain: existing.subdomain,\n domain: existing.domain,\n fqdn: `${existing.subdomain}.${existing.domain}`,\n ipAddress: existing.ipAddress,\n ipv6Address: existing.ipv6Address,\n tunnelProvider: payload?.tunnelProvider,\n updatedAt: existing.updatedAt.toISOString(),\n });\n return;\n }\n\n // 更新数据库记录\n const record = await ddnsRepo.updateRecordIp(subdomain, {\n ipAddress,\n ipv6Address,\n });\n\n if (!record) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n // 更新 DNS 记录\n if (dnsProvider) {\n try {\n const recordType = ipv6Address ? 'AAAA' : 'A';\n const recordValue = ipv6Address ?? ipAddress!;\n\n await dnsProvider.upsertRecord({\n domain: record.domain,\n subdomain: record.subdomain,\n type: recordType,\n value: recordValue,\n ttl: record.ttl,\n });\n logger.info(`Updated DNS record: ${subdomain}.${record.domain} -> ${recordValue}`);\n } catch (dnsError) {\n logger.error(`Failed to update DNS record: ${dnsError}`);\n // 数据库已更新,DNS 更新失败不影响响应\n }\n }\n\n sendJson(response, 200, {\n success: true,\n subdomain: record.subdomain,\n domain: record.domain,\n fqdn: `${record.subdomain}.${record.domain}`,\n ipAddress: record.ipAddress,\n ipv6Address: record.ipv6Address,\n tunnelProvider: payload?.tunnelProvider,\n updatedAt: record.updatedAt.toISOString(),\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('banned')) {\n sendError(response, 403, error.message);\n return;\n }\n logger.error(`Failed to update DDNS record: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * GET /api/v1/ddns/{subdomain}\n *\n * 查询 DNS 记录\n */\n server.get('/api/v1/ddns/:subdomain', async (_request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n try {\n const record = await ddnsRepo.getRecord(subdomain);\n\n if (!record) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n sendJson(response, 200, {\n subdomain: record.subdomain,\n domain: record.domain,\n fqdn: `${record.subdomain}.${record.domain}`,\n ipAddress: record.ipAddress,\n ipv6Address: record.ipv6Address,\n recordType: record.recordType,\n status: record.status,\n ttl: record.ttl,\n createdAt: record.createdAt.toISOString(),\n updatedAt: record.updatedAt.toISOString(),\n });\n } catch (error) {\n logger.error(`Failed to get DDNS record: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * DELETE /api/v1/ddns/{subdomain}\n *\n * 释放子域名\n */\n server.delete('/api/v1/ddns/:subdomain', async (_request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n try {\n const record = await ddnsRepo.getRecord(subdomain);\n\n if (!record) {\n sendError(response, 404, 'Subdomain not found');\n return;\n }\n\n // 删除 DNS 记录\n if (dnsProvider) {\n try {\n await dnsProvider.deleteRecord({\n domain: record.domain,\n subdomain: record.subdomain,\n type: record.recordType,\n });\n logger.info(`Deleted DNS record: ${subdomain}.${record.domain}`);\n } catch (dnsError) {\n logger.error(`Failed to delete DNS record: ${dnsError}`);\n }\n }\n\n // 删除数据库记录\n await ddnsRepo.releaseSubdomain(subdomain);\n\n logger.info(`Released subdomain: ${subdomain}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Subdomain ${subdomain} released`,\n });\n } catch (error) {\n logger.error(`Failed to release subdomain: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n /**\n * POST /api/v1/ddns/{subdomain}/ban\n *\n * 封禁子域名 (管理员)\n */\n server.post('/api/v1/ddns/:subdomain/ban', async (request, response, params) => {\n const subdomain = decodeURIComponent(params.subdomain);\n\n try {\n const body = await readJsonBody(request);\n const payload = body as { reason?: string } | undefined;\n\n const reason = payload?.reason ?? 'Banned by administrator';\n\n await ddnsRepo.banSubdomain(subdomain, reason);\n\n // 删除 DNS 记录\n if (dnsProvider) {\n const record = await ddnsRepo.getRecord(subdomain);\n if (record) {\n try {\n await dnsProvider.deleteRecord({\n domain: record.domain,\n subdomain: record.subdomain,\n type: record.recordType,\n });\n } catch (dnsError) {\n logger.error(`Failed to delete DNS record for banned subdomain: ${dnsError}`);\n }\n }\n }\n\n logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Subdomain ${subdomain} banned`,\n reason,\n });\n } catch (error) {\n logger.error(`Failed to ban subdomain: ${error}`);\n sendError(response, 500, 'Internal server error');\n }\n });\n\n logger.info('DDNS routes registered');\n}\n\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction sendError(response: ServerResponse, status: number, message: string): void {\n sendJson(response, status, { error: message });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { ApiServer } from '../ApiServer';
2
+ export declare function registerLinxCapabilitiesRoutes(server: ApiServer): void;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerLinxCapabilitiesRoutes = registerLinxCapabilitiesRoutes;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const runtime_1 = require("../../runtime");
10
+ const LINX_LOCAL_ONBOARDING_CONTRACT = 'linx-local-onboarding/v1';
11
+ function registerLinxCapabilitiesRoutes(server) {
12
+ server.get('/api/linx/capabilities', async (_request, response) => {
13
+ sendJson(response, 200, {
14
+ contract: LINX_LOCAL_ONBOARDING_CONTRACT,
15
+ baseUrl: ensureTrailingSlash(process.env.CSS_BASE_URL || 'http://localhost:3000/'),
16
+ version: getVersion(),
17
+ });
18
+ }, { public: true });
19
+ }
20
+ function getVersion() {
21
+ try {
22
+ const packageJsonPath = node_path_1.default.join(runtime_1.PACKAGE_ROOT, 'package.json');
23
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
24
+ return pkg.version ?? 'unknown';
25
+ }
26
+ catch {
27
+ return 'unknown';
28
+ }
29
+ }
30
+ function ensureTrailingSlash(value) {
31
+ return value.endsWith('/') ? value : `${value}/`;
32
+ }
33
+ function sendJson(response, status, payload) {
34
+ response.statusCode = status;
35
+ response.setHeader('Content-Type', 'application/json');
36
+ response.end(JSON.stringify(payload));
37
+ }
38
+ //# sourceMappingURL=LinxCapabilitiesHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LinxCapabilitiesHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/LinxCapabilitiesHandler.ts"],"names":[],"mappings":";;;;;AAQA,wEAQC;AAhBD,sDAAyB;AACzB,0DAA6B;AAG7B,2CAA6C;AAE7C,MAAM,8BAA8B,GAAG,0BAA0B,CAAC;AAElE,SAAgB,8BAA8B,CAAC,MAAiB;IAC9D,MAAM,CAAC,GAAG,CAAC,wBAAwB,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;QAChE,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;YACtB,QAAQ,EAAE,8BAA8B;YACxC,OAAO,EAAE,mBAAmB,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,wBAAwB,CAAC;YAClF,OAAO,EAAE,UAAU,EAAE;SACtB,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,mBAAI,CAAC,IAAI,CAAC,sBAAY,EAAE,cAAc,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAyB,CAAC;QACzF,OAAO,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC;AACnD,CAAC;AAED,SAAS,QAAQ,CAAC,QAAwB,EAAE,MAAc,EAAE,OAAgB;IAC1E,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACxC,CAAC","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ServerResponse } from 'node:http';\nimport type { ApiServer } from '../ApiServer';\nimport { PACKAGE_ROOT } from '../../runtime';\n\nconst LINX_LOCAL_ONBOARDING_CONTRACT = 'linx-local-onboarding/v1';\n\nexport function registerLinxCapabilitiesRoutes(server: ApiServer): void {\n server.get('/api/linx/capabilities', async (_request, response) => {\n sendJson(response, 200, {\n contract: LINX_LOCAL_ONBOARDING_CONTRACT,\n baseUrl: ensureTrailingSlash(process.env.CSS_BASE_URL || 'http://localhost:3000/'),\n version: getVersion(),\n });\n }, { public: true });\n}\n\nfunction getVersion(): string {\n try {\n const packageJsonPath = path.join(PACKAGE_ROOT, 'package.json');\n const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { version?: string };\n return pkg.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nfunction ensureTrailingSlash(value: string): string {\n return value.endsWith('/') ? value : `${value}/`;\n}\n\nfunction sendJson(response: ServerResponse, status: number, payload: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(payload));\n}\n"]}
@@ -14,8 +14,12 @@
14
14
  */
15
15
  import type { ApiServer } from '../ApiServer';
16
16
  import type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';
17
+ import type { DdnsRepository } from '../../identity/drizzle/DdnsRepository';
18
+ import type { TunnelProvider } from '../../tunnel/TunnelProvider';
17
19
  export interface ProvisionHandlerOptions {
18
20
  repository: EdgeNodeRepository;
21
+ ddnsRepo?: DdnsRepository;
22
+ tunnelProvider?: TunnelProvider;
19
23
  /** Cloud baseUrl,用于派生 provisionCode 签名密钥 */
20
24
  baseUrl: string;
21
25
  /** 节点域名根域名,如 "undefineds.site" */
@@ -63,28 +63,29 @@ function registerProvisionRoutes(server, options) {
63
63
  nodeId: body.nodeId,
64
64
  serviceToken: body.serviceToken,
65
65
  });
66
- // 预分配子域名前缀(不创建 DNS 记录,延迟到心跳健康检查通过后)
67
- // DB 只存前缀,完整 FQDN 由 DnsCoordinator 的 rootDomain 拼接
68
- // 用 nodeId sanitize 后做前缀(去掉非 DNS 字符,截断到 63 字符)
69
66
  const subdomainPrefix = baseStorageDomain
70
67
  ? result.nodeId.replace(/[^a-z0-9-]/gi, '').toLowerCase().slice(0, 63) || result.nodeId.split('-')[0]
71
68
  : undefined;
72
69
  const spDomain = subdomainPrefix
73
70
  ? `${subdomainPrefix}.${baseStorageDomain}`
74
71
  : undefined;
75
- // 节点注册本身不应依赖后续 connectivity 元数据写入是否成功。
76
- // 这里的 mode/subdomain 只是辅助信息,失败时记录告警但不阻断注册。
72
+ const tunnelState = await ensureManagedTunnelState({
73
+ repository,
74
+ nodeId: result.nodeId,
75
+ subdomainPrefix,
76
+ publicUrl: body.publicUrl,
77
+ localPort: body.localPort,
78
+ ipv4: body.ipv4,
79
+ ddnsRepo: options.ddnsRepo,
80
+ tunnelProvider: options.tunnelProvider,
81
+ baseStorageDomain,
82
+ });
77
83
  if (body.ipv4 || subdomainPrefix) {
78
- try {
79
- await repository.updateNodeMode(result.nodeId, {
80
- accessMode: 'direct',
81
- ipv4: body.ipv4,
82
- subdomain: subdomainPrefix,
83
- });
84
- }
85
- catch (error) {
86
- logger.warn(`Registered SP node ${result.nodeId} but failed to persist connectivity metadata: ${error}`);
87
- }
84
+ await repository.updateNodeMode(result.nodeId, {
85
+ accessMode: tunnelState?.mode === 'tunnel' ? 'proxy' : 'direct',
86
+ ipv4: body.ipv4,
87
+ subdomain: subdomainPrefix,
88
+ });
88
89
  }
89
90
  // 生成自包含 provisionCode(编码了 SP 信息,CSS 解码后直接回调 SP)
90
91
  const provisionCode = codec.encode({
@@ -104,6 +105,15 @@ function registerProvisionRoutes(server, options) {
104
105
  if (spDomain) {
105
106
  responseBody.spDomain = spDomain;
106
107
  }
108
+ if (tunnelState?.tunnelConfig?.tunnelToken) {
109
+ responseBody.tunnelToken = tunnelState.tunnelConfig.tunnelToken;
110
+ }
111
+ if (tunnelState?.tunnelConfig?.provider) {
112
+ responseBody.tunnelProvider = tunnelState.tunnelConfig.provider;
113
+ }
114
+ if (tunnelState?.tunnelConfig?.endpoint) {
115
+ responseBody.tunnelEndpoint = tunnelState.tunnelConfig.endpoint;
116
+ }
107
117
  sendJson(response, 201, responseBody);
108
118
  }
109
119
  catch (error) {
@@ -113,6 +123,84 @@ function registerProvisionRoutes(server, options) {
113
123
  }, { public: true });
114
124
  logger.info('Provision routes registered');
115
125
  }
126
+ async function ensureManagedTunnelState(options) {
127
+ const { repository, ddnsRepo, tunnelProvider, nodeId, subdomainPrefix, baseStorageDomain, publicUrl, localPort, ipv4, } = options;
128
+ if (!subdomainPrefix || !baseStorageDomain) {
129
+ return undefined;
130
+ }
131
+ const mode = ipv4 ? 'direct' : 'tunnel';
132
+ if (ddnsRepo) {
133
+ const existing = await ddnsRepo.getRecord(subdomainPrefix);
134
+ if (!existing) {
135
+ await ddnsRepo.allocateSubdomain({
136
+ subdomain: subdomainPrefix,
137
+ domain: baseStorageDomain,
138
+ nodeId,
139
+ ipAddress: ipv4,
140
+ });
141
+ }
142
+ }
143
+ if (mode === 'direct' || !tunnelProvider || !localPort || localPort <= 0) {
144
+ return { mode };
145
+ }
146
+ const metadataRecord = await repository.getNodeMetadata(nodeId);
147
+ const metadata = metadataRecord?.metadata;
148
+ const existingTunnel = readManagedTunnelConfig(metadata);
149
+ if (existingTunnel && existingTunnel.subdomain === subdomainPrefix && existingTunnel.localPort === localPort) {
150
+ return {
151
+ mode,
152
+ tunnelConfig: existingTunnel.config,
153
+ };
154
+ }
155
+ const tunnelConfig = await tunnelProvider.setup({
156
+ subdomain: subdomainPrefix,
157
+ localPort,
158
+ });
159
+ await repository.mergeNodeMetadata(nodeId, {
160
+ managedTunnel: {
161
+ provider: tunnelConfig.provider,
162
+ tunnelId: tunnelConfig.tunnelId,
163
+ tunnelToken: tunnelConfig.tunnelToken,
164
+ endpoint: tunnelConfig.endpoint,
165
+ subdomain: subdomainPrefix,
166
+ localPort,
167
+ configuredAt: new Date().toISOString(),
168
+ },
169
+ publicAddress: tunnelConfig.endpoint || publicUrl,
170
+ });
171
+ return {
172
+ mode,
173
+ tunnelConfig,
174
+ };
175
+ }
176
+ function readManagedTunnelConfig(metadata) {
177
+ const raw = metadata?.managedTunnel;
178
+ if (!raw || typeof raw !== 'object') {
179
+ return undefined;
180
+ }
181
+ const value = raw;
182
+ const provider = value.provider;
183
+ const endpoint = value.endpoint;
184
+ const tunnelToken = value.tunnelToken;
185
+ const tunnelId = value.tunnelId;
186
+ const subdomain = typeof value.subdomain === 'string' ? value.subdomain : undefined;
187
+ const localPort = typeof value.localPort === 'number' ? value.localPort : undefined;
188
+ if ((provider !== 'cloudflare' && provider !== 'frp' && provider !== 'sakura-frp')
189
+ || typeof endpoint !== 'string') {
190
+ return undefined;
191
+ }
192
+ return {
193
+ subdomain,
194
+ localPort,
195
+ config: {
196
+ provider,
197
+ subdomain: subdomain ?? 'local',
198
+ endpoint,
199
+ tunnelId: typeof tunnelId === 'string' ? tunnelId : undefined,
200
+ tunnelToken: typeof tunnelToken === 'string' ? tunnelToken : undefined,
201
+ },
202
+ };
203
+ }
116
204
  function registerProvisionStatusRoute(server, options) {
117
205
  const logger = (0, global_logger_factory_1.getLoggerFor)('ProvisionStatusHandler');
118
206
  server.get('/provision/status', async (_request, response) => {
@@ -1 +1 @@
1
- {"version":3,"file":"ProvisionHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ProvisionHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provision Handler\n *\n * Cloud 端的 SP 注册 API\n *\n * POST /provision/nodes - SP 注册(公开,无需认证)\n * 返回 nodeId、nodeToken、serviceToken、provisionCode(自包含 JWT)\n *\n * provisionCode 是自包含 token,编码了 SP 的 publicUrl 和 serviceToken。\n * CSS 侧的 ProvisionPodCreator 解码后直接回调 SP,不需要查数据库。\n *\n * GET /provision/status - Local 端 SP 状态查询(公开)\n * 返回 SP 配置状态,供 Linx 查询\n */\n\nimport type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport { ProvisionCodeCodec } from '../../provision/ProvisionCodeCodec';\n\nexport interface ProvisionHandlerOptions {\n repository: EdgeNodeRepository;\n /** Cloud baseUrl,用于派生 provisionCode 签名密钥 */\n baseUrl: string;\n /** 节点域名根域名,如 \"undefineds.site\" */\n baseStorageDomain?: string;\n /** provisionCode 有效期(秒),默认 24 小时 */\n provisionCodeTtl?: number;\n}\n\n/** 默认 24 小时 */\nconst DEFAULT_TTL = 24 * 60 * 60;\n\nexport function registerProvisionRoutes(\n server: ApiServer,\n options: ProvisionHandlerOptions,\n): void {\n const logger = getLoggerFor('ProvisionHandler');\n const { repository, baseUrl, baseStorageDomain } = options;\n const ttl = options.provisionCodeTtl ?? DEFAULT_TTL;\n const codec = new ProvisionCodeCodec(baseUrl);\n\n /**\n * POST /provision/nodes\n *\n * SP 注册端点(公开,SP 启动时调用,此时用户可能还没有 Cloud 账号)\n *\n * Request:\n * { publicUrl: string, nodeId?: string, displayName?: string, ipv4?: string, serviceToken?: string }\n *\n * Response 201:\n * { nodeId, nodeToken, serviceToken, provisionCode, spDomain? }\n */\n server.post('/provision/nodes', async (request, response) => {\n let body: { publicUrl?: string; nodeId?: string; displayName?: string; ipv4?: string; serviceToken?: string };\n try {\n body = await readJsonBody(request) as any ?? {};\n } catch {\n sendJson(response, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n if (!body.publicUrl) {\n sendJson(response, 400, { error: 'publicUrl is required' });\n return;\n }\n\n try {\n new URL(body.publicUrl);\n } catch {\n sendJson(response, 400, { error: 'Invalid publicUrl format' });\n return;\n }\n\n try {\n const result = await repository.registerSpNode({\n publicUrl: body.publicUrl,\n displayName: body.displayName,\n nodeId: body.nodeId,\n serviceToken: body.serviceToken,\n });\n\n // 预分配子域名前缀(不创建 DNS 记录,延迟到心跳健康检查通过后)\n // DB 只存前缀,完整 FQDN 由 DnsCoordinator 的 rootDomain 拼接\n // 用 nodeId sanitize 后做前缀(去掉非 DNS 字符,截断到 63 字符)\n const subdomainPrefix = baseStorageDomain\n ? result.nodeId.replace(/[^a-z0-9-]/gi, '').toLowerCase().slice(0, 63) || result.nodeId.split('-')[0]\n : undefined;\n const spDomain = subdomainPrefix\n ? `${subdomainPrefix}.${baseStorageDomain}`\n : undefined;\n\n // 节点注册本身不应依赖后续 connectivity 元数据写入是否成功。\n // 这里的 mode/subdomain 只是辅助信息,失败时记录告警但不阻断注册。\n if (body.ipv4 || subdomainPrefix) {\n try {\n await repository.updateNodeMode(result.nodeId, {\n accessMode: 'direct',\n ipv4: body.ipv4,\n subdomain: subdomainPrefix,\n });\n } catch (error) {\n logger.warn(`Registered SP node ${result.nodeId} but failed to persist connectivity metadata: ${error}`);\n }\n }\n\n // 生成自包含 provisionCode(编码了 SP 信息,CSS 解码后直接回调 SP)\n const provisionCode = codec.encode({\n spUrl: body.publicUrl,\n serviceToken: result.serviceToken,\n nodeId: result.nodeId,\n spDomain,\n exp: Math.floor(Date.now() / 1000) + ttl,\n });\n\n logger.info(`Registered SP node ${result.nodeId} at ${body.publicUrl}${spDomain ? `, spDomain: ${spDomain}` : ''}`);\n\n const responseBody: Record<string, unknown> = {\n nodeId: result.nodeId,\n nodeToken: result.nodeToken,\n serviceToken: result.serviceToken,\n provisionCode,\n };\n if (spDomain) {\n responseBody.spDomain = spDomain;\n }\n\n sendJson(response, 201, responseBody);\n } catch (error) {\n logger.error(`Failed to register SP node: ${error}`);\n sendJson(response, 500, { error: 'Failed to register SP node' });\n }\n }, { public: true });\n\n logger.info('Provision routes registered');\n}\n\n/**\n * Local 端 SP 状态查询路由\n */\nexport interface ProvisionStatusOptions {\n /** Cloud API 端点 */\n cloudUrl?: string;\n /** 节点 ID */\n nodeId?: string;\n /** SP 子域名 */\n spDomain?: string;\n /** Cloud baseUrl,用于拼 provisionUrl */\n cloudBaseUrl?: string;\n /** provisionCode(可选,由环境变量传入) */\n provisionCode?: string;\n}\n\nexport function registerProvisionStatusRoute(\n server: ApiServer,\n options: ProvisionStatusOptions,\n): void {\n const logger = getLoggerFor('ProvisionStatusHandler');\n\n server.get('/provision/status', async (_request, response) => {\n const registered = Boolean(options.nodeId && options.cloudUrl);\n\n const body: Record<string, unknown> = {\n registered,\n };\n\n if (registered) {\n body.cloudUrl = options.cloudUrl;\n body.nodeId = options.nodeId;\n if (options.spDomain) {\n body.spDomain = options.spDomain;\n }\n if (options.cloudBaseUrl) {\n const provisionUrl = options.provisionCode\n ? `${options.cloudBaseUrl.replace(/\\/$/, '')}/.account/?provisionCode=${encodeURIComponent(options.provisionCode)}`\n : `${options.cloudBaseUrl.replace(/\\/$/, '')}/.account/`;\n body.provisionUrl = provisionUrl;\n }\n }\n\n sendJson(response, 200, body);\n }, { public: true });\n\n logger.info('Provision status route registered');\n}\n\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch (error) {\n reject(error);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
1
+ {"version":3,"file":"ProvisionHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ProvisionHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provision Handler\n *\n * Cloud 端的 SP 注册 API\n *\n * POST /provision/nodes - SP 注册(公开,无需认证)\n * 返回 nodeId、nodeToken、serviceToken、provisionCode(自包含 JWT)\n *\n * provisionCode 是自包含 token,编码了 SP 的 publicUrl 和 serviceToken。\n * CSS 侧的 ProvisionPodCreator 解码后直接回调 SP,不需要查数据库。\n *\n * GET /provision/status - Local 端 SP 状态查询(公开)\n * 返回 SP 配置状态,供 Linx 查询\n */\n\nimport type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { DdnsRepository } from '../../identity/drizzle/DdnsRepository';\nimport type { TunnelProvider, TunnelConfig } from '../../tunnel/TunnelProvider';\nimport { ProvisionCodeCodec } from '../../provision/ProvisionCodeCodec';\n\nexport interface ProvisionHandlerOptions {\n repository: EdgeNodeRepository;\n ddnsRepo?: DdnsRepository;\n tunnelProvider?: TunnelProvider;\n /** Cloud baseUrl,用于派生 provisionCode 签名密钥 */\n baseUrl: string;\n /** 节点域名根域名,如 \"undefineds.site\" */\n baseStorageDomain?: string;\n /** provisionCode 有效期(秒),默认 24 小时 */\n provisionCodeTtl?: number;\n}\n\n/** 默认 24 小时 */\nconst DEFAULT_TTL = 24 * 60 * 60;\n\nexport function registerProvisionRoutes(\n server: ApiServer,\n options: ProvisionHandlerOptions,\n): void {\n const logger = getLoggerFor('ProvisionHandler');\n const { repository, baseUrl, baseStorageDomain } = options;\n const ttl = options.provisionCodeTtl ?? DEFAULT_TTL;\n const codec = new ProvisionCodeCodec(baseUrl);\n\n /**\n * POST /provision/nodes\n *\n * SP 注册端点(公开,SP 启动时调用,此时用户可能还没有 Cloud 账号)\n *\n * Request:\n * { publicUrl: string, nodeId?: string, displayName?: string, ipv4?: string, serviceToken?: string }\n *\n * Response 201:\n * { nodeId, nodeToken, serviceToken, provisionCode, spDomain? }\n */\n server.post('/provision/nodes', async (request, response) => {\n let body: {\n publicUrl?: string;\n nodeId?: string;\n displayName?: string;\n ipv4?: string;\n serviceToken?: string;\n localPort?: number;\n };\n try {\n body = await readJsonBody(request) as any ?? {};\n } catch {\n sendJson(response, 400, { error: 'Invalid JSON body' });\n return;\n }\n\n if (!body.publicUrl) {\n sendJson(response, 400, { error: 'publicUrl is required' });\n return;\n }\n\n try {\n new URL(body.publicUrl);\n } catch {\n sendJson(response, 400, { error: 'Invalid publicUrl format' });\n return;\n }\n\n try {\n const result = await repository.registerSpNode({\n publicUrl: body.publicUrl,\n displayName: body.displayName,\n nodeId: body.nodeId,\n serviceToken: body.serviceToken,\n });\n\n const subdomainPrefix = baseStorageDomain\n ? result.nodeId.replace(/[^a-z0-9-]/gi, '').toLowerCase().slice(0, 63) || result.nodeId.split('-')[0]\n : undefined;\n const spDomain = subdomainPrefix\n ? `${subdomainPrefix}.${baseStorageDomain}`\n : undefined;\n const tunnelState = await ensureManagedTunnelState({\n repository,\n nodeId: result.nodeId,\n subdomainPrefix,\n publicUrl: body.publicUrl,\n localPort: body.localPort,\n ipv4: body.ipv4,\n ddnsRepo: options.ddnsRepo,\n tunnelProvider: options.tunnelProvider,\n baseStorageDomain,\n });\n\n if (body.ipv4 || subdomainPrefix) {\n await repository.updateNodeMode(result.nodeId, {\n accessMode: tunnelState?.mode === 'tunnel' ? 'proxy' : 'direct',\n ipv4: body.ipv4,\n subdomain: subdomainPrefix,\n });\n }\n\n // 生成自包含 provisionCode(编码了 SP 信息,CSS 解码后直接回调 SP)\n const provisionCode = codec.encode({\n spUrl: body.publicUrl,\n serviceToken: result.serviceToken,\n nodeId: result.nodeId,\n spDomain,\n exp: Math.floor(Date.now() / 1000) + ttl,\n });\n\n logger.info(`Registered SP node ${result.nodeId} at ${body.publicUrl}${spDomain ? `, spDomain: ${spDomain}` : ''}`);\n\n const responseBody: Record<string, unknown> = {\n nodeId: result.nodeId,\n nodeToken: result.nodeToken,\n serviceToken: result.serviceToken,\n provisionCode,\n };\n if (spDomain) {\n responseBody.spDomain = spDomain;\n }\n if (tunnelState?.tunnelConfig?.tunnelToken) {\n responseBody.tunnelToken = tunnelState.tunnelConfig.tunnelToken;\n }\n if (tunnelState?.tunnelConfig?.provider) {\n responseBody.tunnelProvider = tunnelState.tunnelConfig.provider;\n }\n if (tunnelState?.tunnelConfig?.endpoint) {\n responseBody.tunnelEndpoint = tunnelState.tunnelConfig.endpoint;\n }\n\n sendJson(response, 201, responseBody);\n } catch (error) {\n logger.error(`Failed to register SP node: ${error}`);\n sendJson(response, 500, { error: 'Failed to register SP node' });\n }\n }, { public: true });\n\n logger.info('Provision routes registered');\n}\n\ninterface ManagedTunnelState {\n mode: 'direct' | 'tunnel';\n tunnelConfig?: TunnelConfig;\n}\n\nasync function ensureManagedTunnelState(options: {\n repository: EdgeNodeRepository;\n ddnsRepo?: DdnsRepository;\n tunnelProvider?: TunnelProvider;\n nodeId: string;\n subdomainPrefix?: string;\n baseStorageDomain?: string;\n publicUrl: string;\n localPort?: number;\n ipv4?: string;\n}): Promise<ManagedTunnelState | undefined> {\n const {\n repository,\n ddnsRepo,\n tunnelProvider,\n nodeId,\n subdomainPrefix,\n baseStorageDomain,\n publicUrl,\n localPort,\n ipv4,\n } = options;\n\n if (!subdomainPrefix || !baseStorageDomain) {\n return undefined;\n }\n\n const mode: 'direct' | 'tunnel' = ipv4 ? 'direct' : 'tunnel';\n\n if (ddnsRepo) {\n const existing = await ddnsRepo.getRecord(subdomainPrefix);\n if (!existing) {\n await ddnsRepo.allocateSubdomain({\n subdomain: subdomainPrefix,\n domain: baseStorageDomain,\n nodeId,\n ipAddress: ipv4,\n });\n }\n }\n\n if (mode === 'direct' || !tunnelProvider || !localPort || localPort <= 0) {\n return { mode };\n }\n\n const metadataRecord = await repository.getNodeMetadata(nodeId);\n const metadata = metadataRecord?.metadata as Record<string, unknown> | null;\n const existingTunnel = readManagedTunnelConfig(metadata);\n if (existingTunnel && existingTunnel.subdomain === subdomainPrefix && existingTunnel.localPort === localPort) {\n return {\n mode,\n tunnelConfig: existingTunnel.config,\n };\n }\n\n const tunnelConfig = await tunnelProvider.setup({\n subdomain: subdomainPrefix,\n localPort,\n });\n\n await repository.mergeNodeMetadata(nodeId, {\n managedTunnel: {\n provider: tunnelConfig.provider,\n tunnelId: tunnelConfig.tunnelId,\n tunnelToken: tunnelConfig.tunnelToken,\n endpoint: tunnelConfig.endpoint,\n subdomain: subdomainPrefix,\n localPort,\n configuredAt: new Date().toISOString(),\n },\n publicAddress: tunnelConfig.endpoint || publicUrl,\n });\n\n return {\n mode,\n tunnelConfig,\n };\n}\n\nfunction readManagedTunnelConfig(metadata: Record<string, unknown> | null): { subdomain?: string; localPort?: number; config: TunnelConfig } | undefined {\n const raw = metadata?.managedTunnel;\n if (!raw || typeof raw !== 'object') {\n return undefined;\n }\n\n const value = raw as Record<string, unknown>;\n const provider = value.provider;\n const endpoint = value.endpoint;\n const tunnelToken = value.tunnelToken;\n const tunnelId = value.tunnelId;\n const subdomain = typeof value.subdomain === 'string' ? value.subdomain : undefined;\n const localPort = typeof value.localPort === 'number' ? value.localPort : undefined;\n\n if (\n (provider !== 'cloudflare' && provider !== 'frp' && provider !== 'sakura-frp')\n || typeof endpoint !== 'string'\n ) {\n return undefined;\n }\n\n return {\n subdomain,\n localPort,\n config: {\n provider,\n subdomain: subdomain ?? 'local',\n endpoint,\n tunnelId: typeof tunnelId === 'string' ? tunnelId : undefined,\n tunnelToken: typeof tunnelToken === 'string' ? tunnelToken : undefined,\n },\n };\n}\n\n/**\n * Local 端 SP 状态查询路由\n */\nexport interface ProvisionStatusOptions {\n /** Cloud API 端点 */\n cloudUrl?: string;\n /** 节点 ID */\n nodeId?: string;\n /** SP 子域名 */\n spDomain?: string;\n /** Cloud baseUrl,用于拼 provisionUrl */\n cloudBaseUrl?: string;\n /** provisionCode(可选,由环境变量传入) */\n provisionCode?: string;\n}\n\nexport function registerProvisionStatusRoute(\n server: ApiServer,\n options: ProvisionStatusOptions,\n): void {\n const logger = getLoggerFor('ProvisionStatusHandler');\n\n server.get('/provision/status', async (_request, response) => {\n const registered = Boolean(options.nodeId && options.cloudUrl);\n\n const body: Record<string, unknown> = {\n registered,\n };\n\n if (registered) {\n body.cloudUrl = options.cloudUrl;\n body.nodeId = options.nodeId;\n if (options.spDomain) {\n body.spDomain = options.spDomain;\n }\n if (options.cloudBaseUrl) {\n const provisionUrl = options.provisionCode\n ? `${options.cloudBaseUrl.replace(/\\/$/, '')}/.account/?provisionCode=${encodeURIComponent(options.provisionCode)}`\n : `${options.cloudBaseUrl.replace(/\\/$/, '')}/.account/`;\n body.provisionUrl = provisionUrl;\n }\n }\n\n sendJson(response, 200, body);\n }, { public: true });\n\n logger.info('Provision status route registered');\n}\n\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch (error) {\n reject(error);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
@@ -14,6 +14,7 @@ export interface DdnsManagerOptions {
14
14
  client: SubdomainClient;
15
15
  detector: EdgeNodeCapabilityDetector;
16
16
  subdomain: string;
17
+ localPort?: number;
17
18
  intervalMs?: number;
18
19
  autoAllocate?: boolean;
19
20
  tunnelProvider?: 'cloudflare' | 'sakura_frp' | 'none';
@@ -23,6 +24,7 @@ export declare class DdnsManager {
23
24
  private readonly client;
24
25
  private readonly detector;
25
26
  private readonly subdomain;
27
+ private readonly localPort?;
26
28
  private readonly intervalMs;
27
29
  private readonly autoAllocate;
28
30
  private readonly tunnelProvider;
@@ -20,6 +20,7 @@ class DdnsManager {
20
20
  this.client = options.client;
21
21
  this.detector = options.detector;
22
22
  this.subdomain = options.subdomain;
23
+ this.localPort = options.localPort;
23
24
  this.intervalMs = options.intervalMs ?? 60_000;
24
25
  this.autoAllocate = options.autoAllocate ?? true;
25
26
  this.tunnelProvider = options.tunnelProvider ?? 'none';
@@ -98,6 +99,7 @@ class DdnsManager {
98
99
  ipv6Address: this.lastMode === 'direct' ? ipv6 : undefined,
99
100
  mode: this.lastMode === 'tunnel' ? 'tunnel' : 'direct',
100
101
  tunnelProvider: this.tunnelProvider,
102
+ localPort: this.localPort,
101
103
  });
102
104
  if (result.success) {
103
105
  this.logger.info(`DDNS allocated: ${result.fqdn}`);
@@ -118,6 +120,7 @@ class DdnsManager {
118
120
  ipv6Address: this.lastMode === 'direct' ? ipv6 : undefined,
119
121
  mode: this.lastMode === 'tunnel' ? 'tunnel' : 'direct',
120
122
  tunnelProvider: this.tunnelProvider,
123
+ localPort: this.localPort,
121
124
  });
122
125
  if (result.success) {
123
126
  this.fqdn = result.fqdn;
@@ -1 +1 @@
1
- {"version":3,"file":"DdnsManager.js","sourceRoot":"","sources":["../../src/edge/DdnsManager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS Manager for Local Managed Mode\n *\n * Responsibilities (Local managed mode):\n * - Allocate a managed domain on Cloud (subdomain -> fqdn)\n * - Keep the record updated when network changes\n *\n * Note: if the machine has no public IP, we assume tunnel mode and let Cloud\n * point the managed domain to the configured tunnel provider.\n */\n\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { SubdomainClient } from '../subdomain/SubdomainClient';\nimport type { EdgeNodeCapabilityDetector } from './EdgeNodeCapabilityDetector';\n\nexport interface DdnsManagerOptions {\n client: SubdomainClient;\n detector: EdgeNodeCapabilityDetector;\n subdomain: string;\n intervalMs?: number;\n autoAllocate?: boolean;\n\n // Local tunnel provider preference (best-effort hint for Cloud).\n tunnelProvider?: 'cloudflare' | 'sakura_frp' | 'none';\n}\n\nexport class DdnsManager {\n private readonly logger = getLoggerFor(this);\n private readonly client: SubdomainClient;\n private readonly detector: EdgeNodeCapabilityDetector;\n private readonly subdomain: string;\n private readonly intervalMs: number;\n private readonly autoAllocate: boolean;\n private readonly tunnelProvider: 'cloudflare' | 'sakura_frp' | 'none';\n\n private interval?: NodeJS.Timeout;\n private allocated = false;\n private fqdn?: string;\n private lastIpv4?: string;\n private lastIpv6?: string;\n private lastMode: 'direct' | 'tunnel' | 'unknown' = 'unknown';\n\n public constructor(options: DdnsManagerOptions) {\n this.client = options.client;\n this.detector = options.detector;\n this.subdomain = options.subdomain;\n this.intervalMs = options.intervalMs ?? 60_000;\n this.autoAllocate = options.autoAllocate ?? true;\n this.tunnelProvider = options.tunnelProvider ?? 'none';\n }\n\n public async start(): Promise<void> {\n if (this.interval) {\n return;\n }\n\n this.logger.info(`Starting DDNS manager for subdomain: ${this.subdomain}`);\n\n await this.runCycle();\n this.interval = setInterval(() => void this.runCycle(), this.intervalMs);\n }\n\n public stop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.logger.info('DDNS manager stopped');\n }\n\n public getFqdn(): string | undefined {\n return this.allocated ? this.fqdn : undefined;\n }\n\n public isAllocated(): boolean {\n return this.allocated;\n }\n\n public getStatus(): {\n allocated: boolean;\n fqdn?: string;\n ipv4?: string;\n ipv6?: string;\n mode: 'direct' | 'tunnel' | 'unknown';\n tunnelProvider: string;\n } {\n return {\n allocated: this.allocated,\n fqdn: this.fqdn,\n ipv4: this.lastIpv4,\n ipv6: this.lastIpv6,\n mode: this.lastMode,\n tunnelProvider: this.tunnelProvider,\n };\n }\n\n public async runOnce(): Promise<void> {\n await this.runCycle();\n }\n\n private async runCycle(): Promise<void> {\n try {\n const netInfo = await this.detector.detectNetworkAddresses();\n\n const ipv4Public = netInfo.ipv4Public;\n const ipv6Public = netInfo.ipv6Public;\n const hasPublicIp = Boolean(ipv4Public || ipv6Public);\n\n this.lastMode = hasPublicIp ? 'direct' : 'tunnel';\n\n const ipv4 = ipv4Public ?? netInfo.ipv4;\n const ipv6 = ipv6Public ?? netInfo.ipv6;\n\n if (!this.allocated && this.autoAllocate) {\n await this.allocateSubdomain(ipv4, ipv6);\n }\n\n if (this.allocated) {\n const ipChanged = ipv4 !== this.lastIpv4 || ipv6 !== this.lastIpv6;\n if (ipChanged || this.lastMode === 'tunnel') {\n await this.updateDdns(ipv4, ipv6);\n }\n }\n } catch (error: unknown) {\n this.logger.error(`DDNS cycle failed: ${(error as Error).message}`);\n }\n }\n\n private async allocateSubdomain(ipv4?: string, ipv6?: string): Promise<void> {\n const existing = await this.client.getDdns(this.subdomain);\n if (existing) {\n this.logger.info(`DDNS subdomain already allocated: ${existing.fqdn}`);\n this.allocated = true;\n this.fqdn = existing.fqdn;\n this.lastIpv4 = existing.ipAddress;\n this.lastIpv6 = existing.ipv6Address;\n return;\n }\n\n this.logger.info(`Allocating DDNS subdomain: ${this.subdomain}`);\n\n const result = await this.client.allocateDdns({\n subdomain: this.subdomain,\n ipAddress: this.lastMode === 'direct' ? ipv4 : undefined,\n ipv6Address: this.lastMode === 'direct' ? ipv6 : undefined,\n mode: this.lastMode === 'tunnel' ? 'tunnel' : 'direct',\n tunnelProvider: this.tunnelProvider,\n });\n\n if (result.success) {\n this.logger.info(`DDNS allocated: ${result.fqdn}`);\n this.allocated = true;\n this.fqdn = result.fqdn;\n this.lastIpv4 = this.lastMode === 'direct' ? ipv4 : undefined;\n this.lastIpv6 = this.lastMode === 'direct' ? ipv6 : undefined;\n }\n }\n\n private async updateDdns(ipv4?: string, ipv6?: string): Promise<void> {\n if (this.lastMode === 'direct' && !ipv4 && !ipv6) {\n this.logger.debug('No IP address to update in direct mode');\n return;\n }\n\n\n this.logger.info(\n `Updating DDNS: ${this.subdomain} mode=${this.lastMode} ipv4=${ipv4 ?? 'none'} ipv6=${ipv6 ?? 'none'} tunnel=${this.tunnelProvider}`,\n );\n\n const result = await this.client.updateDdns(this.subdomain, {\n ipAddress: this.lastMode === 'direct' ? ipv4 : undefined,\n ipv6Address: this.lastMode === 'direct' ? ipv6 : undefined,\n mode: this.lastMode === 'tunnel' ? 'tunnel' : 'direct',\n tunnelProvider: this.tunnelProvider,\n });\n\n if (result.success) {\n this.fqdn = result.fqdn;\n this.lastIpv4 = this.lastMode === 'direct' ? ipv4 : undefined;\n this.lastIpv6 = this.lastMode === 'direct' ? ipv6 : undefined;\n this.logger.info(`DDNS updated: ${result.fqdn}`);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"DdnsManager.js","sourceRoot":"","sources":["../../src/edge/DdnsManager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS Manager for Local Managed Mode\n *\n * Responsibilities (Local managed mode):\n * - Allocate a managed domain on Cloud (subdomain -> fqdn)\n * - Keep the record updated when network changes\n *\n * Note: if the machine has no public IP, we assume tunnel mode and let Cloud\n * point the managed domain to the configured tunnel provider.\n */\n\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { SubdomainClient } from '../subdomain/SubdomainClient';\nimport type { EdgeNodeCapabilityDetector } from './EdgeNodeCapabilityDetector';\n\nexport interface DdnsManagerOptions {\n client: SubdomainClient;\n detector: EdgeNodeCapabilityDetector;\n subdomain: string;\n localPort?: number;\n intervalMs?: number;\n autoAllocate?: boolean;\n\n // Local tunnel provider preference (best-effort hint for Cloud).\n tunnelProvider?: 'cloudflare' | 'sakura_frp' | 'none';\n}\n\nexport class DdnsManager {\n private readonly logger = getLoggerFor(this);\n private readonly client: SubdomainClient;\n private readonly detector: EdgeNodeCapabilityDetector;\n private readonly subdomain: string;\n private readonly localPort?: number;\n private readonly intervalMs: number;\n private readonly autoAllocate: boolean;\n private readonly tunnelProvider: 'cloudflare' | 'sakura_frp' | 'none';\n\n private interval?: NodeJS.Timeout;\n private allocated = false;\n private fqdn?: string;\n private lastIpv4?: string;\n private lastIpv6?: string;\n private lastMode: 'direct' | 'tunnel' | 'unknown' = 'unknown';\n\n public constructor(options: DdnsManagerOptions) {\n this.client = options.client;\n this.detector = options.detector;\n this.subdomain = options.subdomain;\n this.localPort = options.localPort;\n this.intervalMs = options.intervalMs ?? 60_000;\n this.autoAllocate = options.autoAllocate ?? true;\n this.tunnelProvider = options.tunnelProvider ?? 'none';\n }\n\n public async start(): Promise<void> {\n if (this.interval) {\n return;\n }\n\n this.logger.info(`Starting DDNS manager for subdomain: ${this.subdomain}`);\n\n await this.runCycle();\n this.interval = setInterval(() => void this.runCycle(), this.intervalMs);\n }\n\n public stop(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n this.logger.info('DDNS manager stopped');\n }\n\n public getFqdn(): string | undefined {\n return this.allocated ? this.fqdn : undefined;\n }\n\n public isAllocated(): boolean {\n return this.allocated;\n }\n\n public getStatus(): {\n allocated: boolean;\n fqdn?: string;\n ipv4?: string;\n ipv6?: string;\n mode: 'direct' | 'tunnel' | 'unknown';\n tunnelProvider: string;\n } {\n return {\n allocated: this.allocated,\n fqdn: this.fqdn,\n ipv4: this.lastIpv4,\n ipv6: this.lastIpv6,\n mode: this.lastMode,\n tunnelProvider: this.tunnelProvider,\n };\n }\n\n public async runOnce(): Promise<void> {\n await this.runCycle();\n }\n\n private async runCycle(): Promise<void> {\n try {\n const netInfo = await this.detector.detectNetworkAddresses();\n\n const ipv4Public = netInfo.ipv4Public;\n const ipv6Public = netInfo.ipv6Public;\n const hasPublicIp = Boolean(ipv4Public || ipv6Public);\n\n this.lastMode = hasPublicIp ? 'direct' : 'tunnel';\n\n const ipv4 = ipv4Public ?? netInfo.ipv4;\n const ipv6 = ipv6Public ?? netInfo.ipv6;\n\n if (!this.allocated && this.autoAllocate) {\n await this.allocateSubdomain(ipv4, ipv6);\n }\n\n if (this.allocated) {\n const ipChanged = ipv4 !== this.lastIpv4 || ipv6 !== this.lastIpv6;\n if (ipChanged || this.lastMode === 'tunnel') {\n await this.updateDdns(ipv4, ipv6);\n }\n }\n } catch (error: unknown) {\n this.logger.error(`DDNS cycle failed: ${(error as Error).message}`);\n }\n }\n\n private async allocateSubdomain(ipv4?: string, ipv6?: string): Promise<void> {\n const existing = await this.client.getDdns(this.subdomain);\n if (existing) {\n this.logger.info(`DDNS subdomain already allocated: ${existing.fqdn}`);\n this.allocated = true;\n this.fqdn = existing.fqdn;\n this.lastIpv4 = existing.ipAddress;\n this.lastIpv6 = existing.ipv6Address;\n return;\n }\n\n this.logger.info(`Allocating DDNS subdomain: ${this.subdomain}`);\n\n const result = await this.client.allocateDdns({\n subdomain: this.subdomain,\n ipAddress: this.lastMode === 'direct' ? ipv4 : undefined,\n ipv6Address: this.lastMode === 'direct' ? ipv6 : undefined,\n mode: this.lastMode === 'tunnel' ? 'tunnel' : 'direct',\n tunnelProvider: this.tunnelProvider,\n localPort: this.localPort,\n });\n\n if (result.success) {\n this.logger.info(`DDNS allocated: ${result.fqdn}`);\n this.allocated = true;\n this.fqdn = result.fqdn;\n this.lastIpv4 = this.lastMode === 'direct' ? ipv4 : undefined;\n this.lastIpv6 = this.lastMode === 'direct' ? ipv6 : undefined;\n }\n }\n\n private async updateDdns(ipv4?: string, ipv6?: string): Promise<void> {\n if (this.lastMode === 'direct' && !ipv4 && !ipv6) {\n this.logger.debug('No IP address to update in direct mode');\n return;\n }\n\n\n this.logger.info(\n `Updating DDNS: ${this.subdomain} mode=${this.lastMode} ipv4=${ipv4 ?? 'none'} ipv6=${ipv6 ?? 'none'} tunnel=${this.tunnelProvider}`,\n );\n\n const result = await this.client.updateDdns(this.subdomain, {\n ipAddress: this.lastMode === 'direct' ? ipv4 : undefined,\n ipv6Address: this.lastMode === 'direct' ? ipv6 : undefined,\n mode: this.lastMode === 'tunnel' ? 'tunnel' : 'direct',\n tunnelProvider: this.tunnelProvider,\n localPort: this.localPort,\n });\n\n if (result.success) {\n this.fqdn = result.fqdn;\n this.lastIpv4 = this.lastMode === 'direct' ? ipv4 : undefined;\n this.lastIpv6 = this.lastMode === 'direct' ? ipv6 : undefined;\n this.logger.info(`DDNS updated: ${result.fqdn}`);\n }\n }\n}\n"]}
@@ -47,6 +47,9 @@ export interface DdnsAllocationResult {
47
47
  fqdn: string;
48
48
  ipAddress?: string;
49
49
  ipv6Address?: string;
50
+ tunnelProvider?: string;
51
+ tunnelToken?: string;
52
+ tunnelEndpoint?: string;
50
53
  createdAt: string;
51
54
  }
52
55
  export interface DdnsUpdateResult {
@@ -56,6 +59,9 @@ export interface DdnsUpdateResult {
56
59
  fqdn: string;
57
60
  ipAddress?: string;
58
61
  ipv6Address?: string;
62
+ tunnelProvider?: string;
63
+ tunnelToken?: string;
64
+ tunnelEndpoint?: string;
59
65
  updatedAt: string;
60
66
  }
61
67
  export interface DdnsRecordInfo {
@@ -67,6 +73,7 @@ export interface DdnsRecordInfo {
67
73
  recordType: string;
68
74
  status: string;
69
75
  ttl: number;
76
+ tunnelProvider?: string;
70
77
  createdAt: string;
71
78
  updatedAt: string;
72
79
  }
@@ -135,6 +142,7 @@ export declare class SubdomainClient {
135
142
  ipv6Address?: string;
136
143
  mode?: 'direct' | 'tunnel';
137
144
  tunnelProvider?: string;
145
+ localPort?: number;
138
146
  }): Promise<DdnsAllocationResult>;
139
147
  /**
140
148
  * 更新 DDNS 记录
@@ -144,6 +152,7 @@ export declare class SubdomainClient {
144
152
  ipv6Address?: string;
145
153
  mode?: 'direct' | 'tunnel';
146
154
  tunnelProvider?: string;
155
+ localPort?: number;
147
156
  }): Promise<DdnsUpdateResult>;
148
157
  /**
149
158
  * 获取 DDNS 记录
@@ -131,6 +131,7 @@ class SubdomainClient {
131
131
  ipv6Address: options.ipv6Address,
132
132
  mode: options.mode,
133
133
  tunnelProvider: options.tunnelProvider,
134
+ localPort: options.localPort,
134
135
  }),
135
136
  });
136
137
  return response;
@@ -147,6 +148,7 @@ class SubdomainClient {
147
148
  ipv6Address: options.ipv6Address,
148
149
  mode: options.mode,
149
150
  tunnelProvider: options.tunnelProvider,
151
+ localPort: options.localPort,
150
152
  }),
151
153
  });
152
154
  return response;
@@ -1 +1 @@
1
- {"version":3,"file":"SubdomainClient.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainClient.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SubdomainClient - Local 模式子域名客户端\n * \n * Local 模式不持有 DNS/Tunnel 密钥,通过调用 Cloud API 来管理子域名。\n * 使用 Node Token 认证(与 Edge Node 心跳相同的认证方式)。\n */\n\nimport { getLoggerFor } from 'global-logger-factory';\n\nexport interface SubdomainClientOptions {\n /** Cloud API 端点 (如 https://center.example.com/v1/subdomain) */\n cloudApiEndpoint: string;\n \n /** 节点 ID */\n nodeId: string;\n \n /** 节点 Token */\n nodeToken: string;\n \n /** 请求超时 (ms) */\n timeoutMs?: number;\n}\n\nexport interface SubdomainCheckResult {\n subdomain: string;\n available: boolean;\n reason?: string;\n}\n\nexport interface SubdomainRegistrationResult {\n success: boolean;\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n ipv4?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n message?: string;\n}\n\nexport interface SubdomainInfo {\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n ipv4?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n ownerId?: string;\n}\n\nexport interface DdnsAllocationResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n createdAt: string;\n}\n\nexport interface DdnsUpdateResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n updatedAt: string;\n}\n\nexport interface DdnsRecordInfo {\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: string;\n status: string;\n ttl: number;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * 子域名客户端 (Local 模式)\n * \n * 通过 HTTP 调用 Cloud 的子域名 API\n */\nexport class SubdomainClient {\n private readonly logger = getLoggerFor(this);\n private readonly cloudApiEndpoint: string;\n private readonly nodeId: string;\n private readonly nodeToken: string;\n private readonly timeoutMs: number;\n\n constructor(options: SubdomainClientOptions) {\n this.cloudApiEndpoint = options.cloudApiEndpoint.replace(/\\/$/, '');\n this.nodeId = options.nodeId;\n this.nodeToken = options.nodeToken;\n this.timeoutMs = options.timeoutMs ?? 30000;\n }\n\n /**\n * 检查子域名可用性\n */\n async checkAvailability(name: string): Promise<SubdomainCheckResult> {\n const url = `${this.cloudApiEndpoint}/check?name=${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainCheckResult;\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n localPort: number;\n ipv4?: string;\n }): Promise<SubdomainRegistrationResult> {\n const url = `${this.cloudApiEndpoint}/register`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n localPort: options.localPort,\n ipv4: options.ipv4,\n nodeId: this.nodeId,\n }),\n });\n return response as SubdomainRegistrationResult;\n }\n\n /**\n * 获取子域名信息\n */\n async getInfo(name: string): Promise<SubdomainInfo | null> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * 列出所有子域名\n */\n async list(): Promise<{ registrations: SubdomainInfo[]; total: number }> {\n const url = this.cloudApiEndpoint;\n const response = await this.fetch(url, { method: 'GET' });\n return response as { registrations: SubdomainInfo[]; total: number };\n }\n\n /**\n * 释放子域名\n */\n async release(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'DELETE' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/start`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/stop`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n // ============ DDNS API ============\n\n /**\n * 分配 DDNS 子域名\n */\n async allocateDdns(options: {\n subdomain: string;\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n }): Promise<DdnsAllocationResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/allocate`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n nodeId: this.nodeId,\n ipAddress: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n }),\n });\n return response as DdnsAllocationResult;\n }\n\n /**\n * 更新 DDNS 记录\n */\n async updateDdns(subdomain: string, options: {\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n }): Promise<DdnsUpdateResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n ip: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n }),\n });\n return response as DdnsUpdateResult;\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getDdns(subdomain: string): Promise<DdnsRecordInfo | null> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as DdnsRecordInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n // ============ Private Methods ============\n\n private async fetch(url: string, options: {\n method: string;\n body?: string;\n }): Promise<unknown> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n // 使用 undici 的 fetch,禁用代理\n const { fetch: undiciFetch, Agent } = await import('undici');\n const agent = new Agent({\n connect: { timeout: this.timeoutMs },\n });\n\n const response = await undiciFetch(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `XpodNode ${this.nodeId}:${this.nodeToken}`,\n },\n body: options.body,\n signal: controller.signal,\n dispatcher: agent,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = (data as any)?.error ?? 'Unknown error';\n throw new SubdomainClientError(error, response.status);\n }\n\n return data;\n } catch (error) {\n if (error instanceof SubdomainClientError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new SubdomainClientError('Request timeout', 408);\n }\n throw new SubdomainClientError(\n error instanceof Error ? error.message : 'Unknown error',\n 500,\n );\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\n/**\n * SubdomainClient 错误\n */\nexport class SubdomainClientError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n ) {\n super(message);\n this.name = 'SubdomainClientError';\n }\n}\n"]}
1
+ {"version":3,"file":"SubdomainClient.js","sourceRoot":"","sources":["../../src/subdomain/SubdomainClient.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SubdomainClient - Local 模式子域名客户端\n * \n * Local 模式不持有 DNS/Tunnel 密钥,通过调用 Cloud API 来管理子域名。\n * 使用 Node Token 认证(与 Edge Node 心跳相同的认证方式)。\n */\n\nimport { getLoggerFor } from 'global-logger-factory';\n\nexport interface SubdomainClientOptions {\n /** Cloud API 端点 (如 https://center.example.com/v1/subdomain) */\n cloudApiEndpoint: string;\n \n /** 节点 ID */\n nodeId: string;\n \n /** 节点 Token */\n nodeToken: string;\n \n /** 请求超时 (ms) */\n timeoutMs?: number;\n}\n\nexport interface SubdomainCheckResult {\n subdomain: string;\n available: boolean;\n reason?: string;\n}\n\nexport interface SubdomainRegistrationResult {\n success: boolean;\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n ipv4?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n message?: string;\n}\n\nexport interface SubdomainInfo {\n subdomain: string;\n fullDomain: string;\n mode: 'direct' | 'tunnel';\n ipv4?: string;\n tunnelProvider?: string;\n tunnelEndpoint?: string;\n registeredAt: string;\n ownerId?: string;\n}\n\nexport interface DdnsAllocationResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n tunnelProvider?: string;\n tunnelToken?: string;\n tunnelEndpoint?: string;\n createdAt: string;\n}\n\nexport interface DdnsUpdateResult {\n success: boolean;\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n tunnelProvider?: string;\n tunnelToken?: string;\n tunnelEndpoint?: string;\n updatedAt: string;\n}\n\nexport interface DdnsRecordInfo {\n subdomain: string;\n domain: string;\n fqdn: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: string;\n status: string;\n ttl: number;\n tunnelProvider?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * 子域名客户端 (Local 模式)\n * \n * 通过 HTTP 调用 Cloud 的子域名 API\n */\nexport class SubdomainClient {\n private readonly logger = getLoggerFor(this);\n private readonly cloudApiEndpoint: string;\n private readonly nodeId: string;\n private readonly nodeToken: string;\n private readonly timeoutMs: number;\n\n constructor(options: SubdomainClientOptions) {\n this.cloudApiEndpoint = options.cloudApiEndpoint.replace(/\\/$/, '');\n this.nodeId = options.nodeId;\n this.nodeToken = options.nodeToken;\n this.timeoutMs = options.timeoutMs ?? 30000;\n }\n\n /**\n * 检查子域名可用性\n */\n async checkAvailability(name: string): Promise<SubdomainCheckResult> {\n const url = `${this.cloudApiEndpoint}/check?name=${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainCheckResult;\n }\n\n /**\n * 注册子域名\n */\n async register(options: {\n subdomain: string;\n localPort: number;\n ipv4?: string;\n }): Promise<SubdomainRegistrationResult> {\n const url = `${this.cloudApiEndpoint}/register`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n localPort: options.localPort,\n ipv4: options.ipv4,\n nodeId: this.nodeId,\n }),\n });\n return response as SubdomainRegistrationResult;\n }\n\n /**\n * 获取子域名信息\n */\n async getInfo(name: string): Promise<SubdomainInfo | null> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as SubdomainInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * 列出所有子域名\n */\n async list(): Promise<{ registrations: SubdomainInfo[]; total: number }> {\n const url = this.cloudApiEndpoint;\n const response = await this.fetch(url, { method: 'GET' });\n return response as { registrations: SubdomainInfo[]; total: number };\n }\n\n /**\n * 释放子域名\n */\n async release(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}`;\n const response = await this.fetch(url, { method: 'DELETE' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 启动隧道\n */\n async startTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/start`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n /**\n * 停止隧道\n */\n async stopTunnel(name: string): Promise<{ success: boolean; message: string }> {\n const url = `${this.cloudApiEndpoint}/${encodeURIComponent(name)}/stop`;\n const response = await this.fetch(url, { method: 'POST' });\n return response as { success: boolean; message: string };\n }\n\n // ============ DDNS API ============\n\n /**\n * 分配 DDNS 子域名\n */\n async allocateDdns(options: {\n subdomain: string;\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n localPort?: number;\n }): Promise<DdnsAllocationResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/allocate`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n subdomain: options.subdomain,\n nodeId: this.nodeId,\n ipAddress: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n localPort: options.localPort,\n }),\n });\n return response as DdnsAllocationResult;\n }\n\n /**\n * 更新 DDNS 记录\n */\n async updateDdns(subdomain: string, options: {\n ipAddress?: string;\n ipv6Address?: string;\n mode?: 'direct' | 'tunnel';\n tunnelProvider?: string;\n localPort?: number;\n }): Promise<DdnsUpdateResult> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n const response = await this.fetch(url, {\n method: 'POST',\n body: JSON.stringify({\n ip: options.ipAddress,\n ipv6Address: options.ipv6Address,\n mode: options.mode,\n tunnelProvider: options.tunnelProvider,\n localPort: options.localPort,\n }),\n });\n return response as DdnsUpdateResult;\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getDdns(subdomain: string): Promise<DdnsRecordInfo | null> {\n const url = `${this.cloudApiEndpoint}/api/v1/ddns/${encodeURIComponent(subdomain)}`;\n try {\n const response = await this.fetch(url, { method: 'GET' });\n return response as DdnsRecordInfo;\n } catch (error) {\n if (error instanceof SubdomainClientError && error.status === 404) {\n return null;\n }\n throw error;\n }\n }\n\n // ============ Private Methods ============\n\n private async fetch(url: string, options: {\n method: string;\n body?: string;\n }): Promise<unknown> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n // 使用 undici 的 fetch,禁用代理\n const { fetch: undiciFetch, Agent } = await import('undici');\n const agent = new Agent({\n connect: { timeout: this.timeoutMs },\n });\n\n const response = await undiciFetch(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `XpodNode ${this.nodeId}:${this.nodeToken}`,\n },\n body: options.body,\n signal: controller.signal,\n dispatcher: agent,\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n const error = (data as any)?.error ?? 'Unknown error';\n throw new SubdomainClientError(error, response.status);\n }\n\n return data;\n } catch (error) {\n if (error instanceof SubdomainClientError) {\n throw error;\n }\n if (error instanceof Error && error.name === 'AbortError') {\n throw new SubdomainClientError('Request timeout', 408);\n }\n throw new SubdomainClientError(\n error instanceof Error ? error.message : 'Unknown error',\n 500,\n );\n } finally {\n clearTimeout(timeout);\n }\n }\n}\n\n/**\n * SubdomainClient 错误\n */\nexport class SubdomainClientError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n ) {\n super(message);\n this.name = 'SubdomainClientError';\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",