diff --git a/agent/app.ts b/agent/app.ts index e78bfd0..8f457a7 100644 --- a/agent/app.ts +++ b/agent/app.ts @@ -1,7 +1,6 @@ import { App } from '@kevisual/router' import { createStorage } from "unstorage"; import fsDriver from "unstorage/drivers/fs"; -import { CloudflareConfig } from "@agent/task.ts"; export const storage = createStorage({ driver: fsDriver({ base: process.cwd() + '/storage/ddns-agent' diff --git a/agent/ip.ts b/agent/ip.ts index 6922f8f..0370c9a 100644 --- a/agent/ip.ts +++ b/agent/ip.ts @@ -8,6 +8,14 @@ const baseURLv6 = 'https://6.ipw.cn/'; // https://api.ipify.org/?format=text // https://ipinfo.io/ip import { app } from './app.ts'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +const execAsync = promisify(exec); + +const curl6 = async (url: string): Promise => { + const { stdout } = await execAsync(`curl -6 -s "${url}"`); + return stdout.trim(); +}; app.route({ path: 'ip', @@ -29,15 +37,11 @@ app.route({ }).define(async (ctx) => { let ip = '' try { - const response = await fetch(baseURLv6); - ip = (await response.text()).trim(); - } catch (error) { - - } + ip = await curl6(baseURLv6); + } catch (error) { } if (!isIpv6(ip)) { try { - const response2 = await fetch('https://ifconfig.co/ip'); - ip = (await response2.text()).trim(); + ip = await curl6('https://ifconfig.co/ip'); console.log('尝试第二个接口获取IPv6地址:', ip); } catch (error) { } } diff --git a/agent/routes/cloudflare.ts b/agent/routes/cloudflare.ts index 402ce98..0c688c3 100644 --- a/agent/routes/cloudflare.ts +++ b/agent/routes/cloudflare.ts @@ -44,6 +44,24 @@ app.route({ const cf = new CloudflareDDNS(); + // 先搜索是否已存在相同 domain 和 type 的记录 + const searchRes = await cf.getList(zone_id, api_token, { search: domain }); + if (searchRes.success && searchRes.result && searchRes.result.length > 0) { + const existingRecord = searchRes.result.find((r: any) => r.name === domain && r.type === type); + if (existingRecord) { + console.log(`记录已存在: ${domain} (${type}) -> ${existingRecord.content}`); + ctx.body = { + record_id: existingRecord.id, + name: existingRecord.name, + content: existingRecord.content, + result: existingRecord, + existed: true, + }; + return; + } + } + + // 不存在则创建新记录 const result = await cf.createRecord({ zone_id, domain, @@ -52,7 +70,7 @@ app.route({ type, }); if (result.success === false) { - ctx.throw?.(result.errors?.map((e) => e.message).join('; ') || '创建DNS记录失败'); + ctx.throw?.(result.errors?.map((e: any) => e.message).join('; ') || '创建DNS记录失败'); } console.log(`创建成功: ${domain} -> ${new_ip}`); const record_id = result.result.id; @@ -63,7 +81,8 @@ app.route({ record_id: record_id, name: name, content: content, - result: result.result + result: result.result, + existed: false, }; }).addTo(app); diff --git a/agent/task.ts b/agent/task.ts index e1f48de..a7a276c 100644 --- a/agent/task.ts +++ b/agent/task.ts @@ -12,6 +12,12 @@ export type CloudflareConfig = { api_token: string; ipv6: string; ipv4: string; + ipList?: { + type: 'A' | 'AAAA'; + ip: string; + domain: string; + record_id: string; + }[]; flag: number; // 0: 不更新, 1: 仅IPv4, 2: 仅IPv6, 3: IPv4和IPv6 time: string; // 上次更新时间戳 } @@ -43,7 +49,7 @@ app.route({ const oldIp = isV4 ? config.ipv4 : config.ipv6; console.log(date.toLocaleString() + ` 当前${isV4 ? 'IPv4' : 'IPv6'}地址: ${newIp}, 上次记录的IP地址: ${oldIp}`); if (newIp !== oldIp) { - // IP地址有变化,更新DNS记录 + // IP地址有变化,更新主DNS记录 const cfUpdateRes = await app.run({ path: 'cf', key: 'update', payload: { zone_id: config.zone_id, @@ -55,6 +61,49 @@ app.route({ } }, {}); console.log(date.toLocaleString() + ` 更新${isV4 ? 'IPv4' : 'IPv6'}地址结果:`, cfUpdateRes); + + // 更新ipList中所有匹配的记录 + if (config.ipList && config.ipList.length > 0) { + const recordType = isV4 ? 'A' : 'AAAA'; + for (const item of config.ipList) { + if (item.type === recordType) { + if (item.record_id) { + // record_id 存在,更新记录 + const listUpdateRes = await app.run({ + path: 'cf', key: 'update', payload: { + zone_id: config.zone_id, + record_id: item.record_id, + domain: item.domain, + new_ip: newIp, + api_token: config.api_token, + type: recordType, + } + }, {}); + console.log(date.toLocaleString() + ` 更新ipList[${item.domain}] 结果:`, listUpdateRes); + item.ip = newIp; + } else if (item.domain) { + // record_id 不存在但 domain 存在,创建新记录 + const listCreateRes = await app.run({ + path: 'cf', key: 'create', payload: { + zone_id: config.zone_id, + domain: item.domain, + new_ip: newIp, + api_token: config.api_token, + type: recordType, + } + }, {}); + console.log(date.toLocaleString() + ` 创建ipList[${item.domain}] 记录结果:`, listCreateRes); + if (listCreateRes.data?.record_id) { + item.record_id = listCreateRes.data.record_id as string; + item.ip = newIp; + } + } + } + } + // 重新赋值 ipList 确保引用变化,触发更新 + config.ipList = [...config.ipList]; + } + // 更新配置文件中的IP地址 if (isV4) { config.ipv4 = newIp; @@ -118,8 +167,8 @@ app.route({ if (cfRes.code !== 200) { ctx.throw?.(`创建${isV4 ? 'IPv4' : 'IPv6'}记录失败: ` + cfRes.message); } - console.log(`创建${isV4 ? 'IPv4' : 'IPv6'}记录结果:`, cfRes.body.record_id); - const record_id = cfRes.body.record_id as string; + console.log(`创建${isV4 ? 'IPv4' : 'IPv6'}记录结果:`, cfRes.data.record_id); + const record_id = cfRes.data.record_id as string; if (isV4) { config.record_id4 = record_id; diff --git a/package.json b/package.json index cb6ffea..415b388 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/ddns-agent", - "version": "1.0.3", + "version": "1.0.4", "description": "", "main": "index.js", "type": "module", @@ -14,7 +14,7 @@ ] }, "scripts": { - "pub": "ev deploy . -k ddns-agent -v 1.0.3 -u", + "pub": "ev deploy . -k ddns-agent -v 1.0.4 -u", "packup": "ev pack -p", "pm2": "pm2 start agent/main.ts --interpreter bun --name ddns-agent" }, diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c9f4c69 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# 定时获取ip + +ip变化了,修改dns解析。 \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index cc532a6..3364e29 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,9 +9,11 @@ "newLine": "LF", "strict": false, "typeRoots": [ - "node", "node_modules/@types", ], + "types": [ + "node" + ], "declaration": false, "noEmit": true, "allowImportingTsExtensions": true,