@undefineds.co/xpod 0.1.6 → 0.2.0-preview.2
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.
- package/README.md +141 -2
- package/config/cli.json +9 -71
- package/config/cloud.json +34 -7
- package/config/local.json +6 -2
- package/config/resolver.json +11 -49
- package/config/runtime-open.json +22 -0
- package/config/xpod.base.json +32 -0
- package/config/xpod.cluster.json +2 -44
- package/config/xpod.json +5 -2
- package/dist/api/ApiServer.js +1 -1
- package/dist/api/ApiServer.js.map +1 -1
- package/dist/api/auth/AuthContext.d.ts +12 -1
- package/dist/api/auth/AuthContext.js +18 -1
- package/dist/api/auth/AuthContext.js.map +1 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +0 -1
- package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
- package/dist/api/auth/ServiceTokenAuthenticator.d.ts +18 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js +50 -0
- package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -0
- package/dist/api/auth/index.d.ts +1 -0
- package/dist/api/auth/index.js +1 -0
- package/dist/api/auth/index.js.map +1 -1
- package/dist/api/chatkit/ai-provider.d.ts +0 -10
- package/dist/api/chatkit/ai-provider.js +11 -120
- package/dist/api/chatkit/ai-provider.js.map +1 -1
- package/dist/api/chatkit/default-agent.js +11 -8
- package/dist/api/chatkit/default-agent.js.map +1 -1
- package/dist/api/chatkit/pod-store.js +19 -3
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/chatkit/schema.d.ts +9 -3
- package/dist/api/chatkit/schema.js +14 -6
- package/dist/api/chatkit/schema.js.map +1 -1
- package/dist/api/container/business-token.d.ts +9 -0
- package/dist/api/container/business-token.js +32 -0
- package/dist/api/container/business-token.js.map +1 -0
- package/dist/api/container/cloud.js +36 -12
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/common.js +12 -5
- package/dist/api/container/common.js.map +1 -1
- package/dist/api/container/index.js +94 -14
- package/dist/api/container/index.js.map +1 -1
- package/dist/api/container/local.js +2 -1
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/container/routes.js +81 -15
- package/dist/api/container/routes.js.map +1 -1
- package/dist/api/container/types.d.ts +8 -6
- package/dist/api/container/types.js.map +1 -1
- package/dist/api/handlers/AdminHandler.js +9 -9
- package/dist/api/handlers/AdminHandler.js.map +1 -1
- package/dist/api/handlers/ApiKeyHandler.js +0 -6
- package/dist/api/handlers/ApiKeyHandler.js.map +1 -1
- package/dist/api/handlers/EdgeNodeSignalHandler.d.ts +17 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js +171 -0
- package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -0
- package/dist/api/handlers/PodManagementHandler.d.ts +5 -4
- package/dist/api/handlers/PodManagementHandler.js +11 -10
- package/dist/api/handlers/PodManagementHandler.js.map +1 -1
- package/dist/api/handlers/ProvisionHandler.d.ts +42 -0
- package/dist/api/handlers/ProvisionHandler.js +161 -0
- package/dist/api/handlers/ProvisionHandler.js.map +1 -0
- package/dist/api/handlers/QuotaHandler.d.ts +7 -7
- package/dist/api/handlers/QuotaHandler.js +143 -73
- package/dist/api/handlers/QuotaHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainClientHandler.js +2 -2
- package/dist/api/handlers/SubdomainClientHandler.js.map +1 -1
- package/dist/api/handlers/SubdomainHandler.js +13 -8
- package/dist/api/handlers/SubdomainHandler.js.map +1 -1
- package/dist/api/handlers/UsageHandler.d.ts +14 -0
- package/dist/api/handlers/UsageHandler.js +123 -0
- package/dist/api/handlers/UsageHandler.js.map +1 -0
- package/dist/api/handlers/index.d.ts +3 -1
- package/dist/api/handlers/index.js +3 -1
- package/dist/api/handlers/index.js.map +1 -1
- package/dist/api/main.js +18 -0
- package/dist/api/main.js.map +1 -1
- package/dist/api/middleware/OpenAuthMiddleware.d.ts +12 -0
- package/dist/api/middleware/OpenAuthMiddleware.js +27 -0
- package/dist/api/middleware/OpenAuthMiddleware.js.map +1 -0
- package/dist/api/runtime.d.ts +15 -0
- package/dist/api/runtime.js +104 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/service/VercelChatService.d.ts +16 -7
- package/dist/api/service/VercelChatService.js +98 -178
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/store/DrizzleClientCredentialsStore.d.ts +6 -11
- package/dist/api/store/DrizzleClientCredentialsStore.js +9 -39
- package/dist/api/store/DrizzleClientCredentialsStore.js.map +1 -1
- package/dist/authorization/AuthModeSelector.d.ts +10 -0
- package/dist/authorization/AuthModeSelector.js +27 -0
- package/dist/authorization/AuthModeSelector.js.map +1 -0
- package/dist/authorization/AuthModeSelector.jsonld +81 -0
- package/dist/cli/commands/account.d.ts +6 -0
- package/dist/cli/commands/account.js +119 -0
- package/dist/cli/commands/account.js.map +1 -0
- package/dist/cli/commands/auth.js +20 -29
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/backup.d.ts +15 -0
- package/dist/cli/commands/backup.js +286 -0
- package/dist/cli/commands/backup.js.map +1 -0
- package/dist/cli/commands/config.d.ts +34 -3
- package/dist/cli/commands/config.js +195 -258
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +6 -0
- package/dist/cli/commands/doctor.js +94 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/pod.d.ts +6 -0
- package/dist/cli/commands/pod.js +124 -0
- package/dist/cli/commands/pod.js.map +1 -0
- package/dist/cli/commands/start.js +28 -5
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lib/credentials-store.d.ts +17 -0
- package/dist/cli/lib/credentials-store.js +73 -0
- package/dist/cli/lib/credentials-store.js.map +1 -0
- package/dist/cli/lib/css-account.d.ts +17 -0
- package/dist/cli/lib/css-account.js +56 -0
- package/dist/cli/lib/css-account.js.map +1 -1
- package/dist/cli/lib/pod-thread-store.d.ts +57 -0
- package/dist/cli/lib/pod-thread-store.js +310 -0
- package/dist/cli/lib/pod-thread-store.js.map +1 -0
- package/dist/cli/lib/solid-auth.d.ts +20 -0
- package/dist/cli/lib/solid-auth.js +70 -0
- package/dist/cli/lib/solid-auth.js.map +1 -0
- package/dist/components/components.jsonld +5 -8
- package/dist/components/context.jsonld +114 -244
- package/dist/edge/EdgeNodeAgent.js +2 -2
- package/dist/edge/EdgeNodeAgent.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -7
- package/dist/edge/EdgeNodeDnsCoordinator.js +31 -41
- package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
- package/dist/edge/EdgeNodeDnsCoordinator.jsonld +1 -27
- package/dist/edge/EdgeNodeModeDetector.d.ts +1 -1
- package/dist/edge/EdgeNodeModeDetector.js +9 -11
- package/dist/edge/EdgeNodeModeDetector.js.map +1 -1
- package/dist/http/ClusterIngressRouter.js +3 -3
- package/dist/http/ClusterIngressRouter.js.map +1 -1
- package/dist/http/ClusterWebSocketConfigurator.js +2 -2
- package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
- package/dist/http/PodRoutingHttpHandler.js +2 -2
- package/dist/http/PodRoutingHttpHandler.js.map +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.d.ts +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js +1 -1
- package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
- package/dist/identity/drizzle/EdgeNodeRepository.d.ts +37 -4
- package/dist/identity/drizzle/EdgeNodeRepository.js +120 -128
- package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
- package/dist/identity/drizzle/ServiceTokenRepository.d.ts +52 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js +143 -0
- package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -0
- package/dist/identity/drizzle/db.d.ts +9 -0
- package/dist/identity/drizzle/db.js +208 -1
- package/dist/identity/drizzle/db.js.map +1 -1
- package/dist/identity/drizzle/schema.pg.d.ts +5 -0
- package/dist/identity/drizzle/schema.pg.js +49 -20
- package/dist/identity/drizzle/schema.pg.js.map +1 -1
- package/dist/identity/drizzle/schema.sqlite.d.ts +332 -57
- package/dist/identity/drizzle/schema.sqlite.js +48 -18
- package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js +6 -4
- package/dist/identity/oidc/AutoDetectIdentityProviderHandler.js.map +1 -1
- package/dist/index.d.ts +6 -9
- package/dist/index.js +12 -14
- package/dist/index.js.map +1 -1
- package/dist/main.js +25 -8
- package/dist/main.js.map +1 -1
- package/dist/provision/ProvisionCodeCodec.d.ts +39 -0
- package/dist/provision/ProvisionCodeCodec.js +65 -0
- package/dist/provision/ProvisionCodeCodec.js.map +1 -0
- package/dist/provision/ProvisionCodeCodec.jsonld +47 -0
- package/dist/provision/ProvisionPodCreator.d.ts +20 -0
- package/dist/provision/ProvisionPodCreator.js +84 -0
- package/dist/provision/ProvisionPodCreator.js.map +1 -0
- package/dist/provision/ProvisionPodCreator.jsonld +118 -0
- package/dist/quota/DrizzleQuotaService.d.ts +17 -3
- package/dist/quota/DrizzleQuotaService.js +108 -8
- package/dist/quota/DrizzleQuotaService.js.map +1 -1
- package/dist/quota/DrizzleQuotaService.jsonld +33 -22
- package/dist/quota/NoopQuotaService.d.ts +7 -1
- package/dist/quota/NoopQuotaService.js +12 -0
- package/dist/quota/NoopQuotaService.js.map +1 -1
- package/dist/quota/NoopQuotaService.jsonld +24 -0
- package/dist/quota/QuotaService.d.ts +17 -0
- package/dist/quota/QuotaService.js +5 -0
- package/dist/quota/QuotaService.js.map +1 -1
- package/dist/quota/QuotaService.jsonld +50 -0
- package/dist/runtime/Proxy.d.ts +22 -4
- package/dist/runtime/Proxy.js +154 -35
- package/dist/runtime/Proxy.js.map +1 -1
- package/dist/runtime/XpodRuntime.d.ts +49 -0
- package/dist/runtime/XpodRuntime.js +374 -0
- package/dist/runtime/XpodRuntime.js.map +1 -0
- package/dist/runtime/env-utils.d.ts +2 -0
- package/dist/runtime/env-utils.js +55 -0
- package/dist/runtime/env-utils.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +8 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/socket-fetch.d.ts +1 -0
- package/dist/runtime/socket-fetch.js +72 -0
- package/dist/runtime/socket-fetch.js.map +1 -0
- package/dist/runtime/socket-http.d.ts +1 -0
- package/dist/runtime/socket-http.js +142 -0
- package/dist/runtime/socket-http.js.map +1 -0
- package/dist/runtime/socket-utils.d.ts +2 -0
- package/dist/runtime/socket-utils.js +34 -0
- package/dist/runtime/socket-utils.js.map +1 -0
- package/dist/service/{EdgeNodeHeartbeatService.d.ts → EdgeNodeSignalClient.d.ts} +3 -3
- package/dist/service/{EdgeNodeHeartbeatService.js → EdgeNodeSignalClient.js} +4 -4
- package/dist/service/EdgeNodeSignalClient.js.map +1 -0
- package/dist/service/PodMigrationService.d.ts +1 -2
- package/dist/service/PodMigrationService.js +1 -2
- package/dist/service/PodMigrationService.js.map +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js +1 -1
- package/dist/storage/SparqlUpdateResourceStore.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.d.ts +6 -0
- package/dist/storage/accessors/MinioDataAccessor.js +10 -0
- package/dist/storage/accessors/MinioDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MinioDataAccessor.jsonld +4 -0
- package/dist/storage/accessors/MixDataAccessor.d.ts +2 -1
- package/dist/storage/accessors/MixDataAccessor.js +12 -1
- package/dist/storage/accessors/MixDataAccessor.js.map +1 -1
- package/dist/storage/accessors/MixDataAccessor.jsonld +19 -0
- package/dist/storage/locking/UrlAwareRedisLocker.d.ts +18 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js +60 -0
- package/dist/storage/locking/UrlAwareRedisLocker.js.map +1 -0
- package/dist/storage/locking/UrlAwareRedisLocker.jsonld +123 -0
- package/dist/storage/quota/UsageRepository.d.ts +41 -8
- package/dist/storage/quota/UsageRepository.js +252 -50
- package/dist/storage/quota/UsageRepository.js.map +1 -1
- package/dist/storage/sparql/ComunicaQuintEngine.d.ts +9 -0
- package/dist/storage/sparql/ComunicaQuintEngine.js +50 -9
- package/dist/storage/sparql/ComunicaQuintEngine.js.map +1 -1
- package/dist/storage/sparql/QueryOptimizer.js +13 -1
- package/dist/storage/sparql/QueryOptimizer.js.map +1 -1
- package/dist/storage/sparql/QuintQuerySource.d.ts +14 -0
- package/dist/storage/sparql/QuintQuerySource.js +152 -1
- package/dist/storage/sparql/QuintQuerySource.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.d.ts +1 -0
- package/dist/storage/sparql/SubgraphQueryEngine.js +6 -2
- package/dist/storage/sparql/SubgraphQueryEngine.js.map +1 -1
- package/dist/storage/sparql/SubgraphQueryEngine.jsonld +4 -0
- package/dist/subdomain/SubdomainClient.d.ts +3 -3
- package/dist/subdomain/SubdomainClient.js +1 -1
- package/dist/subdomain/SubdomainClient.js.map +1 -1
- package/dist/subdomain/SubdomainService.d.ts +15 -16
- package/dist/subdomain/SubdomainService.js +80 -54
- package/dist/subdomain/SubdomainService.js.map +1 -1
- package/dist/subdomain/SubdomainService.jsonld +22 -26
- package/dist/supervisor/Supervisor.d.ts +7 -2
- package/dist/supervisor/Supervisor.js +33 -1
- package/dist/supervisor/Supervisor.js.map +1 -1
- package/dist/test-utils/index.d.ts +4 -0
- package/dist/test-utils/index.js +8 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/no-auth-xpod.d.ts +11 -0
- package/dist/test-utils/no-auth-xpod.js +25 -0
- package/dist/test-utils/no-auth-xpod.js.map +1 -0
- package/dist/test-utils/seed-pod.d.ts +5 -0
- package/dist/test-utils/seed-pod.js +61 -0
- package/dist/test-utils/seed-pod.js.map +1 -0
- package/package.json +23 -5
- package/templates/identity/account/create-pod.html.ejs +110 -0
- package/templates/main.html.ejs +10 -0
- package/dist/api/handlers/DevHandler.d.ts +0 -18
- package/dist/api/handlers/DevHandler.js +0 -276
- package/dist/api/handlers/DevHandler.js.map +0 -1
- package/dist/api/handlers/SignalHandler.d.ts +0 -13
- package/dist/api/handlers/SignalHandler.js +0 -122
- package/dist/api/handlers/SignalHandler.js.map +0 -1
- package/dist/gateway/Proxy.d.ts +0 -24
- package/dist/gateway/Proxy.js +0 -209
- package/dist/gateway/Proxy.js.map +0 -1
- package/dist/gateway/Supervisor.d.ts +0 -2
- package/dist/gateway/Supervisor.js +0 -7
- package/dist/gateway/Supervisor.js.map +0 -1
- package/dist/gateway/port-finder.d.ts +0 -4
- package/dist/gateway/port-finder.js +0 -15
- package/dist/gateway/port-finder.js.map +0 -1
- package/dist/gateway/types.d.ts +0 -1
- package/dist/gateway/types.js +0 -3
- package/dist/gateway/types.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.d.ts +0 -24
- package/dist/http/SignalInterceptHttpHandler.js +0 -47
- package/dist/http/SignalInterceptHttpHandler.js.map +0 -1
- package/dist/http/SignalInterceptHttpHandler.jsonld +0 -103
- package/dist/http/admin/EdgeNodeSignalHttpHandler.d.ts +0 -71
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js +0 -674
- package/dist/http/admin/EdgeNodeSignalHttpHandler.js.map +0 -1
- package/dist/http/admin/EdgeNodeSignalHttpHandler.jsonld +0 -406
- package/dist/http/cluster/PodMigrationHttpHandler.jsonld +0 -169
- package/dist/quota/DefaultQuotaService.d.ts +0 -16
- package/dist/quota/DefaultQuotaService.js +0 -37
- package/dist/quota/DefaultQuotaService.js.map +0 -1
- package/dist/quota/DefaultQuotaService.jsonld +0 -85
- package/dist/service/EdgeNodeHeartbeatService.js.map +0 -1
- package/dist/service/PodMigrationService.jsonld +0 -76
- package/dist/storage/MigratableDataAccessor.d.ts +0 -63
- package/dist/storage/MigratableDataAccessor.js +0 -11
- package/dist/storage/MigratableDataAccessor.js.map +0 -1
- package/dist/storage/MigratableDataAccessor.jsonld +0 -60
- package/dist/storage/accessors/TieredMinioDataAccessor.d.ts +0 -150
- package/dist/storage/accessors/TieredMinioDataAccessor.js +0 -582
- package/dist/storage/accessors/TieredMinioDataAccessor.js.map +0 -1
- package/dist/storage/accessors/TieredMinioDataAccessor.jsonld +0 -333
- package/static/app/assets/index.css +0 -1
- package/static/app/assets/main.js +0 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeAgent.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'node:os';\nimport { spawn } from 'node:child_process';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { EdgeNodeHeartbeatServiceOptions } from '../service/EdgeNodeHeartbeatService';\nimport { EdgeNodeHeartbeatService } from '../service/EdgeNodeHeartbeatService';\nimport { FrpcProcessManager, type FrpcRuntimeStatus } from './frp/FrpcProcessManager';\nimport { AcmeCertificateManager } from './acme/AcmeCertificateManager';\nimport { ClusterCertificateManager } from './acme/ClusterCertificateManager';\nimport { EdgeNodeCapabilityDetector, type NetworkAddressInfo } from './EdgeNodeCapabilityDetector';\n\nexport interface EdgeNodeAgentOptions {\n signalEndpoint: string;\n nodeId: string;\n nodeToken: string;\n baseUrl?: string;\n publicAddress?: string;\n pods?: string[];\n includeSystemMetrics?: boolean;\n enableNetworkDetection?: boolean;\n metadata?: Record<string, unknown>;\n intervalMs?: number;\n onHeartbeatResponse?: (data: unknown) => void;\n acme?: {\n mode?: 'local' | 'cluster';\n email?: string;\n domains?: string[];\n directoryUrl?: string;\n accountKeyPath?: string;\n certificateKeyPath: string;\n certificatePath: string;\n fullChainPath?: string;\n renewBeforeDays?: number;\n propagationDelayMs?: number;\n postDeployCommand?: string[];\n };\n frp?: {\n binaryPath: string;\n configPath: string;\n workingDirectory?: string;\n logPrefix?: string;\n autoRestart?: boolean;\n };\n}\n\nexport class EdgeNodeAgent {\n private readonly logger = getLoggerFor(this);\n private heartbeat?: EdgeNodeHeartbeatService;\n private frpManager?: FrpcProcessManager;\n private clusterCertificate?: ClusterCertificateManager;\n private networkDetector?: EdgeNodeCapabilityDetector;\n private cachedNetworkInfo?: NetworkAddressInfo;\n private lastNetworkDetection = 0;\n private readonly networkDetectionIntervalMs = 60_000; // 每分钟重新检测一次\n\n public async start(options: EdgeNodeAgentOptions): Promise<void> {\n if (options.acme) {\n const mode = options.acme.mode ?? 'local';\n if (mode === 'cluster') {\n await this.ensureClusterCertificate(options);\n } else {\n await this.issueCertificateLocally(options);\n }\n }\n if (options.frp) {\n this.frpManager = new FrpcProcessManager({\n binaryPath: options.frp.binaryPath,\n configPath: options.frp.configPath,\n workingDirectory: options.frp.workingDirectory,\n logPrefix: options.frp.logPrefix,\n autoRestart: options.frp.autoRestart,\n });\n }\n \n // 初始化网络检测器\n if (options.enableNetworkDetection !== false) {\n this.networkDetector = new EdgeNodeCapabilityDetector({\n dynamicDetection: {\n enableNetworkDetection: true,\n },\n });\n // 执行初始网络检测\n this.cachedNetworkInfo = await this.networkDetector.detectNetworkAddresses();\n this.lastNetworkDetection = Date.now();\n this.logger.info(`Network detection: IPv4=${this.cachedNetworkInfo.ipv4Public ?? this.cachedNetworkInfo.ipv4}, IPv6=${this.cachedNetworkInfo.ipv6Public ?? this.cachedNetworkInfo.ipv6}, hasPublicIPv6=${this.cachedNetworkInfo.hasPublicIPv6}`);\n }\n \n const systemMetrics = options.includeSystemMetrics ? this.collectSystemMetrics() : undefined;\n const metadataPayload = {\n ...(options.metadata ?? {}),\n system: systemMetrics,\n } as Record<string, unknown>;\n\n const certificatePayload = this.clusterCertificate?.getHeartbeatPayload();\n const heartbeatOptions: EdgeNodeHeartbeatServiceOptions = {\n edgeNodesEnabled: true,\n signalEndpoint: options.signalEndpoint,\n nodeId: options.nodeId,\n nodeToken: options.nodeToken,\n baseUrl: options.baseUrl,\n publicAddress: options.publicAddress,\n pods: options.pods,\n intervalMs: options.intervalMs,\n metadata: this.stringifyIfContent(metadataPayload),\n metrics: systemMetrics ? JSON.stringify(systemMetrics) : undefined,\n certificate: certificatePayload ? JSON.stringify(certificatePayload) : undefined,\n onHeartbeatResponse: (data: unknown): void => {\n this.handleHeartbeatResponse(data);\n options.onHeartbeatResponse?.(data);\n },\n networkSupplier: this.networkDetector ? () => this.getNetworkInfo() : undefined,\n };\n if (this.frpManager) {\n heartbeatOptions.tunnelSupplier = () => this.buildTunnelHeartbeatPayload();\n }\n\n this.heartbeat = new EdgeNodeHeartbeatService(heartbeatOptions);\n }\n\n public stop(): void {\n if (this.heartbeat && typeof (this.heartbeat as any).dispose === 'function') {\n (this.heartbeat as any).dispose();\n }\n this.heartbeat = undefined;\n void this.frpManager?.stop();\n this.clusterCertificate?.stop();\n }\n\n private stringifyIfContent(data: Record<string, unknown>): string | undefined {\n const sanitized: Record<string, unknown> = {};\n for (const [ key, value ] of Object.entries(data)) {\n if (value !== undefined) {\n sanitized[key] = value;\n }\n }\n return Object.keys(sanitized).length > 0 ? JSON.stringify(sanitized) : undefined;\n }\n\n private collectSystemMetrics(): Record<string, unknown> {\n const load = os.loadavg();\n return {\n hostname: os.hostname(),\n platform: os.platform(),\n arch: os.arch(),\n uptime: os.uptime(),\n cpuCount: os.cpus().length,\n load1: load[0],\n load5: load[1],\n load15: load[2],\n totalMem: os.totalmem(),\n freeMem: os.freemem(),\n };\n }\n\n private handleHeartbeatResponse(data: unknown): void {\n if (!data || typeof data !== 'object') {\n return;\n }\n const body = data as Record<string, any>;\n const metadata = body.metadata as Record<string, any> | undefined;\n this.clusterCertificate?.handleHeartbeatMetadata(metadata);\n const tunnel = metadata?.tunnel as Record<string, any> | undefined;\n const config = tunnel?.config as Record<string, any> | undefined;\n if (config) {\n this.logger.debug(`接收到隧道配置: ${JSON.stringify({ entrypoint: tunnel?.entrypoint, proxyName: config.proxyName })}`);\n }\n const entrypoint = typeof tunnel?.entrypoint === 'string' ? tunnel.entrypoint :\n typeof config?.publicUrl === 'string' ? config.publicUrl : undefined;\n void this.frpManager?.applyConfig(config as any, tunnel?.status as string | undefined, entrypoint);\n }\n\n private async issueCertificateLocally(options: EdgeNodeAgentOptions): Promise<void> {\n const acmeOptions = options.acme!;\n if (!acmeOptions.email || !acmeOptions.domains || acmeOptions.domains.length === 0) {\n throw new Error('本地 ACME 模式需要提供 email 与 domains。');\n }\n if (!acmeOptions.accountKeyPath) {\n throw new Error('本地 ACME 模式需要提供 accountKeyPath。');\n }\n const manager = new AcmeCertificateManager({\n signalEndpoint: options.signalEndpoint,\n nodeId: options.nodeId,\n nodeToken: options.nodeToken,\n email: acmeOptions.email!,\n domains: acmeOptions.domains!,\n directoryUrl: acmeOptions.directoryUrl,\n accountKeyPath: acmeOptions.accountKeyPath!,\n certificateKeyPath: acmeOptions.certificateKeyPath,\n certificatePath: acmeOptions.certificatePath,\n fullChainPath: acmeOptions.fullChainPath,\n renewBeforeDays: acmeOptions.renewBeforeDays,\n propagationDelayMs: acmeOptions.propagationDelayMs,\n });\n try {\n const issued = await manager.ensureCertificate();\n if (issued && acmeOptions.postDeployCommand && acmeOptions.postDeployCommand.length > 0) {\n await this.runPostDeploy(acmeOptions.postDeployCommand);\n }\n } catch (error: unknown) {\n this.logger.error(`自动签发证书失败:${(error as Error).message}`);\n throw error;\n }\n }\n\n private async ensureClusterCertificate(options: EdgeNodeAgentOptions): Promise<void> {\n const acmeOptions = options.acme!;\n if (!acmeOptions.certificateKeyPath || !acmeOptions.certificatePath) {\n throw new Error('Cluster 模式需要提供 certificateKeyPath 与 certificatePath。');\n }\n const manager = new ClusterCertificateManager({\n signalEndpoint: options.signalEndpoint,\n nodeId: options.nodeId,\n nodeToken: options.nodeToken,\n certificateKeyPath: acmeOptions.certificateKeyPath,\n certificatePath: acmeOptions.certificatePath,\n fullChainPath: acmeOptions.fullChainPath,\n renewBeforeDays: acmeOptions.renewBeforeDays,\n onCertificateInstalled: acmeOptions.postDeployCommand ? () => this.runPostDeploy(acmeOptions.postDeployCommand!) : undefined,\n });\n this.clusterCertificate = manager;\n await manager.start();\n }\n\n private async runPostDeploy(command: string[]): Promise<void> {\n const [ executable, ...args ] = command;\n if (!executable) {\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const child = spawn(executable, args, { stdio: 'inherit' });\n child.on('error', reject);\n child.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`命令 ${executable} 退出码 ${code}`));\n }\n });\n });\n }\n\n private buildTunnelHeartbeatPayload(): Record<string, unknown> | undefined {\n const status: FrpcRuntimeStatus | undefined = this.frpManager?.getStatus();\n if (!status) {\n return undefined;\n }\n return { client: status };\n }\n\n /**\n * 获取网络信息(带缓存,每分钟刷新一次)\n */\n private async getNetworkInfo(): Promise<{ ipv4?: string; ipv6?: string }> {\n const now = Date.now();\n \n // 如果缓存过期,重新检测\n if (!this.cachedNetworkInfo || (now - this.lastNetworkDetection) > this.networkDetectionIntervalMs) {\n if (this.networkDetector) {\n try {\n this.cachedNetworkInfo = await this.networkDetector.detectNetworkAddresses();\n this.lastNetworkDetection = now;\n } catch (error: unknown) {\n this.logger.debug(`Network detection failed: ${(error as Error).message}`);\n }\n }\n }\n \n // 优先返回公网地址\n return {\n ipv4: this.cachedNetworkInfo?.ipv4Public ?? this.cachedNetworkInfo?.ipv4,\n ipv6: this.cachedNetworkInfo?.ipv6Public ?? this.cachedNetworkInfo?.ipv6,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeAgent.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'node:os';\nimport { spawn } from 'node:child_process';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { EdgeNodeSignalClientOptions } from '../service/EdgeNodeSignalClient';\nimport { EdgeNodeSignalClient } from '../service/EdgeNodeSignalClient';\nimport { FrpcProcessManager, type FrpcRuntimeStatus } from './frp/FrpcProcessManager';\nimport { AcmeCertificateManager } from './acme/AcmeCertificateManager';\nimport { ClusterCertificateManager } from './acme/ClusterCertificateManager';\nimport { EdgeNodeCapabilityDetector, type NetworkAddressInfo } from './EdgeNodeCapabilityDetector';\n\nexport interface EdgeNodeAgentOptions {\n signalEndpoint: string;\n nodeId: string;\n nodeToken: string;\n baseUrl?: string;\n publicAddress?: string;\n pods?: string[];\n includeSystemMetrics?: boolean;\n enableNetworkDetection?: boolean;\n metadata?: Record<string, unknown>;\n intervalMs?: number;\n onHeartbeatResponse?: (data: unknown) => void;\n acme?: {\n mode?: 'local' | 'cluster';\n email?: string;\n domains?: string[];\n directoryUrl?: string;\n accountKeyPath?: string;\n certificateKeyPath: string;\n certificatePath: string;\n fullChainPath?: string;\n renewBeforeDays?: number;\n propagationDelayMs?: number;\n postDeployCommand?: string[];\n };\n frp?: {\n binaryPath: string;\n configPath: string;\n workingDirectory?: string;\n logPrefix?: string;\n autoRestart?: boolean;\n };\n}\n\nexport class EdgeNodeAgent {\n private readonly logger = getLoggerFor(this);\n private heartbeat?: EdgeNodeSignalClient;\n private frpManager?: FrpcProcessManager;\n private clusterCertificate?: ClusterCertificateManager;\n private networkDetector?: EdgeNodeCapabilityDetector;\n private cachedNetworkInfo?: NetworkAddressInfo;\n private lastNetworkDetection = 0;\n private readonly networkDetectionIntervalMs = 60_000; // 每分钟重新检测一次\n\n public async start(options: EdgeNodeAgentOptions): Promise<void> {\n if (options.acme) {\n const mode = options.acme.mode ?? 'local';\n if (mode === 'cluster') {\n await this.ensureClusterCertificate(options);\n } else {\n await this.issueCertificateLocally(options);\n }\n }\n if (options.frp) {\n this.frpManager = new FrpcProcessManager({\n binaryPath: options.frp.binaryPath,\n configPath: options.frp.configPath,\n workingDirectory: options.frp.workingDirectory,\n logPrefix: options.frp.logPrefix,\n autoRestart: options.frp.autoRestart,\n });\n }\n \n // 初始化网络检测器\n if (options.enableNetworkDetection !== false) {\n this.networkDetector = new EdgeNodeCapabilityDetector({\n dynamicDetection: {\n enableNetworkDetection: true,\n },\n });\n // 执行初始网络检测\n this.cachedNetworkInfo = await this.networkDetector.detectNetworkAddresses();\n this.lastNetworkDetection = Date.now();\n this.logger.info(`Network detection: IPv4=${this.cachedNetworkInfo.ipv4Public ?? this.cachedNetworkInfo.ipv4}, IPv6=${this.cachedNetworkInfo.ipv6Public ?? this.cachedNetworkInfo.ipv6}, hasPublicIPv6=${this.cachedNetworkInfo.hasPublicIPv6}`);\n }\n \n const systemMetrics = options.includeSystemMetrics ? this.collectSystemMetrics() : undefined;\n const metadataPayload = {\n ...(options.metadata ?? {}),\n system: systemMetrics,\n } as Record<string, unknown>;\n\n const certificatePayload = this.clusterCertificate?.getHeartbeatPayload();\n const heartbeatOptions: EdgeNodeSignalClientOptions = {\n edgeNodesEnabled: true,\n signalEndpoint: options.signalEndpoint,\n nodeId: options.nodeId,\n nodeToken: options.nodeToken,\n baseUrl: options.baseUrl,\n publicAddress: options.publicAddress,\n pods: options.pods,\n intervalMs: options.intervalMs,\n metadata: this.stringifyIfContent(metadataPayload),\n metrics: systemMetrics ? JSON.stringify(systemMetrics) : undefined,\n certificate: certificatePayload ? JSON.stringify(certificatePayload) : undefined,\n onHeartbeatResponse: (data: unknown): void => {\n this.handleHeartbeatResponse(data);\n options.onHeartbeatResponse?.(data);\n },\n networkSupplier: this.networkDetector ? () => this.getNetworkInfo() : undefined,\n };\n if (this.frpManager) {\n heartbeatOptions.tunnelSupplier = () => this.buildTunnelHeartbeatPayload();\n }\n\n this.heartbeat = new EdgeNodeSignalClient(heartbeatOptions);\n }\n\n public stop(): void {\n if (this.heartbeat && typeof (this.heartbeat as any).dispose === 'function') {\n (this.heartbeat as any).dispose();\n }\n this.heartbeat = undefined;\n void this.frpManager?.stop();\n this.clusterCertificate?.stop();\n }\n\n private stringifyIfContent(data: Record<string, unknown>): string | undefined {\n const sanitized: Record<string, unknown> = {};\n for (const [ key, value ] of Object.entries(data)) {\n if (value !== undefined) {\n sanitized[key] = value;\n }\n }\n return Object.keys(sanitized).length > 0 ? JSON.stringify(sanitized) : undefined;\n }\n\n private collectSystemMetrics(): Record<string, unknown> {\n const load = os.loadavg();\n return {\n hostname: os.hostname(),\n platform: os.platform(),\n arch: os.arch(),\n uptime: os.uptime(),\n cpuCount: os.cpus().length,\n load1: load[0],\n load5: load[1],\n load15: load[2],\n totalMem: os.totalmem(),\n freeMem: os.freemem(),\n };\n }\n\n private handleHeartbeatResponse(data: unknown): void {\n if (!data || typeof data !== 'object') {\n return;\n }\n const body = data as Record<string, any>;\n const metadata = body.metadata as Record<string, any> | undefined;\n this.clusterCertificate?.handleHeartbeatMetadata(metadata);\n const tunnel = metadata?.tunnel as Record<string, any> | undefined;\n const config = tunnel?.config as Record<string, any> | undefined;\n if (config) {\n this.logger.debug(`接收到隧道配置: ${JSON.stringify({ entrypoint: tunnel?.entrypoint, proxyName: config.proxyName })}`);\n }\n const entrypoint = typeof tunnel?.entrypoint === 'string' ? tunnel.entrypoint :\n typeof config?.publicUrl === 'string' ? config.publicUrl : undefined;\n void this.frpManager?.applyConfig(config as any, tunnel?.status as string | undefined, entrypoint);\n }\n\n private async issueCertificateLocally(options: EdgeNodeAgentOptions): Promise<void> {\n const acmeOptions = options.acme!;\n if (!acmeOptions.email || !acmeOptions.domains || acmeOptions.domains.length === 0) {\n throw new Error('本地 ACME 模式需要提供 email 与 domains。');\n }\n if (!acmeOptions.accountKeyPath) {\n throw new Error('本地 ACME 模式需要提供 accountKeyPath。');\n }\n const manager = new AcmeCertificateManager({\n signalEndpoint: options.signalEndpoint,\n nodeId: options.nodeId,\n nodeToken: options.nodeToken,\n email: acmeOptions.email!,\n domains: acmeOptions.domains!,\n directoryUrl: acmeOptions.directoryUrl,\n accountKeyPath: acmeOptions.accountKeyPath!,\n certificateKeyPath: acmeOptions.certificateKeyPath,\n certificatePath: acmeOptions.certificatePath,\n fullChainPath: acmeOptions.fullChainPath,\n renewBeforeDays: acmeOptions.renewBeforeDays,\n propagationDelayMs: acmeOptions.propagationDelayMs,\n });\n try {\n const issued = await manager.ensureCertificate();\n if (issued && acmeOptions.postDeployCommand && acmeOptions.postDeployCommand.length > 0) {\n await this.runPostDeploy(acmeOptions.postDeployCommand);\n }\n } catch (error: unknown) {\n this.logger.error(`自动签发证书失败:${(error as Error).message}`);\n throw error;\n }\n }\n\n private async ensureClusterCertificate(options: EdgeNodeAgentOptions): Promise<void> {\n const acmeOptions = options.acme!;\n if (!acmeOptions.certificateKeyPath || !acmeOptions.certificatePath) {\n throw new Error('Cluster 模式需要提供 certificateKeyPath 与 certificatePath。');\n }\n const manager = new ClusterCertificateManager({\n signalEndpoint: options.signalEndpoint,\n nodeId: options.nodeId,\n nodeToken: options.nodeToken,\n certificateKeyPath: acmeOptions.certificateKeyPath,\n certificatePath: acmeOptions.certificatePath,\n fullChainPath: acmeOptions.fullChainPath,\n renewBeforeDays: acmeOptions.renewBeforeDays,\n onCertificateInstalled: acmeOptions.postDeployCommand ? () => this.runPostDeploy(acmeOptions.postDeployCommand!) : undefined,\n });\n this.clusterCertificate = manager;\n await manager.start();\n }\n\n private async runPostDeploy(command: string[]): Promise<void> {\n const [ executable, ...args ] = command;\n if (!executable) {\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const child = spawn(executable, args, { stdio: 'inherit' });\n child.on('error', reject);\n child.on('exit', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`命令 ${executable} 退出码 ${code}`));\n }\n });\n });\n }\n\n private buildTunnelHeartbeatPayload(): Record<string, unknown> | undefined {\n const status: FrpcRuntimeStatus | undefined = this.frpManager?.getStatus();\n if (!status) {\n return undefined;\n }\n return { client: status };\n }\n\n /**\n * 获取网络信息(带缓存,每分钟刷新一次)\n */\n private async getNetworkInfo(): Promise<{ ipv4?: string; ipv6?: string }> {\n const now = Date.now();\n \n // 如果缓存过期,重新检测\n if (!this.cachedNetworkInfo || (now - this.lastNetworkDetection) > this.networkDetectionIntervalMs) {\n if (this.networkDetector) {\n try {\n this.cachedNetworkInfo = await this.networkDetector.detectNetworkAddresses();\n this.lastNetworkDetection = now;\n } catch (error: unknown) {\n this.logger.debug(`Network detection failed: ${(error as Error).message}`);\n }\n }\n }\n \n // 优先返回公网地址\n return {\n ipv4: this.cachedNetworkInfo?.ipv4Public ?? this.cachedNetworkInfo?.ipv4,\n ipv6: this.cachedNetworkInfo?.ipv6Public ?? this.cachedNetworkInfo?.ipv6,\n };\n }\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DnsProvider, DnsRecordTypeValue } from '../dns/DnsProvider';
|
|
2
2
|
export interface EdgeNodeDnsCoordinatorOptions {
|
|
3
3
|
provider: DnsProvider;
|
|
4
|
-
/** 顶级域名,例如 `
|
|
4
|
+
/** 顶级域名,例如 `undefineds.site`。 */
|
|
5
5
|
rootDomain?: string | null;
|
|
6
6
|
/**
|
|
7
7
|
* 默认记录类型,当目标地址无法识别时回退使用。
|
|
@@ -10,11 +10,6 @@ export interface EdgeNodeDnsCoordinatorOptions {
|
|
|
10
10
|
defaultRecordType?: DnsRecordTypeValue;
|
|
11
11
|
/** TTL 秒数,缺省按供应商默认。 */
|
|
12
12
|
ttl?: number | string | null;
|
|
13
|
-
/**
|
|
14
|
-
* Cluster 的公网 IP 地址,用于 proxy 模式的 DNS 指向。
|
|
15
|
-
* 如果未设置,proxy 模式节点将跳过 DNS 同步。
|
|
16
|
-
*/
|
|
17
|
-
clusterIp?: string | null;
|
|
18
13
|
}
|
|
19
14
|
export declare class EdgeNodeDnsCoordinator {
|
|
20
15
|
private readonly logger;
|
|
@@ -22,7 +17,6 @@ export declare class EdgeNodeDnsCoordinator {
|
|
|
22
17
|
private readonly rootDomain?;
|
|
23
18
|
private readonly defaultRecordType;
|
|
24
19
|
private readonly ttl?;
|
|
25
|
-
private readonly clusterIp?;
|
|
26
20
|
private readonly enabled;
|
|
27
21
|
constructor(options: EdgeNodeDnsCoordinatorOptions);
|
|
28
22
|
synchronize(nodeId: string, metadata: Record<string, unknown>): Promise<void>;
|
|
@@ -9,7 +9,6 @@ class EdgeNodeDnsCoordinator {
|
|
|
9
9
|
this.rootDomain = this.normalizeRootDomain(options.rootDomain);
|
|
10
10
|
this.defaultRecordType = options.defaultRecordType ?? 'A';
|
|
11
11
|
this.ttl = this.normalizeTtl(options.ttl);
|
|
12
|
-
this.clusterIp = this.extractString(options.clusterIp);
|
|
13
12
|
this.enabled = Boolean(this.rootDomain);
|
|
14
13
|
}
|
|
15
14
|
async synchronize(nodeId, metadata) {
|
|
@@ -17,55 +16,47 @@ class EdgeNodeDnsCoordinator {
|
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
18
|
const hints = this.extractDnsHints(metadata);
|
|
20
|
-
// Extract subdomain and access mode
|
|
21
19
|
const subdomain = this.extractString(metadata.subdomain) ?? hints?.subdomain;
|
|
22
20
|
if (!subdomain) {
|
|
23
21
|
this.logger.debug(`Node ${nodeId} 未提供 subdomain,跳过 DNS 同步。`);
|
|
24
22
|
return;
|
|
25
23
|
}
|
|
26
|
-
|
|
27
|
-
const normalizedAccessMode = accessMode?.trim().toLowerCase();
|
|
28
|
-
// Determine DNS target based on access mode
|
|
24
|
+
// 用节点上报的地址(公网 IP 或隧道入口,由节点自行决定)
|
|
29
25
|
let target;
|
|
30
26
|
let recordType;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (ipv6 && this.isIpv6(ipv6)) {
|
|
40
|
-
target = ipv6;
|
|
41
|
-
recordType = 'AAAA';
|
|
42
|
-
this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
target = publicIp ?? ipv4 ?? publicAddress;
|
|
46
|
-
}
|
|
47
|
-
if (!target && hints?.target) {
|
|
48
|
-
target = hints.target;
|
|
49
|
-
}
|
|
50
|
-
if (!target) {
|
|
51
|
-
this.logger.warn(`Node ${nodeId} (direct mode) 未提供公网 IP,跳过 DNS 同步。`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
27
|
+
const ipv6 = this.extractString(metadata.ipv6);
|
|
28
|
+
const ipv4 = this.extractString(metadata.ipv4);
|
|
29
|
+
const publicAddress = this.extractString(metadata.publicAddress);
|
|
30
|
+
// IPv6 优先
|
|
31
|
+
if (ipv6 && this.isIpv6(ipv6)) {
|
|
32
|
+
target = ipv6;
|
|
33
|
+
recordType = 'AAAA';
|
|
34
|
+
this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);
|
|
54
35
|
}
|
|
55
|
-
else
|
|
56
|
-
|
|
57
|
-
if (!this.clusterIp) {
|
|
58
|
-
this.logger.debug(`Cluster IP 未配置,跳过 proxy 模式节点 ${nodeId} 的 DNS 同步。`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
target = this.clusterIp;
|
|
36
|
+
else {
|
|
37
|
+
target = ipv4 ?? publicAddress;
|
|
62
38
|
}
|
|
63
|
-
|
|
64
|
-
// Fallback: 使用旧的逻辑从 metadata 提取
|
|
39
|
+
if (!target && hints?.target) {
|
|
65
40
|
target = hints.target;
|
|
66
41
|
}
|
|
67
|
-
|
|
68
|
-
this.logger.debug(`Node ${nodeId}
|
|
42
|
+
if (!target) {
|
|
43
|
+
this.logger.debug(`Node ${nodeId} 未提供可用地址,跳过 DNS 同步。`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// 健康检查未通过时删除 DNS 记录
|
|
47
|
+
const connectivityStatus = this.extractString(metadata.connectivityStatus);
|
|
48
|
+
if (connectivityStatus === 'unreachable') {
|
|
49
|
+
this.logger.info(`节点 ${nodeId} 不可达,删除 DNS 记录 ${subdomain}.${this.rootDomain}`);
|
|
50
|
+
try {
|
|
51
|
+
await this.provider.deleteRecord({
|
|
52
|
+
domain: this.rootDomain,
|
|
53
|
+
subdomain,
|
|
54
|
+
type: recordType ?? this.defaultRecordType,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.logger.error(`删除节点 ${nodeId} DNS 记录失败: ${error.message}`);
|
|
59
|
+
}
|
|
69
60
|
return;
|
|
70
61
|
}
|
|
71
62
|
const type = recordType ?? this.detectRecordType(target) ?? this.defaultRecordType;
|
|
@@ -82,8 +73,7 @@ class EdgeNodeDnsCoordinator {
|
|
|
82
73
|
value,
|
|
83
74
|
ttl: this.ttl,
|
|
84
75
|
});
|
|
85
|
-
|
|
86
|
-
this.logger.info(`已同步节点 ${nodeId} (${loggedMode}) 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);
|
|
76
|
+
this.logger.info(`已同步节点 ${nodeId} 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);
|
|
87
77
|
}
|
|
88
78
|
catch (error) {
|
|
89
79
|
this.logger.error(`同步节点 ${nodeId} DNS 记录失败: ${error.message}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeDnsCoordinator.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeDnsCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport type { DnsProvider, DnsRecordTypeValue } from '../dns/DnsProvider';\n\nexport interface EdgeNodeDnsCoordinatorOptions {\n provider: DnsProvider;\n /** 顶级域名,例如 `xpod.example`。 */\n rootDomain?: string | null;\n /**\n * 默认记录类型,当目标地址无法识别时回退使用。\n * 一般为 `A`(IPv4)或 `AAAA`(IPv6)。\n */\n defaultRecordType?: DnsRecordTypeValue;\n /** TTL 秒数,缺省按供应商默认。 */\n ttl?: number | string | null;\n /**\n * Cluster 的公网 IP 地址,用于 proxy 模式的 DNS 指向。\n * 如果未设置,proxy 模式节点将跳过 DNS 同步。\n */\n clusterIp?: string | null;\n}\n\nexport class EdgeNodeDnsCoordinator {\n private readonly logger = getLoggerFor(this);\n private readonly provider: DnsProvider;\n private readonly rootDomain?: string;\n private readonly defaultRecordType: DnsRecordTypeValue;\n private readonly ttl?: number;\n private readonly clusterIp?: string;\n private readonly enabled: boolean;\n\n public constructor(options: EdgeNodeDnsCoordinatorOptions) {\n this.provider = options.provider;\n this.rootDomain = this.normalizeRootDomain(options.rootDomain);\n this.defaultRecordType = options.defaultRecordType ?? 'A';\n this.ttl = this.normalizeTtl(options.ttl);\n this.clusterIp = this.extractString(options.clusterIp);\n this.enabled = Boolean(this.rootDomain);\n }\n\n public async synchronize(nodeId: string, metadata: Record<string, unknown>): Promise<void> {\n if (!this.enabled) {\n return;\n }\n \n const hints = this.extractDnsHints(metadata);\n\n // Extract subdomain and access mode\n const subdomain = this.extractString(metadata.subdomain) ?? hints?.subdomain;\n if (!subdomain) {\n this.logger.debug(`Node ${nodeId} 未提供 subdomain,跳过 DNS 同步。`);\n return;\n }\n \n const accessMode = this.extractString(metadata.accessMode);\n const normalizedAccessMode = accessMode?.trim().toLowerCase();\n \n // Determine DNS target based on access mode\n let target: string | undefined;\n let recordType: DnsRecordTypeValue | undefined;\n \n if (normalizedAccessMode === 'direct') {\n // Direct mode: DNS 指向节点公网 IP\n // 优先使用 IPv6(如果有),因为可以避免 NAT 问题\n const ipv6 = this.extractString(metadata.ipv6);\n const ipv4 = this.extractString(metadata.ipv4);\n const publicIp = this.extractString(metadata.publicIp);\n const publicAddress = this.extractString(metadata.publicAddress);\n \n // IPv6 优先策略:如果有公网 IPv6,优先使用\n if (ipv6 && this.isIpv6(ipv6)) {\n target = ipv6;\n recordType = 'AAAA';\n this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);\n } else {\n target = publicIp ?? ipv4 ?? publicAddress;\n }\n \n if (!target && hints?.target) {\n target = hints.target;\n }\n\n if (!target) {\n this.logger.warn(`Node ${nodeId} (direct mode) 未提供公网 IP,跳过 DNS 同步。`);\n return;\n }\n } else if (normalizedAccessMode === 'proxy') {\n // Proxy mode: DNS 指向 Cluster IP\n if (!this.clusterIp) {\n this.logger.debug(`Cluster IP 未配置,跳过 proxy 模式节点 ${nodeId} 的 DNS 同步。`);\n return;\n }\n target = this.clusterIp;\n } else if (hints?.target) {\n // Fallback: 使用旧的逻辑从 metadata 提取\n target = hints.target;\n } else {\n this.logger.debug(`Node ${nodeId} 未提供 accessMode/dns hints,跳过同步。`);\n return;\n }\n \n const type = recordType ?? this.detectRecordType(target) ?? this.defaultRecordType;\n const value = this.normalizeRecordValue(target, type);\n\n if (!value) {\n this.logger.warn(`Edge node ${nodeId} DNS 目标解析失败,跳过同步。`);\n return;\n }\n\n try {\n await this.provider.upsertRecord({\n domain: this.rootDomain!,\n subdomain,\n type,\n value,\n ttl: this.ttl,\n });\n const loggedMode = normalizedAccessMode ?? 'unknown';\n this.logger.info(`已同步节点 ${nodeId} (${loggedMode}) 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);\n } catch (error: unknown) {\n this.logger.error(`同步节点 ${nodeId} DNS 记录失败: ${(error as Error).message}`);\n throw error;\n }\n }\n\n private extractDnsHints(metadata: Record<string, unknown>): { subdomain: string; target: string } | undefined {\n const dns = metadata?.dns;\n if (!dns || typeof dns !== 'object') {\n return undefined;\n }\n const subdomain = this.extractString((dns as Record<string, unknown>).subdomain);\n if (!subdomain) {\n return undefined;\n }\n\n const target = this.extractString((dns as Record<string, unknown>).target)\n ?? this.extractString(metadata.publicAddress)\n ?? this.extractString(metadata.baseUrl);\n\n if (!target) {\n return undefined;\n }\n return { subdomain, target };\n }\n\n private extractString(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private detectRecordType(target: string): DnsRecordTypeValue | undefined {\n const host = this.extractHost(target) ?? target;\n if (this.isIpv4(host)) {\n return 'A';\n }\n if (this.isIpv6(host)) {\n return 'AAAA';\n }\n if (host.includes('.')) {\n return 'CNAME';\n }\n return undefined;\n }\n\n private normalizeRecordValue(target: string, type: DnsRecordTypeValue): string | undefined {\n const host = this.extractHost(target) ?? target;\n if (type === 'A' && this.isIpv4(host)) {\n return host;\n }\n if (type === 'AAAA' && this.isIpv6(host)) {\n return host;\n }\n if (type === 'CNAME') {\n return host.endsWith('.') ? host : `${host}.`;\n }\n if (type === 'TXT') {\n return host;\n }\n return undefined;\n }\n\n private extractHost(input: string): string | undefined {\n try {\n const url = new URL(input);\n return url.hostname;\n } catch {\n return input;\n }\n }\n\n private isIpv4(value: string): boolean {\n const parts = value.split('.');\n if (parts.length !== 4) {\n return false;\n }\n return parts.every((part) => {\n if (!/^[0-9]{1,3}$/.test(part)) {\n return false;\n }\n const num = Number(part);\n return num >= 0 && num <= 255;\n });\n }\n\n private isIpv6(value: string): boolean {\n return /^[0-9a-fA-F:]+$/.test(value) && value.includes(':');\n }\n\n private normalizeRootDomain(value?: string | null): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n let trimmed = value.trim();\n if (trimmed.includes('://')) {\n try {\n trimmed = new URL(trimmed).hostname;\n } catch {\n // ignore\n }\n }\n trimmed = trimmed.replace(/\\.$/, '');\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private normalizeTtl(value?: number | string | null): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) {\n return Math.trunc(value);\n }\n if (typeof value === 'string') {\n const parsed = Number(value.trim());\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.trunc(parsed);\n }\n }\n return undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeDnsCoordinator.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeDnsCoordinator.ts"],"names":[],"mappings":";;;AAAA,iEAAqD;AAgBrD,MAAa,sBAAsB;IAQjC,YAAmB,OAAsC;QAPxC,WAAM,GAAG,IAAA,oCAAY,EAAC,IAAI,CAAC,CAAC;QAQ3C,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC/D,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,GAAG,CAAC;QAC1D,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,QAAiC;QACxE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAE7C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,KAAK,EAAE,SAAS,CAAC;QAC7E,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,2BAA2B,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QAED,gCAAgC;QAChC,IAAI,MAA0B,CAAC;QAC/B,IAAI,UAA0C,CAAC;QAE/C,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAEjE,UAAU;QACV,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,IAAI,CAAC;YACd,UAAU,GAAG,MAAM,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,gBAAgB,IAAI,EAAE,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,IAAI,IAAI,aAAa,CAAC;QACjC,CAAC;QAED,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,MAAM,EAAE,CAAC;YAC7B,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QACxB,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,qBAAqB,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QAED,oBAAoB;QACpB,MAAM,kBAAkB,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAC3E,IAAI,kBAAkB,KAAK,aAAa,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,MAAM,kBAAkB,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YAC/E,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;oBAC/B,MAAM,EAAE,IAAI,CAAC,UAAW;oBACxB,SAAS;oBACT,IAAI,EAAE,UAAU,IAAI,IAAI,CAAC,iBAAiB;iBAC3C,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,cAAe,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5E,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,UAAU,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,iBAAiB,CAAC;QACnF,MAAM,KAAK,GAAG,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAEtD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,MAAM,mBAAmB,CAAC,CAAC;YACzD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAC/B,MAAM,EAAE,IAAI,CAAC,UAAW;gBACxB,SAAS;gBACT,IAAI;gBACJ,KAAK;gBACL,GAAG,EAAE,IAAI,CAAC,GAAG;aACd,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,WAAW,SAAS,IAAI,IAAI,CAAC,UAAU,OAAO,KAAK,EAAE,CAAC,CAAC;QACzF,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,cAAe,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1E,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,QAAiC;QACvD,MAAM,GAAG,GAAG,QAAQ,EAAE,GAAG,CAAC;QAC1B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YACpC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAE,GAA+B,CAAC,SAAS,CAAC,CAAC;QACjF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAE,GAA+B,CAAC,MAAM,CAAC;eACrE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC;eAC1C,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAE1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC/B,CAAC;IAEO,aAAa,CAAC,KAAc;QAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IAEO,gBAAgB,CAAC,MAAc;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC;QAChD,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,oBAAoB,CAAC,MAAc,EAAE,IAAwB;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC;QAChD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC;QAChD,CAAC;QACD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,WAAW,CAAC,KAAa;QAC/B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;YAC3B,OAAO,GAAG,CAAC,QAAQ,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,KAAa;QAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACzB,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,MAAM,CAAC,KAAa;QAC1B,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC9D,CAAC;IAEO,mBAAmB,CAAC,KAAqB;QAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACrC,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IAEO,YAAY,CAAC,KAA8B;QACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACpC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAhND,wDAgNC","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\nimport type { DnsProvider, DnsRecordTypeValue } from '../dns/DnsProvider';\n\nexport interface EdgeNodeDnsCoordinatorOptions {\n provider: DnsProvider;\n /** 顶级域名,例如 `undefineds.site`。 */\n rootDomain?: string | null;\n /**\n * 默认记录类型,当目标地址无法识别时回退使用。\n * 一般为 `A`(IPv4)或 `AAAA`(IPv6)。\n */\n defaultRecordType?: DnsRecordTypeValue;\n /** TTL 秒数,缺省按供应商默认。 */\n ttl?: number | string | null;\n}\n\nexport class EdgeNodeDnsCoordinator {\n private readonly logger = getLoggerFor(this);\n private readonly provider: DnsProvider;\n private readonly rootDomain?: string;\n private readonly defaultRecordType: DnsRecordTypeValue;\n private readonly ttl?: number;\n private readonly enabled: boolean;\n\n public constructor(options: EdgeNodeDnsCoordinatorOptions) {\n this.provider = options.provider;\n this.rootDomain = this.normalizeRootDomain(options.rootDomain);\n this.defaultRecordType = options.defaultRecordType ?? 'A';\n this.ttl = this.normalizeTtl(options.ttl);\n this.enabled = Boolean(this.rootDomain);\n }\n\n public async synchronize(nodeId: string, metadata: Record<string, unknown>): Promise<void> {\n if (!this.enabled) {\n return;\n }\n\n const hints = this.extractDnsHints(metadata);\n\n const subdomain = this.extractString(metadata.subdomain) ?? hints?.subdomain;\n if (!subdomain) {\n this.logger.debug(`Node ${nodeId} 未提供 subdomain,跳过 DNS 同步。`);\n return;\n }\n\n // 用节点上报的地址(公网 IP 或隧道入口,由节点自行决定)\n let target: string | undefined;\n let recordType: DnsRecordTypeValue | undefined;\n\n const ipv6 = this.extractString(metadata.ipv6);\n const ipv4 = this.extractString(metadata.ipv4);\n const publicAddress = this.extractString(metadata.publicAddress);\n\n // IPv6 优先\n if (ipv6 && this.isIpv6(ipv6)) {\n target = ipv6;\n recordType = 'AAAA';\n this.logger.debug(`Node ${nodeId} 使用 IPv6 地址: ${ipv6}`);\n } else {\n target = ipv4 ?? publicAddress;\n }\n\n if (!target && hints?.target) {\n target = hints.target;\n }\n\n if (!target) {\n this.logger.debug(`Node ${nodeId} 未提供可用地址,跳过 DNS 同步。`);\n return;\n }\n\n // 健康检查未通过时删除 DNS 记录\n const connectivityStatus = this.extractString(metadata.connectivityStatus);\n if (connectivityStatus === 'unreachable') {\n this.logger.info(`节点 ${nodeId} 不可达,删除 DNS 记录 ${subdomain}.${this.rootDomain}`);\n try {\n await this.provider.deleteRecord({\n domain: this.rootDomain!,\n subdomain,\n type: recordType ?? this.defaultRecordType,\n });\n } catch (error: unknown) {\n this.logger.error(`删除节点 ${nodeId} DNS 记录失败: ${(error as Error).message}`);\n }\n return;\n }\n\n const type = recordType ?? this.detectRecordType(target) ?? this.defaultRecordType;\n const value = this.normalizeRecordValue(target, type);\n\n if (!value) {\n this.logger.warn(`Edge node ${nodeId} DNS 目标解析失败,跳过同步。`);\n return;\n }\n\n try {\n await this.provider.upsertRecord({\n domain: this.rootDomain!,\n subdomain,\n type,\n value,\n ttl: this.ttl,\n });\n this.logger.info(`已同步节点 ${nodeId} 的 DNS: ${subdomain}.${this.rootDomain} -> ${value}`);\n } catch (error: unknown) {\n this.logger.error(`同步节点 ${nodeId} DNS 记录失败: ${(error as Error).message}`);\n throw error;\n }\n }\n\n private extractDnsHints(metadata: Record<string, unknown>): { subdomain: string; target: string } | undefined {\n const dns = metadata?.dns;\n if (!dns || typeof dns !== 'object') {\n return undefined;\n }\n const subdomain = this.extractString((dns as Record<string, unknown>).subdomain);\n if (!subdomain) {\n return undefined;\n }\n\n const target = this.extractString((dns as Record<string, unknown>).target)\n ?? this.extractString(metadata.publicAddress)\n ?? this.extractString(metadata.baseUrl);\n\n if (!target) {\n return undefined;\n }\n return { subdomain, target };\n }\n\n private extractString(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private detectRecordType(target: string): DnsRecordTypeValue | undefined {\n const host = this.extractHost(target) ?? target;\n if (this.isIpv4(host)) {\n return 'A';\n }\n if (this.isIpv6(host)) {\n return 'AAAA';\n }\n if (host.includes('.')) {\n return 'CNAME';\n }\n return undefined;\n }\n\n private normalizeRecordValue(target: string, type: DnsRecordTypeValue): string | undefined {\n const host = this.extractHost(target) ?? target;\n if (type === 'A' && this.isIpv4(host)) {\n return host;\n }\n if (type === 'AAAA' && this.isIpv6(host)) {\n return host;\n }\n if (type === 'CNAME') {\n return host.endsWith('.') ? host : `${host}.`;\n }\n if (type === 'TXT') {\n return host;\n }\n return undefined;\n }\n\n private extractHost(input: string): string | undefined {\n try {\n const url = new URL(input);\n return url.hostname;\n } catch {\n return input;\n }\n }\n\n private isIpv4(value: string): boolean {\n const parts = value.split('.');\n if (parts.length !== 4) {\n return false;\n }\n return parts.every((part) => {\n if (!/^[0-9]{1,3}$/.test(part)) {\n return false;\n }\n const num = Number(part);\n return num >= 0 && num <= 255;\n });\n }\n\n private isIpv6(value: string): boolean {\n return /^[0-9a-fA-F:]+$/.test(value) && value.includes(':');\n }\n\n private normalizeRootDomain(value?: string | null): string | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n let trimmed = value.trim();\n if (trimmed.includes('://')) {\n try {\n trimmed = new URL(trimmed).hostname;\n } catch {\n // ignore\n }\n }\n trimmed = trimmed.replace(/\\.$/, '');\n return trimmed.length > 0 ? trimmed : undefined;\n }\n\n private normalizeTtl(value?: number | string | null): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value) && value > 0) {\n return Math.trunc(value);\n }\n if (typeof value === 'string') {\n const parsed = Number(value.trim());\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.trunc(parsed);\n }\n }\n return undefined;\n }\n}\n"]}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}
|
|
28
28
|
]
|
|
29
29
|
},
|
|
30
|
-
"comment": "顶级域名,例如 `
|
|
30
|
+
"comment": "顶级域名,例如 `undefineds.site`。"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_defaultRecordType",
|
|
@@ -78,22 +78,6 @@
|
|
|
78
78
|
]
|
|
79
79
|
},
|
|
80
80
|
"comment": "TTL 秒数,缺省按供应商默认。"
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_clusterIp",
|
|
84
|
-
"range": {
|
|
85
|
-
"@type": "ParameterRangeUnion",
|
|
86
|
-
"parameterRangeElements": [
|
|
87
|
-
"xsd:string",
|
|
88
|
-
{
|
|
89
|
-
"@type": "ParameterRangeWildcard"
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"@type": "ParameterRangeUndefined"
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
},
|
|
96
|
-
"comment": "Cluster 的公网 IP 地址,用于 proxy 模式的 DNS 指向。 如果未设置,proxy 模式节点将跳过 DNS 同步。"
|
|
97
81
|
}
|
|
98
82
|
],
|
|
99
83
|
"memberFields": [
|
|
@@ -117,10 +101,6 @@
|
|
|
117
101
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator__member_ttl",
|
|
118
102
|
"memberFieldName": "ttl"
|
|
119
103
|
},
|
|
120
|
-
{
|
|
121
|
-
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator__member_clusterIp",
|
|
122
|
-
"memberFieldName": "clusterIp"
|
|
123
|
-
},
|
|
124
104
|
{
|
|
125
105
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator__member_enabled",
|
|
126
106
|
"memberFieldName": "enabled"
|
|
@@ -197,12 +177,6 @@
|
|
|
197
177
|
"value": {
|
|
198
178
|
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_ttl"
|
|
199
179
|
}
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"keyRaw": "clusterIp",
|
|
203
|
-
"value": {
|
|
204
|
-
"@id": "undefineds:dist/edge/EdgeNodeDnsCoordinator.jsonld#EdgeNodeDnsCoordinator_options_clusterIp"
|
|
205
|
-
}
|
|
206
180
|
}
|
|
207
181
|
]
|
|
208
182
|
}
|
|
@@ -38,11 +38,11 @@ class EdgeNodeModeDetector {
|
|
|
38
38
|
const supportsDirect = supportedModes.has('direct');
|
|
39
39
|
const supportsProxy = supportedModes.has('proxy');
|
|
40
40
|
// Prefer direct if supported and has public IP (IPv6 or IPv4)
|
|
41
|
-
const hasPublicIp = Boolean(nodeInfo.
|
|
41
|
+
const hasPublicIp = Boolean(nodeInfo.ipv4 || nodeInfo.publicIpv6);
|
|
42
42
|
let connectivityTest;
|
|
43
43
|
if (supportsDirect && hasPublicIp) {
|
|
44
44
|
// 优先测试 IPv6 连通性
|
|
45
|
-
const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.
|
|
45
|
+
const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.ipv4;
|
|
46
46
|
const port = nodeInfo.publicPort ?? 443;
|
|
47
47
|
connectivityTest = await this.testDirectConnectivity(ipToTest, port);
|
|
48
48
|
if (connectivityTest.success) {
|
|
@@ -55,11 +55,11 @@ class EdgeNodeModeDetector {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
// 如果 IPv6 测试失败且有 IPv4,尝试 IPv4
|
|
58
|
-
if (nodeInfo.publicIpv6 && nodeInfo.
|
|
58
|
+
if (nodeInfo.publicIpv6 && nodeInfo.ipv4) {
|
|
59
59
|
this.logger.debug(`IPv6 connectivity failed for ${nodeInfo.nodeId}, trying IPv4...`);
|
|
60
|
-
connectivityTest = await this.testDirectConnectivity(nodeInfo.
|
|
60
|
+
connectivityTest = await this.testDirectConnectivity(nodeInfo.ipv4, port);
|
|
61
61
|
if (connectivityTest.success) {
|
|
62
|
-
this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.
|
|
62
|
+
this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.ipv4}:${port}`);
|
|
63
63
|
return {
|
|
64
64
|
accessMode: 'direct',
|
|
65
65
|
reason: 'Direct connectivity test passed (IPv4 fallback)',
|
|
@@ -95,10 +95,8 @@ class EdgeNodeModeDetector {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
generateSubdomain(nodeId) {
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const sanitized = nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
|
|
101
|
-
return `${sanitized}.${this.baseDomain}`;
|
|
98
|
+
// 只返回前缀,完整 FQDN 由 DnsCoordinator 的 rootDomain 拼接
|
|
99
|
+
return nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();
|
|
102
100
|
}
|
|
103
101
|
async testDirectConnectivity(ip, port) {
|
|
104
102
|
let lastResult = await this.singleConnectivityAttempt(ip, port);
|
|
@@ -161,10 +159,10 @@ class EdgeNodeModeDetector {
|
|
|
161
159
|
async recheckMode(currentMode, nodeInfo) {
|
|
162
160
|
// Only recheck if currently in proxy mode and public IP is available
|
|
163
161
|
const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);
|
|
164
|
-
if (currentMode !== 'proxy' || !nodeInfo.
|
|
162
|
+
if (currentMode !== 'proxy' || !nodeInfo.ipv4 || !supportedModes.has('direct')) {
|
|
165
163
|
return null;
|
|
166
164
|
}
|
|
167
|
-
const connectivityTest = await this.testDirectConnectivity(nodeInfo.
|
|
165
|
+
const connectivityTest = await this.testDirectConnectivity(nodeInfo.ipv4, nodeInfo.publicPort ?? 443);
|
|
168
166
|
if (connectivityTest.success) {
|
|
169
167
|
const subdomain = this.generateSubdomain(nodeInfo.nodeId);
|
|
170
168
|
this.logger.info(`Node ${nodeInfo.nodeId} connectivity restored, switching to direct mode`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdgeNodeModeDetector.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeModeDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\n\nexport interface NodeRegistrationInfo {\n nodeId: string;\n publicIp?: string;\n publicIpv6?: string;\n publicPort?: number;\n capabilities: NodeCapabilities;\n}\n\nexport interface NodeCapabilities {\n solidProtocolVersion?: string;\n storageBackends?: string[];\n authMethods?: string[];\n maxBandwidth?: number;\n supportedModes?: ('direct' | 'proxy')[];\n location?: {\n country?: string;\n region?: string;\n coordinates?: { lat: number; lon: number };\n };\n}\n\nexport interface ModeDetectionResult {\n accessMode: 'direct' | 'proxy';\n reason: string;\n subdomain: string;\n connectivityTest?: {\n success: boolean;\n latency?: number;\n error?: string;\n };\n}\n\nexport interface EdgeNodeModeDetectorOptions {\n baseDomain: string;\n connectivityTimeoutMs?: number;\n maxDirectModeAttempts?: number;\n}\n\nexport class EdgeNodeModeDetector {\n private readonly logger = getLoggerFor(this);\n private readonly baseDomain: string;\n private readonly connectivityTimeoutMs: number;\n private readonly maxDirectModeAttempts: number;\n\n public constructor(options: EdgeNodeModeDetectorOptions) {\n this.baseDomain = options.baseDomain;\n this.connectivityTimeoutMs = options.connectivityTimeoutMs ?? 3000;\n this.maxDirectModeAttempts = Math.max(1, options.maxDirectModeAttempts ?? 1);\n }\n\n public async detectMode(nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult> {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n const supportsDirect = supportedModes.has('direct');\n const supportsProxy = supportedModes.has('proxy');\n\n // Prefer direct if supported and has public IP (IPv6 or IPv4)\n const hasPublicIp = Boolean(nodeInfo.publicIp || nodeInfo.publicIpv6);\n let connectivityTest: { success: boolean; latencyMs?: number; error?: string } | undefined;\n\n if (supportsDirect && hasPublicIp) {\n // 优先测试 IPv6 连通性\n const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.publicIp!;\n const port = nodeInfo.publicPort ?? 443;\n \n connectivityTest = await this.testDirectConnectivity(ipToTest, port);\n\n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable at ${ipToTest}:${port}`);\n return {\n accessMode: 'direct',\n reason: `Direct connectivity test passed (${nodeInfo.publicIpv6 ? 'IPv6' : 'IPv4'})`,\n subdomain,\n connectivityTest,\n };\n }\n \n // 如果 IPv6 测试失败且有 IPv4,尝试 IPv4\n if (nodeInfo.publicIpv6 && nodeInfo.publicIp) {\n this.logger.debug(`IPv6 connectivity failed for ${nodeInfo.nodeId}, trying IPv4...`);\n connectivityTest = await this.testDirectConnectivity(nodeInfo.publicIp, port);\n \n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.publicIp}:${port}`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity test passed (IPv4 fallback)',\n subdomain,\n connectivityTest,\n };\n }\n }\n\n this.logger.info(`Node ${nodeInfo.nodeId} is not directly reachable, will fall back to proxy if available: ${connectivityTest.error}`);\n if (!supportsProxy) {\n return {\n accessMode: 'direct',\n reason: `Direct connectivity failed and proxy not supported: ${connectivityTest.error}`,\n subdomain,\n connectivityTest,\n };\n }\n }\n\n // Direct not available or failed; if proxy supported, use proxy mode\n if (supportsProxy) {\n return {\n accessMode: 'proxy',\n reason: supportsDirect && hasPublicIp ? 'Direct connectivity failed, using proxy' : 'Direct not available, using proxy',\n subdomain,\n connectivityTest,\n };\n }\n\n // Neither direct nor proxy viable\n return {\n accessMode: supportsDirect ? 'direct' : 'proxy',\n reason: supportsDirect ? 'Direct mode only; no proxy configured' : 'Proxy only; no direct available',\n subdomain,\n };\n }\n\n private generateSubdomain(nodeId: string): string {\n // Generate a unique subdomain based on node ID\n // For now, use a simple approach - in production you might want more sophisticated logic\n const sanitized = nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();\n return `${sanitized}.${this.baseDomain}`;\n }\n\n private async testDirectConnectivity(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n let lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success || this.maxDirectModeAttempts === 1) {\n return lastResult;\n }\n\n for (let attempt = 2; attempt <= this.maxDirectModeAttempts; attempt++) {\n lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success) {\n return lastResult;\n }\n }\n return lastResult;\n }\n\n private async singleConnectivityAttempt(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n const net = await import('node:net');\n\n return new Promise((resolve) => {\n const startTime = Date.now();\n const socket = new net.Socket();\n\n const cleanup = (): void => {\n socket.destroy();\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n resolve({\n success: false,\n error: `Connection timeout after ${this.connectivityTimeoutMs}ms`,\n });\n }, this.connectivityTimeoutMs);\n\n socket.on('connect', () => {\n clearTimeout(timeout);\n const latency = Date.now() - startTime;\n cleanup();\n resolve({\n success: true,\n latency,\n });\n });\n\n socket.on('error', (error) => {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error.message,\n });\n });\n\n try {\n socket.connect(port, ip);\n } catch (error) {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error instanceof Error ? error.message : 'Unknown connection error',\n });\n }\n });\n }\n\n public async recheckMode(currentMode: string, nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult | null> {\n // Only recheck if currently in proxy mode and public IP is available\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n if (currentMode !== 'proxy' || !nodeInfo.publicIp || !supportedModes.has('direct')) {\n return null;\n }\n\n const connectivityTest = await this.testDirectConnectivity(\n nodeInfo.publicIp,\n nodeInfo.publicPort ?? 443\n );\n\n if (connectivityTest.success) {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n this.logger.info(`Node ${nodeInfo.nodeId} connectivity restored, switching to direct mode`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity restored',\n subdomain,\n connectivityTest,\n };\n }\n\n return null;\n }\n\n private extractSupportedModes(capabilities: NodeCapabilities): Set<string> {\n const modes = new Set<string>();\n const rawModes = capabilities.supportedModes ?? ['direct', 'proxy'];\n \n for (const mode of rawModes) {\n modes.add(mode);\n }\n \n if (modes.size === 0) {\n modes.add('direct');\n modes.add('proxy');\n }\n return modes;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"EdgeNodeModeDetector.js","sourceRoot":"","sources":["../../src/edge/EdgeNodeModeDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLoggerFor } from 'global-logger-factory';\n\nexport interface NodeRegistrationInfo {\n nodeId: string;\n ipv4?: string;\n publicIpv6?: string;\n publicPort?: number;\n capabilities: NodeCapabilities;\n}\n\nexport interface NodeCapabilities {\n solidProtocolVersion?: string;\n storageBackends?: string[];\n authMethods?: string[];\n maxBandwidth?: number;\n supportedModes?: ('direct' | 'proxy')[];\n location?: {\n country?: string;\n region?: string;\n coordinates?: { lat: number; lon: number };\n };\n}\n\nexport interface ModeDetectionResult {\n accessMode: 'direct' | 'proxy';\n reason: string;\n subdomain: string;\n connectivityTest?: {\n success: boolean;\n latency?: number;\n error?: string;\n };\n}\n\nexport interface EdgeNodeModeDetectorOptions {\n baseDomain: string;\n connectivityTimeoutMs?: number;\n maxDirectModeAttempts?: number;\n}\n\nexport class EdgeNodeModeDetector {\n private readonly logger = getLoggerFor(this);\n private readonly baseDomain: string;\n private readonly connectivityTimeoutMs: number;\n private readonly maxDirectModeAttempts: number;\n\n public constructor(options: EdgeNodeModeDetectorOptions) {\n this.baseDomain = options.baseDomain;\n this.connectivityTimeoutMs = options.connectivityTimeoutMs ?? 3000;\n this.maxDirectModeAttempts = Math.max(1, options.maxDirectModeAttempts ?? 1);\n }\n\n public async detectMode(nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult> {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n const supportsDirect = supportedModes.has('direct');\n const supportsProxy = supportedModes.has('proxy');\n\n // Prefer direct if supported and has public IP (IPv6 or IPv4)\n const hasPublicIp = Boolean(nodeInfo.ipv4 || nodeInfo.publicIpv6);\n let connectivityTest: { success: boolean; latencyMs?: number; error?: string } | undefined;\n\n if (supportsDirect && hasPublicIp) {\n // 优先测试 IPv6 连通性\n const ipToTest = nodeInfo.publicIpv6 ?? nodeInfo.ipv4!;\n const port = nodeInfo.publicPort ?? 443;\n\n connectivityTest = await this.testDirectConnectivity(ipToTest, port);\n\n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable at ${ipToTest}:${port}`);\n return {\n accessMode: 'direct',\n reason: `Direct connectivity test passed (${nodeInfo.publicIpv6 ? 'IPv6' : 'IPv4'})`,\n subdomain,\n connectivityTest,\n };\n }\n\n // 如果 IPv6 测试失败且有 IPv4,尝试 IPv4\n if (nodeInfo.publicIpv6 && nodeInfo.ipv4) {\n this.logger.debug(`IPv6 connectivity failed for ${nodeInfo.nodeId}, trying IPv4...`);\n connectivityTest = await this.testDirectConnectivity(nodeInfo.ipv4, port);\n\n if (connectivityTest.success) {\n this.logger.info(`Node ${nodeInfo.nodeId} is directly reachable via IPv4 at ${nodeInfo.ipv4}:${port}`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity test passed (IPv4 fallback)',\n subdomain,\n connectivityTest,\n };\n }\n }\n\n this.logger.info(`Node ${nodeInfo.nodeId} is not directly reachable, will fall back to proxy if available: ${connectivityTest.error}`);\n if (!supportsProxy) {\n return {\n accessMode: 'direct',\n reason: `Direct connectivity failed and proxy not supported: ${connectivityTest.error}`,\n subdomain,\n connectivityTest,\n };\n }\n }\n\n // Direct not available or failed; if proxy supported, use proxy mode\n if (supportsProxy) {\n return {\n accessMode: 'proxy',\n reason: supportsDirect && hasPublicIp ? 'Direct connectivity failed, using proxy' : 'Direct not available, using proxy',\n subdomain,\n connectivityTest,\n };\n }\n\n // Neither direct nor proxy viable\n return {\n accessMode: supportsDirect ? 'direct' : 'proxy',\n reason: supportsDirect ? 'Direct mode only; no proxy configured' : 'Proxy only; no direct available',\n subdomain,\n };\n }\n\n private generateSubdomain(nodeId: string): string {\n // 只返回前缀,完整 FQDN 由 DnsCoordinator 的 rootDomain 拼接\n return nodeId.replace(/[^a-zA-Z0-9-]/g, '').toLowerCase();\n }\n\n private async testDirectConnectivity(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n let lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success || this.maxDirectModeAttempts === 1) {\n return lastResult;\n }\n\n for (let attempt = 2; attempt <= this.maxDirectModeAttempts; attempt++) {\n lastResult = await this.singleConnectivityAttempt(ip, port);\n if (lastResult.success) {\n return lastResult;\n }\n }\n return lastResult;\n }\n\n private async singleConnectivityAttempt(ip: string, port: number): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n const net = await import('node:net');\n\n return new Promise((resolve) => {\n const startTime = Date.now();\n const socket = new net.Socket();\n\n const cleanup = (): void => {\n socket.destroy();\n };\n\n const timeout = setTimeout(() => {\n cleanup();\n resolve({\n success: false,\n error: `Connection timeout after ${this.connectivityTimeoutMs}ms`,\n });\n }, this.connectivityTimeoutMs);\n\n socket.on('connect', () => {\n clearTimeout(timeout);\n const latency = Date.now() - startTime;\n cleanup();\n resolve({\n success: true,\n latency,\n });\n });\n\n socket.on('error', (error) => {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error.message,\n });\n });\n\n try {\n socket.connect(port, ip);\n } catch (error) {\n clearTimeout(timeout);\n cleanup();\n resolve({\n success: false,\n error: error instanceof Error ? error.message : 'Unknown connection error',\n });\n }\n });\n }\n\n public async recheckMode(currentMode: string, nodeInfo: NodeRegistrationInfo): Promise<ModeDetectionResult | null> {\n // Only recheck if currently in proxy mode and public IP is available\n const supportedModes = this.extractSupportedModes(nodeInfo.capabilities);\n if (currentMode !== 'proxy' || !nodeInfo.ipv4 || !supportedModes.has('direct')) {\n return null;\n }\n\n const connectivityTest = await this.testDirectConnectivity(\n nodeInfo.ipv4,\n nodeInfo.publicPort ?? 443\n );\n\n if (connectivityTest.success) {\n const subdomain = this.generateSubdomain(nodeInfo.nodeId);\n this.logger.info(`Node ${nodeInfo.nodeId} connectivity restored, switching to direct mode`);\n return {\n accessMode: 'direct',\n reason: 'Direct connectivity restored',\n subdomain,\n connectivityTest,\n };\n }\n\n return null;\n }\n\n private extractSupportedModes(capabilities: NodeCapabilities): Set<string> {\n const modes = new Set<string>();\n const rawModes = capabilities.supportedModes ?? ['direct', 'proxy'];\n \n for (const mode of rawModes) {\n modes.add(mode);\n }\n \n if (modes.size === 0) {\n modes.add('direct');\n modes.add('proxy');\n }\n return modes;\n }\n}\n"]}
|
|
@@ -106,7 +106,7 @@ class ClusterIngressRouter extends community_server_1.HttpHandler {
|
|
|
106
106
|
throw new community_server_2.InternalServerError(`Node ${nodeId} connectivity info not found.`);
|
|
107
107
|
}
|
|
108
108
|
const mode = this.normalizeMode(nodeInfo.accessMode);
|
|
109
|
-
if (mode === 'direct' && nodeInfo.
|
|
109
|
+
if (mode === 'direct' && nodeInfo.ipv4) {
|
|
110
110
|
await this.handleDirectModeRedirect(response, nodeInfo, url);
|
|
111
111
|
}
|
|
112
112
|
else if (mode === 'proxy') {
|
|
@@ -128,13 +128,13 @@ class ClusterIngressRouter extends community_server_1.HttpHandler {
|
|
|
128
128
|
*/
|
|
129
129
|
async handleDirectModeRedirect(response, nodeInfo, url) {
|
|
130
130
|
const port = nodeInfo.publicPort && nodeInfo.publicPort !== 443 ? `:${nodeInfo.publicPort}` : '';
|
|
131
|
-
const nodeDirectUrl = `https://${nodeInfo.
|
|
131
|
+
const nodeDirectUrl = `https://${nodeInfo.ipv4}${port}${url.pathname}${url.search}${url.hash}`;
|
|
132
132
|
this.logger.debug(`Redirecting to edge node (direct mode): ${nodeDirectUrl}`);
|
|
133
133
|
response.statusCode = 307;
|
|
134
134
|
response.setHeader('Location', nodeDirectUrl);
|
|
135
135
|
response.setHeader('Cache-Control', 'no-cache');
|
|
136
136
|
response.setHeader('X-Xpod-Direct-Node', nodeInfo.nodeId);
|
|
137
|
-
response.setHeader('X-Xpod-Target-IP', nodeInfo.
|
|
137
|
+
response.setHeader('X-Xpod-Target-IP', nodeInfo.ipv4);
|
|
138
138
|
response.end();
|
|
139
139
|
}
|
|
140
140
|
/**
|