@undefineds.co/xpod 0.2.12 → 0.2.13
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/dist/api/container/cloud.js +12 -1
- package/dist/api/container/cloud.js.map +1 -1
- package/dist/api/container/local.js +4 -0
- package/dist/api/container/local.js.map +1 -1
- package/dist/api/handlers/DdnsHandler.js +49 -14
- package/dist/api/handlers/DdnsHandler.js.map +1 -1
- package/dist/identity/drizzle/DdnsRepository.d.ts +5 -3
- package/dist/identity/drizzle/DdnsRepository.js +10 -5
- package/dist/identity/drizzle/DdnsRepository.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.registerCloudServices = registerCloudServices;
|
|
10
10
|
const awilix_1 = require("awilix");
|
|
11
11
|
const TencentDnsProvider_1 = require("../../dns/tencent/TencentDnsProvider");
|
|
12
|
+
const CloudflareDnsProvider_1 = require("../../dns/cloudflare/CloudflareDnsProvider");
|
|
12
13
|
const CloudflareTunnelProvider_1 = require("../../tunnel/CloudflareTunnelProvider");
|
|
13
14
|
const SubdomainService_1 = require("../../subdomain/SubdomainService");
|
|
14
15
|
const EdgeNodeDnsCoordinator_1 = require("../../edge/EdgeNodeDnsCoordinator");
|
|
@@ -46,7 +47,7 @@ function registerCloudServices(container) {
|
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
48
49
|
const { tencentDnsSecretId, tencentDnsSecretKey, cloudflareAccountId, cloudflareApiToken, } = config.subdomain;
|
|
49
|
-
// DNS Provider (
|
|
50
|
+
// DNS Provider (腾讯云优先,否则回退到 Cloudflare)
|
|
50
51
|
if (tencentDnsSecretId && tencentDnsSecretKey) {
|
|
51
52
|
container.register({
|
|
52
53
|
dnsProvider: (0, awilix_1.asFunction)(() => {
|
|
@@ -58,6 +59,16 @@ function registerCloudServices(container) {
|
|
|
58
59
|
});
|
|
59
60
|
logger.info('Tencent DNS provider registered');
|
|
60
61
|
}
|
|
62
|
+
else if (cloudflareApiToken) {
|
|
63
|
+
container.register({
|
|
64
|
+
dnsProvider: (0, awilix_1.asFunction)(() => {
|
|
65
|
+
return new CloudflareDnsProvider_1.CloudflareDnsProvider({
|
|
66
|
+
apiToken: cloudflareApiToken,
|
|
67
|
+
});
|
|
68
|
+
}).singleton(),
|
|
69
|
+
});
|
|
70
|
+
logger.info('Cloudflare DNS provider registered');
|
|
71
|
+
}
|
|
61
72
|
// Tunnel Provider (Cloudflare)
|
|
62
73
|
if (cloudflareAccountId && cloudflareApiToken) {
|
|
63
74
|
container.register({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.js","sourceRoot":"","sources":["../../../src/api/container/cloud.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;
|
|
1
|
+
{"version":3,"file":"cloud.js","sourceRoot":"","sources":["../../../src/api/container/cloud.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Cloud 模式服务注册\n *\n * Cloud 模式持有 DNS/Tunnel 密钥,直接操作子域名\n * 提供身份服务 (IdP) 和可选的托管存储 (SP)\n */\n\nimport { asFunction, type AwilixContainer } from 'awilix';\nimport type { ApiContainerCradle, ApiContainerConfig } from './types';\n\nimport { TencentDnsProvider } from '../../dns/tencent/TencentDnsProvider';\nimport { CloudflareDnsProvider } from '../../dns/cloudflare/CloudflareDnsProvider';\nimport { CloudflareTunnelProvider } from '../../tunnel/CloudflareTunnelProvider';\nimport { SubdomainService } from '../../subdomain/SubdomainService';\nimport { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';\nimport { WebIdProfileRepository } from '../../identity/drizzle/WebIdProfileRepository';\nimport { DdnsRepository } from '../../identity/drizzle/DdnsRepository';\nimport { getLoggerFor } from 'global-logger-factory';\n\nconst logger = getLoggerFor('CloudServices');\n\n/**\n * 注册 Cloud 模式专属服务\n */\nexport function registerCloudServices(\n container: AwilixContainer<ApiContainerCradle>,\n): void {\n const config = container.resolve('config') as ApiContainerConfig;\n const db = container.resolve('db');\n\n // 获取 baseUrl 用于 WebID Profile\n const baseUrl = process.env.CSS_BASE_URL || `http://localhost:${process.env.CSS_PORT || 3000}`;\n\n // 注册 WebID Profile Repository (始终注册,用于身份服务)\n container.register({\n webIdProfileRepo: asFunction(() => {\n return new WebIdProfileRepository(db, { baseUrl });\n }).singleton(),\n });\n logger.info('WebID Profile repository registered');\n\n // 注册 DDNS Repository (始终注册,用于 DDNS 服务)\n container.register({\n ddnsRepo: asFunction(() => {\n return new DdnsRepository(db);\n }).singleton(),\n });\n logger.info('DDNS repository registered');\n\n // 只有配置了 baseStorageDomain 才注册 DNS/Tunnel 服务\n const baseStorageDomain = config.subdomain?.baseStorageDomain;\n if (!baseStorageDomain) {\n logger.info('Subdomain service disabled (no CSS_BASE_STORAGE_DOMAIN)');\n return;\n }\n\n const {\n tencentDnsSecretId,\n tencentDnsSecretKey,\n cloudflareAccountId,\n cloudflareApiToken,\n } = config.subdomain!;\n\n // DNS Provider (腾讯云优先,否则回退到 Cloudflare)\n if (tencentDnsSecretId && tencentDnsSecretKey) {\n container.register({\n dnsProvider: asFunction(() => {\n return new TencentDnsProvider({\n tokenId: tencentDnsSecretId,\n token: tencentDnsSecretKey,\n });\n }).singleton(),\n });\n logger.info('Tencent DNS provider registered');\n } else if (cloudflareApiToken) {\n container.register({\n dnsProvider: asFunction(() => {\n return new CloudflareDnsProvider({\n apiToken: cloudflareApiToken,\n });\n }).singleton(),\n });\n logger.info('Cloudflare DNS provider registered');\n }\n\n // Tunnel Provider (Cloudflare)\n if (cloudflareAccountId && cloudflareApiToken) {\n container.register({\n tunnelProvider: asFunction(() => {\n return new CloudflareTunnelProvider({\n accountId: cloudflareAccountId,\n apiToken: cloudflareApiToken,\n baseDomain: baseStorageDomain,\n });\n }).singleton(),\n });\n logger.info('Cloudflare Tunnel provider registered');\n }\n\n // Subdomain Service (需要 DNS 和 Tunnel Provider)\n try {\n const dnsProvider = container.resolve('dnsProvider', { allowUnregistered: true });\n const tunnelProvider = container.resolve('tunnelProvider', { allowUnregistered: true });\n\n if (dnsProvider && tunnelProvider) {\n const nodeRepo = container.resolve('nodeRepo');\n container.register({\n subdomainService: asFunction(() => {\n return new SubdomainService({\n baseDomain: baseStorageDomain,\n dnsProvider: dnsProvider as any,\n tunnelProvider: tunnelProvider as any,\n edgeNodeRepo: nodeRepo,\n });\n }).singleton(),\n });\n logger.info(`Subdomain service registered for domain: ${baseStorageDomain}`);\n }\n } catch {\n logger.warn('Subdomain service not registered (missing DNS or Tunnel provider)');\n }\n\n // DNS Coordinator (心跳→DNS 同步,需要 dnsProvider)\n const dnsProvider = container.resolve('dnsProvider', { allowUnregistered: true });\n if (dnsProvider) {\n container.register({\n dnsCoordinator: asFunction(() => {\n return new EdgeNodeDnsCoordinator({\n provider: dnsProvider as any,\n rootDomain: baseStorageDomain,\n });\n }).singleton(),\n });\n logger.info('DNS coordinator registered');\n }\n\n // Health Probe Service (心跳时探测节点可达性)\n const nodeRepo = container.resolve('nodeRepo');\n container.register({\n healthProbeService: asFunction(() => {\n return new EdgeNodeHealthProbeService({\n repository: nodeRepo,\n enabled: true,\n });\n }).singleton(),\n });\n logger.info('Health probe service registered');\n}\n"]}
|
|
@@ -148,11 +148,15 @@ function registerLocalServices(container) {
|
|
|
148
148
|
}).singleton(),
|
|
149
149
|
// DDNS Manager: 自动分配和更新 DDNS
|
|
150
150
|
ddnsManager: (0, awilix_1.asFunction)(({ subdomainClient, capabilityDetector }) => {
|
|
151
|
+
const tunnelProvider = cloudflareTunnelToken
|
|
152
|
+
? 'cloudflare'
|
|
153
|
+
: (sakuraTunnelToken ? 'sakura_frp' : 'none');
|
|
151
154
|
return new DdnsManager_1.DdnsManager({
|
|
152
155
|
client: subdomainClient,
|
|
153
156
|
detector: capabilityDetector,
|
|
154
157
|
subdomain: subdomain || nodeId || 'auto',
|
|
155
158
|
autoAllocate: true,
|
|
159
|
+
tunnelProvider,
|
|
156
160
|
});
|
|
157
161
|
}).singleton(),
|
|
158
162
|
});
|
|
@@ -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 return new DdnsManager({\n client: subdomainClient!,\n detector: capabilityDetector!,\n subdomain: subdomain || nodeId || 'auto',\n autoAllocate: true,\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\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"]}
|
|
@@ -13,6 +13,21 @@ 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
|
+
}
|
|
16
31
|
function registerDdnsRoutes(server, options) {
|
|
17
32
|
const { ddnsRepo, dnsProvider, defaultDomain } = options;
|
|
18
33
|
/**
|
|
@@ -46,25 +61,38 @@ function registerDdnsRoutes(server, options) {
|
|
|
46
61
|
sendError(response, 409, 'Subdomain already allocated');
|
|
47
62
|
return;
|
|
48
63
|
}
|
|
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
|
+
});
|
|
49
77
|
const record = await ddnsRepo.allocateSubdomain({
|
|
50
78
|
subdomain: payload.subdomain,
|
|
51
79
|
domain: defaultDomain,
|
|
52
80
|
nodeId: payload.nodeId,
|
|
53
81
|
username: payload.username,
|
|
54
|
-
ipAddress: payload.ipAddress,
|
|
55
|
-
ipv6Address: payload.ipv6Address,
|
|
82
|
+
ipAddress: mode === 'direct' ? payload.ipAddress : undefined,
|
|
83
|
+
ipv6Address: mode === 'direct' ? payload.ipv6Address : undefined,
|
|
84
|
+
recordType,
|
|
56
85
|
});
|
|
57
|
-
|
|
58
|
-
if (dnsProvider && (payload.ipAddress || payload.ipv6Address)) {
|
|
86
|
+
if (dnsProvider && recordValue) {
|
|
59
87
|
try {
|
|
60
88
|
await dnsProvider.upsertRecord({
|
|
61
89
|
domain: defaultDomain,
|
|
62
90
|
subdomain: payload.subdomain,
|
|
63
|
-
type:
|
|
64
|
-
value:
|
|
91
|
+
type: recordType,
|
|
92
|
+
value: recordValue,
|
|
65
93
|
ttl: 60,
|
|
66
94
|
});
|
|
67
|
-
logger.info(`Created DNS record: ${payload.subdomain}.${defaultDomain}`);
|
|
95
|
+
logger.info(`Created DNS record: ${payload.subdomain}.${defaultDomain} (${recordType})`);
|
|
68
96
|
}
|
|
69
97
|
catch (dnsError) {
|
|
70
98
|
logger.error(`Failed to create DNS record: ${dnsError}`);
|
|
@@ -109,24 +137,31 @@ function registerDdnsRoutes(server, options) {
|
|
|
109
137
|
const payload = body;
|
|
110
138
|
const ipAddress = payload?.ip ?? payload?.ipAddress;
|
|
111
139
|
const ipv6Address = payload?.ipv6Address;
|
|
112
|
-
|
|
140
|
+
const mode = payload?.mode === 'tunnel' ? 'tunnel' : 'direct';
|
|
141
|
+
if (mode === 'direct' && !ipAddress && !ipv6Address) {
|
|
113
142
|
sendError(response, 400, 'ip or ipv6Address is required');
|
|
114
143
|
return;
|
|
115
144
|
}
|
|
116
|
-
|
|
117
|
-
const
|
|
145
|
+
const recordType = pickRecordType({ mode, ipAddress, ipv6Address });
|
|
146
|
+
const recordValue = pickRecordValue({
|
|
147
|
+
subdomain,
|
|
148
|
+
mode,
|
|
149
|
+
tunnelProvider: payload?.tunnelProvider,
|
|
118
150
|
ipAddress,
|
|
119
151
|
ipv6Address,
|
|
120
152
|
});
|
|
153
|
+
// 更新数据库记录
|
|
154
|
+
const record = await ddnsRepo.updateRecordIp(subdomain, {
|
|
155
|
+
ipAddress: mode === 'direct' ? (ipAddress ?? null) : null,
|
|
156
|
+
ipv6Address: mode === 'direct' ? (ipv6Address ?? null) : null,
|
|
157
|
+
recordType,
|
|
158
|
+
});
|
|
121
159
|
if (!record) {
|
|
122
160
|
sendError(response, 404, 'Subdomain not found');
|
|
123
161
|
return;
|
|
124
162
|
}
|
|
125
|
-
|
|
126
|
-
if (dnsProvider) {
|
|
163
|
+
if (dnsProvider && recordValue) {
|
|
127
164
|
try {
|
|
128
|
-
const recordType = ipv6Address ? 'AAAA' : 'A';
|
|
129
|
-
const recordValue = ipv6Address ?? ipAddress;
|
|
130
165
|
await dnsProvider.upsertRecord({
|
|
131
166
|
domain: record.domain,
|
|
132
167
|
subdomain: record.subdomain,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DdnsHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/DdnsHandler.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAgBH,gDA6SC;AA1TD,iEAAqD;AAKrD,MAAM,MAAM,GAAG,IAAA,oCAAY,EAAC,aAAa,CAAC,CAAC;AAQ3C,SAAgB,kBAAkB,CAChC,MAAiB,EACjB,OAA2B;IAE3B,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAEzD;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QACxE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAMH,CAAC;YAEd,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;gBACxB,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YAED,UAAU;YACV,IAAI,CAAC,mCAAmC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjE,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,0BAA0B,CAAC,CAAC;gBACrD,OAAO;YACT,CAAC;YAED,UAAU;YACV,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC7D,IAAI,QAAQ,EAAE,CAAC;gBACb,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,6BAA6B,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,iBAAiB,CAAC;gBAC9C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,aAAa;gBACrB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,WAAW,EAAE,OAAO,CAAC,WAAW;aACjC,CAAC,CAAC;YAEH,mCAAmC;YACnC,IAAI,WAAW,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC9D,IAAI,CAAC;oBACH,MAAM,WAAW,CAAC,YAAY,CAAC;wBAC7B,MAAM,EAAE,aAAa;wBACrB,SAAS,EAAE,OAAO,CAAC,SAAS;wBAC5B,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG;wBACxC,KAAK,EAAE,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,SAAU;wBAChD,GAAG,EAAE,EAAE;qBACR,CAAC,CAAC;oBACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,OAAO,CAAC,SAAS,IAAI,aAAa,EAAE,CAAC,CAAC;gBAC3E,CAAC;gBAAC,OAAO,QAAQ,EAAE,CAAC;oBAClB,MAAM,CAAC,KAAK,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;oBACzD,sBAAsB;gBACxB,CAAC;YACH,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,wBAAwB,OAAO,CAAC,SAAS,IAAI,aAAa,EAAE,CAAC,CAAC;YAE1E,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE;gBAC5C,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;aAC1C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,iCAAiC,KAAK,EAAE,CAAC,CAAC;YACvD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACzE,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAEvD,iBAAiB;QACjB,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAKH,CAAC;YAEd,MAAM,SAAS,GAAG,OAAO,EAAE,EAAE,IAAI,OAAO,EAAE,SAAS,CAAC;YACpD,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC;YAEzC,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;gBAC/B,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,+BAA+B,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YAED,UAAU;YACV,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,SAAS,EAAE;gBACtD,SAAS;gBACT,WAAW;aACZ,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,qBAAqB,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,YAAY;YACZ,IAAI,WAAW,EAAE,CAAC;gBAChB,IAAI,CAAC;oBACH,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;oBAC9C,MAAM,WAAW,GAAG,WAAW,IAAI,SAAU,CAAC;oBAE9C,MAAM,WAAW,CAAC,YAAY,CAAC;wBAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,IAAI,EAAE,UAAU;wBAChB,KAAK,EAAE,WAAW;wBAClB,GAAG,EAAE,MAAM,CAAC,GAAG;qBAChB,CAAC,CAAC;oBACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,SAAS,IAAI,MAAM,CAAC,MAAM,OAAO,WAAW,EAAE,CAAC,CAAC;gBACrF,CAAC;gBAAC,OAAO,QAAQ,EAAE,CAAC;oBAClB,MAAM,CAAC,KAAK,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;oBACzD,uBAAuB;gBACzB,CAAC;YACH,CAAC;YAED,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE;gBAC5C,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;aAC1C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC/D,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBACxC,OAAO;YACT,CAAC;YACD,MAAM,CAAC,KAAK,CAAC,iCAAiC,KAAK,EAAE,CAAC,CAAC;YACvD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,yBAAyB,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACzE,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAEvD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAEnD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,qBAAqB,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE;gBAC5C,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;gBACzC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;aAC1C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;YACpD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,yBAAyB,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QAC5E,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAEvD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAEnD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,qBAAqB,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,YAAY;YACZ,IAAI,WAAW,EAAE,CAAC;gBAChB,IAAI,CAAC;oBACH,MAAM,WAAW,CAAC,YAAY,CAAC;wBAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;wBACrB,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,IAAI,EAAE,MAAM,CAAC,UAAU;qBACxB,CAAC,CAAC;oBACH,MAAM,CAAC,IAAI,CAAC,uBAAuB,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBACnE,CAAC;gBAAC,OAAO,QAAQ,EAAE,CAAC;oBAClB,MAAM,CAAC,KAAK,CAAC,gCAAgC,QAAQ,EAAE,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC;YAED,UAAU;YACV,MAAM,QAAQ,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAE3C,MAAM,CAAC,IAAI,CAAC,uBAAuB,SAAS,EAAE,CAAC,CAAC;YAEhD,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,aAAa,SAAS,WAAW;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,gCAAgC,KAAK,EAAE,CAAC,CAAC;YACtD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QAC7E,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAEvD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,OAAO,GAAG,IAAuC,CAAC;YAExD,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,yBAAyB,CAAC;YAE5D,MAAM,QAAQ,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAE/C,YAAY;YACZ,IAAI,WAAW,EAAE,CAAC;gBAChB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;gBACnD,IAAI,MAAM,EAAE,CAAC;oBACX,IAAI,CAAC;wBACH,MAAM,WAAW,CAAC,YAAY,CAAC;4BAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;4BACrB,SAAS,EAAE,MAAM,CAAC,SAAS;4BAC3B,IAAI,EAAE,MAAM,CAAC,UAAU;yBACxB,CAAC,CAAC;oBACL,CAAC;oBAAC,OAAO,QAAQ,EAAE,CAAC;wBAClB,MAAM,CAAC,KAAK,CAAC,qDAAqD,QAAQ,EAAE,CAAC,CAAC;oBAChF,CAAC;gBACH,CAAC;YACH,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,qBAAqB,SAAS,aAAa,MAAM,EAAE,CAAC,CAAC;YAEjE,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,aAAa,SAAS,SAAS;gBACxC,MAAM;aACP,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,4BAA4B,KAAK,EAAE,CAAC,CAAC;YAClD,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;AACxC,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,OAAwB;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACnC,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,CAAC,SAAS,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,SAAS,CAAC,CAAC;YACrB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,QAAwB,EAAE,MAAc,EAAE,IAAa;IACvE,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,IAAI,CAAC,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,SAAS,CAAC,QAAwB,EAAE,MAAc,EAAE,OAAe;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AACjD,CAAC","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 } | 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 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 } | undefined;\n\n const ipAddress = payload?.ip ?? payload?.ipAddress;\n const ipv6Address = payload?.ipv6Address;\n\n if (!ipAddress && !ipv6Address) {\n sendError(response, 400, 'ip or ipv6Address is required');\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 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\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"]}
|
|
@@ -16,7 +16,7 @@ export interface DdnsRecord {
|
|
|
16
16
|
domain: string;
|
|
17
17
|
ipAddress?: string;
|
|
18
18
|
ipv6Address?: string;
|
|
19
|
-
recordType: 'A' | 'AAAA';
|
|
19
|
+
recordType: 'A' | 'AAAA' | 'CNAME';
|
|
20
20
|
nodeId?: string;
|
|
21
21
|
username?: string;
|
|
22
22
|
status: 'active' | 'banned';
|
|
@@ -30,12 +30,14 @@ export interface CreateDdnsRecordInput {
|
|
|
30
30
|
domain: string;
|
|
31
31
|
ipAddress?: string;
|
|
32
32
|
ipv6Address?: string;
|
|
33
|
+
recordType?: 'A' | 'AAAA' | 'CNAME';
|
|
33
34
|
nodeId?: string;
|
|
34
35
|
username?: string;
|
|
35
36
|
}
|
|
36
37
|
export interface UpdateDdnsRecordInput {
|
|
37
|
-
ipAddress?: string;
|
|
38
|
-
ipv6Address?: string;
|
|
38
|
+
ipAddress?: string | null;
|
|
39
|
+
ipv6Address?: string | null;
|
|
40
|
+
recordType?: 'A' | 'AAAA' | 'CNAME';
|
|
39
41
|
}
|
|
40
42
|
export declare class DdnsRepository {
|
|
41
43
|
private readonly db;
|
|
@@ -75,7 +75,7 @@ class DdnsRepository {
|
|
|
75
75
|
throw new Error(`Subdomain ${subdomain} already allocated`);
|
|
76
76
|
}
|
|
77
77
|
const now = new Date();
|
|
78
|
-
const recordType = ipv6Address ? 'AAAA' : 'A';
|
|
78
|
+
const recordType = input.recordType ?? (ipv6Address ? 'AAAA' : 'A');
|
|
79
79
|
await this.db.insert(this.schema.ddnsRecords).values({
|
|
80
80
|
subdomain,
|
|
81
81
|
domain,
|
|
@@ -147,14 +147,19 @@ class DdnsRepository {
|
|
|
147
147
|
const updates = { updatedAt: (0, db_1.toDbTimestamp)(this.db, now) };
|
|
148
148
|
if (input.ipAddress !== undefined) {
|
|
149
149
|
updates.ipAddress = input.ipAddress;
|
|
150
|
-
|
|
150
|
+
if (input.ipAddress !== null && input.recordType === undefined) {
|
|
151
|
+
updates.recordType = 'A';
|
|
152
|
+
}
|
|
151
153
|
}
|
|
152
154
|
if (input.ipv6Address !== undefined) {
|
|
153
155
|
updates.ipv6Address = input.ipv6Address;
|
|
154
|
-
if (!input.ipAddress) {
|
|
156
|
+
if (input.ipv6Address !== null && !input.ipAddress && input.recordType === undefined) {
|
|
155
157
|
updates.recordType = 'AAAA';
|
|
156
158
|
}
|
|
157
159
|
}
|
|
160
|
+
if (input.recordType !== undefined) {
|
|
161
|
+
updates.recordType = input.recordType;
|
|
162
|
+
}
|
|
158
163
|
await this.db
|
|
159
164
|
.update(this.schema.ddnsRecords)
|
|
160
165
|
.set(updates)
|
|
@@ -162,8 +167,8 @@ class DdnsRepository {
|
|
|
162
167
|
logger.info(`Updated DDNS record: ${subdomain} -> ${input.ipAddress ?? input.ipv6Address}`);
|
|
163
168
|
return {
|
|
164
169
|
...existing,
|
|
165
|
-
ipAddress: input.ipAddress
|
|
166
|
-
ipv6Address: input.ipv6Address
|
|
170
|
+
ipAddress: input.ipAddress === undefined ? existing.ipAddress : (input.ipAddress ?? undefined),
|
|
171
|
+
ipv6Address: input.ipv6Address === undefined ? existing.ipv6Address : (input.ipv6Address ?? undefined),
|
|
167
172
|
recordType: updates.recordType ?? existing.recordType,
|
|
168
173
|
updatedAt: now,
|
|
169
174
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DdnsRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/DdnsRepository.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS Repository\n *\n * 管理 DDNS 域名池和记录\n */\n\nimport { eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { getSchema, toDbTimestamp, fromDbTimestamp } from './db';\n\nconst logger = getLoggerFor('DdnsRepository');\n\nexport interface DdnsDomain {\n domain: string;\n status: 'active' | 'suspended';\n provider?: string;\n zoneId?: string;\n createdAt: Date;\n}\n\nexport interface DdnsRecord {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: 'A' | 'AAAA';\n nodeId?: string;\n username?: string;\n status: 'active' | 'banned';\n bannedReason?: string;\n ttl: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateDdnsRecordInput {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n nodeId?: string;\n username?: string;\n}\n\nexport interface UpdateDdnsRecordInput {\n ipAddress?: string;\n ipv6Address?: string;\n}\n\nexport class DdnsRepository {\n private readonly schema: ReturnType<typeof getSchema>;\n\n constructor(private readonly db: IdentityDatabase) {\n this.schema = getSchema(db);\n }\n\n // ==================== Domain Pool ====================\n\n /**\n * 添加域名到池中\n */\n async addDomain(\n domain: string,\n provider?: string,\n zoneId?: string,\n ): Promise<DdnsDomain> {\n const now = new Date();\n\n await this.db.insert(this.schema.ddnsDomains).values({\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: toDbTimestamp(this.db, now),\n });\n\n logger.info(`Added domain to pool: ${domain}`);\n\n return {\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: now,\n };\n }\n\n /**\n * 获取所有活跃的域名\n */\n async getActiveDomains(): Promise<DdnsDomain[]> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsDomains)\n .where(eq(this.schema.ddnsDomains.status, 'active'));\n\n return results.map((row: typeof results[0]) => ({\n domain: row.domain,\n status: row.status as 'active' | 'suspended',\n provider: row.provider ?? undefined,\n zoneId: row.zoneId ?? undefined,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n }));\n }\n\n /**\n * 暂停域名\n */\n async suspendDomain(domain: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsDomains)\n .set({ status: 'suspended' })\n .where(eq(this.schema.ddnsDomains.domain, domain));\n\n logger.info(`Suspended domain: ${domain}`);\n }\n\n // ==================== DDNS Records ====================\n\n /**\n * 分配子域名\n */\n async allocateSubdomain(input: CreateDdnsRecordInput): Promise<DdnsRecord> {\n const { subdomain, domain, ipAddress, ipv6Address, nodeId, username } = input;\n\n // 检查是否已存在\n const existing = await this.getRecord(subdomain);\n if (existing) {\n throw new Error(`Subdomain ${subdomain} already allocated`);\n }\n\n const now = new Date();\n const recordType = ipv6Address ? 'AAAA' : 'A';\n\n await this.db.insert(this.schema.ddnsRecords).values({\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: toDbTimestamp(this.db, now),\n updatedAt: toDbTimestamp(this.db, now),\n });\n\n logger.info(`Allocated subdomain: ${subdomain}.${domain}`);\n\n return {\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getRecord(subdomain: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n };\n }\n\n /**\n * 更新 DDNS 记录的 IP 地址\n */\n async updateRecordIp(\n subdomain: string,\n input: UpdateDdnsRecordInput,\n ): Promise<DdnsRecord | null> {\n const existing = await this.getRecord(subdomain);\n if (!existing) {\n return null;\n }\n\n if (existing.status === 'banned') {\n throw new Error(`Subdomain ${subdomain} is banned: ${existing.bannedReason}`);\n }\n\n const now = new Date();\n const updates: Record<string, unknown> = { updatedAt: toDbTimestamp(this.db, now) };\n\n if (input.ipAddress !== undefined) {\n updates.ipAddress = input.ipAddress;\n updates.recordType = 'A';\n }\n if (input.ipv6Address !== undefined) {\n updates.ipv6Address = input.ipv6Address;\n if (!input.ipAddress) {\n updates.recordType = 'AAAA';\n }\n }\n\n await this.db\n .update(this.schema.ddnsRecords)\n .set(updates)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Updated DDNS record: ${subdomain} -> ${input.ipAddress ?? input.ipv6Address}`);\n\n return {\n ...existing,\n ipAddress: input.ipAddress ?? existing.ipAddress,\n ipv6Address: input.ipv6Address ?? existing.ipv6Address,\n recordType: (updates.recordType as 'A' | 'AAAA') ?? existing.recordType,\n updatedAt: now,\n };\n }\n\n /**\n * 封禁子域名\n */\n async banSubdomain(subdomain: string, reason: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsRecords)\n .set({\n status: 'banned',\n bannedReason: reason,\n updatedAt: toDbTimestamp(this.db, new Date()),\n })\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);\n }\n\n /**\n * 解封子域名\n */\n async unbanSubdomain(subdomain: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsRecords)\n .set({\n status: 'active',\n bannedReason: null,\n updatedAt: toDbTimestamp(this.db, new Date()),\n })\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Unbanned subdomain: ${subdomain}`);\n }\n\n /**\n * 释放子域名\n */\n async releaseSubdomain(subdomain: string): Promise<boolean> {\n await this.db\n .delete(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Released subdomain: ${subdomain}`);\n return true;\n }\n\n /**\n * 获取用户的所有子域名\n */\n async getRecordsByUsername(username: string): Promise<DdnsRecord[]> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.username, username));\n\n return results.map((row: typeof results[0]) => ({\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n }));\n }\n\n /**\n * 获取节点的子域名\n */\n async getRecordByNodeId(nodeId: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.nodeId, nodeId))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"DdnsRepository.js","sourceRoot":"","sources":["../../../src/identity/drizzle/DdnsRepository.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DDNS Repository\n *\n * 管理 DDNS 域名池和记录\n */\n\nimport { eq } from 'drizzle-orm';\nimport type { IdentityDatabase } from './db';\nimport { getLoggerFor } from 'global-logger-factory';\nimport { getSchema, toDbTimestamp, fromDbTimestamp } from './db';\n\nconst logger = getLoggerFor('DdnsRepository');\n\nexport interface DdnsDomain {\n domain: string;\n status: 'active' | 'suspended';\n provider?: string;\n zoneId?: string;\n createdAt: Date;\n}\n\nexport interface DdnsRecord {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType: 'A' | 'AAAA' | 'CNAME';\n nodeId?: string;\n username?: string;\n status: 'active' | 'banned';\n bannedReason?: string;\n ttl: number;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateDdnsRecordInput {\n subdomain: string;\n domain: string;\n ipAddress?: string;\n ipv6Address?: string;\n recordType?: 'A' | 'AAAA' | 'CNAME';\n nodeId?: string;\n username?: string;\n}\n\nexport interface UpdateDdnsRecordInput {\n ipAddress?: string | null;\n ipv6Address?: string | null;\n recordType?: 'A' | 'AAAA' | 'CNAME';\n}\n\nexport class DdnsRepository {\n private readonly schema: ReturnType<typeof getSchema>;\n\n constructor(private readonly db: IdentityDatabase) {\n this.schema = getSchema(db);\n }\n\n // ==================== Domain Pool ====================\n\n /**\n * 添加域名到池中\n */\n async addDomain(\n domain: string,\n provider?: string,\n zoneId?: string,\n ): Promise<DdnsDomain> {\n const now = new Date();\n\n await this.db.insert(this.schema.ddnsDomains).values({\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: toDbTimestamp(this.db, now),\n });\n\n logger.info(`Added domain to pool: ${domain}`);\n\n return {\n domain,\n status: 'active',\n provider,\n zoneId,\n createdAt: now,\n };\n }\n\n /**\n * 获取所有活跃的域名\n */\n async getActiveDomains(): Promise<DdnsDomain[]> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsDomains)\n .where(eq(this.schema.ddnsDomains.status, 'active'));\n\n return results.map((row: typeof results[0]) => ({\n domain: row.domain,\n status: row.status as 'active' | 'suspended',\n provider: row.provider ?? undefined,\n zoneId: row.zoneId ?? undefined,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n }));\n }\n\n /**\n * 暂停域名\n */\n async suspendDomain(domain: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsDomains)\n .set({ status: 'suspended' })\n .where(eq(this.schema.ddnsDomains.domain, domain));\n\n logger.info(`Suspended domain: ${domain}`);\n }\n\n // ==================== DDNS Records ====================\n\n /**\n * 分配子域名\n */\n async allocateSubdomain(input: CreateDdnsRecordInput): Promise<DdnsRecord> {\n const { subdomain, domain, ipAddress, ipv6Address, nodeId, username } = input;\n\n // 检查是否已存在\n const existing = await this.getRecord(subdomain);\n if (existing) {\n throw new Error(`Subdomain ${subdomain} already allocated`);\n }\n\n const now = new Date();\n const recordType = input.recordType ?? (ipv6Address ? 'AAAA' : 'A');\n\n await this.db.insert(this.schema.ddnsRecords).values({\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: toDbTimestamp(this.db, now),\n updatedAt: toDbTimestamp(this.db, now),\n });\n\n logger.info(`Allocated subdomain: ${subdomain}.${domain}`);\n\n return {\n subdomain,\n domain,\n ipAddress,\n ipv6Address,\n recordType,\n nodeId,\n username,\n status: 'active',\n ttl: 60,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * 获取 DDNS 记录\n */\n async getRecord(subdomain: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA' | 'CNAME') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n };\n }\n\n /**\n * 更新 DDNS 记录的 IP 地址\n */\n async updateRecordIp(\n subdomain: string,\n input: UpdateDdnsRecordInput,\n ): Promise<DdnsRecord | null> {\n const existing = await this.getRecord(subdomain);\n if (!existing) {\n return null;\n }\n\n if (existing.status === 'banned') {\n throw new Error(`Subdomain ${subdomain} is banned: ${existing.bannedReason}`);\n }\n\n const now = new Date();\n const updates: Record<string, unknown> = { updatedAt: toDbTimestamp(this.db, now) };\n\n if (input.ipAddress !== undefined) {\n updates.ipAddress = input.ipAddress;\n if (input.ipAddress !== null && input.recordType === undefined) {\n updates.recordType = 'A';\n }\n }\n if (input.ipv6Address !== undefined) {\n updates.ipv6Address = input.ipv6Address;\n if (input.ipv6Address !== null && !input.ipAddress && input.recordType === undefined) {\n updates.recordType = 'AAAA';\n }\n }\n if (input.recordType !== undefined) {\n updates.recordType = input.recordType;\n }\n\n await this.db\n .update(this.schema.ddnsRecords)\n .set(updates)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Updated DDNS record: ${subdomain} -> ${input.ipAddress ?? input.ipv6Address}`);\n\n return {\n ...existing,\n ipAddress: input.ipAddress === undefined ? existing.ipAddress : (input.ipAddress ?? undefined),\n ipv6Address: input.ipv6Address === undefined ? existing.ipv6Address : (input.ipv6Address ?? undefined),\n recordType: (updates.recordType as 'A' | 'AAAA' | 'CNAME') ?? existing.recordType,\n updatedAt: now,\n };\n }\n\n /**\n * 封禁子域名\n */\n async banSubdomain(subdomain: string, reason: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsRecords)\n .set({\n status: 'banned',\n bannedReason: reason,\n updatedAt: toDbTimestamp(this.db, new Date()),\n })\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.warn(`Banned subdomain: ${subdomain}, reason: ${reason}`);\n }\n\n /**\n * 解封子域名\n */\n async unbanSubdomain(subdomain: string): Promise<void> {\n await this.db\n .update(this.schema.ddnsRecords)\n .set({\n status: 'active',\n bannedReason: null,\n updatedAt: toDbTimestamp(this.db, new Date()),\n })\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Unbanned subdomain: ${subdomain}`);\n }\n\n /**\n * 释放子域名\n */\n async releaseSubdomain(subdomain: string): Promise<boolean> {\n await this.db\n .delete(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.subdomain, subdomain));\n\n logger.info(`Released subdomain: ${subdomain}`);\n return true;\n }\n\n /**\n * 获取用户的所有子域名\n */\n async getRecordsByUsername(username: string): Promise<DdnsRecord[]> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.username, username));\n\n return results.map((row: typeof results[0]) => ({\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n }));\n }\n\n /**\n * 获取节点的子域名\n */\n async getRecordByNodeId(nodeId: string): Promise<DdnsRecord | null> {\n const results = await this.db\n .select()\n .from(this.schema.ddnsRecords)\n .where(eq(this.schema.ddnsRecords.nodeId, nodeId))\n .limit(1);\n\n if (results.length === 0) {\n return null;\n }\n\n const row = results[0];\n return {\n subdomain: row.subdomain,\n domain: row.domain,\n ipAddress: row.ipAddress ?? undefined,\n ipv6Address: row.ipv6Address ?? undefined,\n recordType: (row.recordType as 'A' | 'AAAA') ?? 'A',\n nodeId: row.nodeId ?? undefined,\n username: row.username ?? undefined,\n status: (row.status as 'active' | 'banned') ?? 'active',\n bannedReason: row.bannedReason ?? undefined,\n ttl: row.ttl ?? 60,\n createdAt: fromDbTimestamp(row.createdAt) ?? new Date(0),\n updatedAt: fromDbTimestamp(row.updatedAt) ?? new Date(0),\n };\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undefineds.co/xpod",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
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",
|