generated from template/slidev-template
update
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
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'
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
export const app = new App();
|
||||
@@ -14,7 +14,7 @@ export class CloudflareDDNS {
|
||||
};
|
||||
}
|
||||
async updateRecord(data: DnsUpdate) {
|
||||
const { zone_id, record_id, domain, type ,new_ip, api_token } = data;
|
||||
const { zone_id, record_id, domain, type, new_ip, api_token } = data;
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}`;
|
||||
const body = {
|
||||
"type": type || 'A',
|
||||
@@ -29,17 +29,60 @@ export class CloudflareDDNS {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const result = await response.json();
|
||||
if(!result.success) {
|
||||
if (!result.success) {
|
||||
throw new Error(`更新失败: ${JSON.stringify(result.errors)}`);
|
||||
}
|
||||
console.log(`更新成功: ${domain} -> ${new_ip}`);
|
||||
return result;
|
||||
}
|
||||
async getList(zone_id: string, api_token: string) {
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records`;
|
||||
async getList(zone_id: string, api_token: string, opts?: { per_page?: number; page?: number, search?: string }) {
|
||||
const per_page = opts?.per_page || 100;
|
||||
const page = opts?.page || 1;
|
||||
const search = opts?.search ? `&search=${encodeURIComponent(opts.search)}` : '';
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?per_page=${per_page}&page=${page}${search}`;
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.makeHeader(api_token),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
async getRecord(zone_id: string, record_id: string, api_token: string) {
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}`;
|
||||
return fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.makeHeader(api_token),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
async createRecord(data: Partial<DnsUpdate>) {
|
||||
const { zone_id, domain, type = 'A', new_ip: content, api_token } = data;
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records`;
|
||||
const body = {
|
||||
"type": type,
|
||||
"name": domain,
|
||||
"content": content,
|
||||
"ttl": 0,
|
||||
"proxied": false,
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: this.makeHeader(api_token),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(`创建失败: ${JSON.stringify(result.errors)}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async deleteRecord(zone_id: string, record_id: string, api_token: string) {
|
||||
const url = `https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: this.makeHeader(api_token),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(`删除失败: ${JSON.stringify(result.errors)}`);
|
||||
}
|
||||
console.log(`删除成功: Record ID ${record_id}`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {app} from '../app.ts';
|
||||
import { app } from '../app.ts';
|
||||
import { CloudflareDDNS } from '../ddns/cloudflare/index.ts';
|
||||
|
||||
app.route({
|
||||
@@ -8,11 +8,12 @@ app.route({
|
||||
}).define(async (ctx) => {
|
||||
const { zone_id, record_id, domain, new_ip, api_token, type = 'A' } = ctx.query || {};
|
||||
|
||||
if(!zone_id || !record_id || !domain || !new_ip || !api_token) {
|
||||
if (!zone_id || !record_id || !domain || !new_ip || !api_token) {
|
||||
ctx.throw?.('缺少必要参数');
|
||||
}
|
||||
|
||||
const cf = new CloudflareDDNS();
|
||||
|
||||
const result = await cf.updateRecord({
|
||||
zone_id,
|
||||
record_id,
|
||||
@@ -21,6 +22,83 @@ app.route({
|
||||
api_token,
|
||||
type,
|
||||
});
|
||||
if (result.success === false) {
|
||||
ctx.throw?.(result.errors?.map((e) => e.message).join('; ') || '更新DNS记录失败');
|
||||
} else {
|
||||
console.log('更新DNS记录成功:', `${domain} -> ${new_ip}`);
|
||||
}
|
||||
|
||||
ctx.body = { result };
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'cf',
|
||||
key: 'create',
|
||||
description: '创建Cloudflare DNS记录, 需要提供zone_id, domain, new_ip, api_token, type参数, type参数可选,默认为A记录, A 或AAAA',
|
||||
}).define(async (ctx) => {
|
||||
const { zone_id, domain, new_ip, api_token, type = 'A' } = ctx.query || {};
|
||||
|
||||
if (!zone_id || !domain || !new_ip || !api_token) {
|
||||
ctx.throw?.('缺少必要参数');
|
||||
}
|
||||
|
||||
const cf = new CloudflareDDNS();
|
||||
|
||||
const result = await cf.createRecord({
|
||||
zone_id,
|
||||
domain,
|
||||
new_ip,
|
||||
api_token,
|
||||
type,
|
||||
});
|
||||
if (result.success === false) {
|
||||
ctx.throw?.(result.errors?.map((e) => e.message).join('; ') || '创建DNS记录失败');
|
||||
}
|
||||
console.log(`创建成功: ${domain} -> ${new_ip}`);
|
||||
const record_id = result.result.id;
|
||||
const name = result.result.name;
|
||||
const content = result.result.content;
|
||||
console.log(`id->name: ${result.result.id} -> ${name}, content: ${content}`);
|
||||
ctx.body = {
|
||||
record_id: record_id,
|
||||
name: name,
|
||||
content: content,
|
||||
result: result.result
|
||||
};
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'cf',
|
||||
key: 'delete',
|
||||
description: '删除Cloudflare DNS记录, 需要提供zone_id, record_id, api_token参数',
|
||||
}).define(async (ctx) => {
|
||||
const { zone_id, record_id, api_token } = ctx.query || {};
|
||||
if (!zone_id || !record_id || !api_token) {
|
||||
ctx.throw?.('缺少必要参数');
|
||||
}
|
||||
|
||||
const cf = new CloudflareDDNS();
|
||||
|
||||
const result = await cf.deleteRecord(zone_id, record_id, api_token);
|
||||
if (result.success === false) {
|
||||
ctx.throw?.(result.errors?.map((e) => e.message).join('; ') || '删除DNS记录失败');
|
||||
}
|
||||
ctx.body = { result };
|
||||
})
|
||||
|
||||
app.route({
|
||||
path: 'cf',
|
||||
key: 'list',
|
||||
description: '获取Cloudflare DNS记录列表, 需要提供zone_id, api_token参数,可选search参数用于模糊搜索域名',
|
||||
}).define(async (ctx) => {
|
||||
const { zone_id, api_token, search } = ctx.query || {};
|
||||
|
||||
if (!zone_id || !api_token) {
|
||||
ctx.throw?.('缺少必要参数');
|
||||
}
|
||||
|
||||
const cf = new CloudflareDDNS();
|
||||
const result = await cf.getList(zone_id, api_token, search ? { search } : undefined);
|
||||
|
||||
ctx.body = { result };
|
||||
}).addTo(app);
|
||||
107
agent/task.ts
107
agent/task.ts
@@ -1,12 +1,4 @@
|
||||
import { app } from './app.ts';
|
||||
import { createStorage } from "unstorage";
|
||||
import fsDriver from "unstorage/drivers/fs";
|
||||
|
||||
const storage = createStorage({
|
||||
driver: fsDriver({
|
||||
base: process.cwd() + '/storage/ddns-agent'
|
||||
}),
|
||||
});
|
||||
import { app, storage } from './app.ts';
|
||||
|
||||
export type CloudflareConfig = {
|
||||
// Cloudflare 访问地址
|
||||
@@ -49,14 +41,16 @@ app.route({
|
||||
const oldIp = isV4 ? config.ipv4 : config.ipv6;
|
||||
if (newIp !== oldIp) {
|
||||
// IP地址有变化,更新DNS记录
|
||||
await app.call({ path: 'cf', key: 'update' }, {
|
||||
zone_id: config.zone_id,
|
||||
record_id: isV4 ? config.record_id4 : config.record_id6,
|
||||
domain: config.domain,
|
||||
new_ip: newIp,
|
||||
api_token: config.api_token,
|
||||
type: isV4 ? 'A' : 'AAAA',
|
||||
});
|
||||
await app.call({
|
||||
path: 'cf', key: 'update', payload: {
|
||||
zone_id: config.zone_id,
|
||||
record_id: isV4 ? config.record_id4 : config.record_id6,
|
||||
domain: config.domain,
|
||||
new_ip: newIp,
|
||||
api_token: config.api_token,
|
||||
type: isV4 ? 'A' : 'AAAA',
|
||||
}
|
||||
}, {});
|
||||
// 更新配置文件中的IP地址
|
||||
if (isV4) {
|
||||
config.ipv4 = newIp;
|
||||
@@ -83,8 +77,83 @@ app.route({
|
||||
ctx.body = { message: '任务完成' };
|
||||
}).addTo(app);
|
||||
|
||||
export const main = () => {
|
||||
app.call({
|
||||
app.route({
|
||||
path: 'ip',
|
||||
key: 'init',
|
||||
description: '初始化配置文件cloudflare.json',
|
||||
}).define(async (ctx) => {
|
||||
// 初始化逻辑
|
||||
const config = await storage.getItem<CloudflareConfig>('cloudflare.json');
|
||||
|
||||
const isIpv4 = config?.flag === 1 || config?.flag === 3;
|
||||
const isIpv6 = config?.flag === 2 || config?.flag === 3;
|
||||
const apiToken = config?.api_token || '';
|
||||
if (!apiToken) {
|
||||
ctx.throw?.('配置错误:api_token为空');
|
||||
}
|
||||
const getIp = async (isV4: boolean) => {
|
||||
const res = await app.call({ path: 'ip', key: isV4 ? 'v4' : 'v6' });
|
||||
if (res.code !== 200) {
|
||||
ctx.throw?.(`获取${isV4 ? 'IPv4' : 'IPv6'}地址失败: ` + res.message);
|
||||
}
|
||||
return res.body.ip as string;
|
||||
}
|
||||
const createCfRecord = async (isV4: boolean) => {
|
||||
const newIp = await getIp(isV4);
|
||||
const cfRes = await app.call({
|
||||
path: 'cf',
|
||||
key: 'create',
|
||||
payload: {
|
||||
zone_id: config.zone_id,
|
||||
domain: config.domain,
|
||||
new_ip: newIp,
|
||||
api_token: config.api_token,
|
||||
type: isV4 ? 'A' : 'AAAA',
|
||||
}
|
||||
});
|
||||
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;
|
||||
|
||||
if (isV4) {
|
||||
config.record_id4 = record_id;
|
||||
} else {
|
||||
config.record_id6 = record_id;
|
||||
}
|
||||
config.time = new Date().toLocaleString();
|
||||
await storage.setItem('cloudflare.json', config);
|
||||
}
|
||||
let isInit = false;
|
||||
if (isIpv4 && config.record_id4 === '') {
|
||||
console.log('配置错误:需要更新IPv4地址,但record_id4为空, 正在创建新记录...');
|
||||
await createCfRecord(true);
|
||||
isInit = true;
|
||||
}
|
||||
if (isIpv6 && config.record_id6 === '') {
|
||||
console.log('配置警告:需要更新IPv6地址,但record_id6为空,正在创建新记录...');
|
||||
await createCfRecord(false);
|
||||
isInit = true;
|
||||
}
|
||||
ctx.body = { init: isInit, message: '初始化完成' };
|
||||
|
||||
}).addTo(app);
|
||||
|
||||
export const main = async () => {
|
||||
const res = await app.call({
|
||||
path: 'ip',
|
||||
key: 'init',
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
console.error('初始化失败:', res.message);
|
||||
return;
|
||||
}
|
||||
if (res.body.init) {
|
||||
console.log('初始化完成,并创建了必要的DNS记录,任务结束。');
|
||||
return;
|
||||
}
|
||||
await app.call({
|
||||
path: 'ip',
|
||||
key: 'task',
|
||||
})
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { createStorage } from "unstorage";
|
||||
import fsDriver from "unstorage/drivers/fs";
|
||||
import { CloudflareDDNS } from "@agent/ddns/cloudflare/index.ts";
|
||||
import { storage } from "@agent/app.ts";
|
||||
import { CloudflareConfig } from "@agent/task.ts";
|
||||
const storage = createStorage({
|
||||
driver: fsDriver({
|
||||
base: process.cwd() + '/storage/ddns-agent'
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
export const config = await storage.getItem<CloudflareConfig>('cloudflare.json');
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { config } from "./common.ts";
|
||||
|
||||
const cf = new CloudflareDDNS();
|
||||
if (config) {
|
||||
const res = await cf.getList(config.zone_id, config.api_token);
|
||||
const res = await cf.getList(config.zone_id, config.api_token, {search: 'xion'});
|
||||
console.log('Cloudflare DNS Records List:', res);
|
||||
} else {
|
||||
console.log('No configuration found.');
|
||||
|
||||
5
agent/test/init.ts
Normal file
5
agent/test/init.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import '../index.ts';
|
||||
|
||||
import { main } from '@agent/task.ts';
|
||||
|
||||
main()
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "@kevisual/ddns-agent",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"basename": "/root/ddns-agent",
|
||||
"app": {
|
||||
"entry": "agent/index.ts",
|
||||
"type": "script-app",
|
||||
"entry": "agent/main.ts",
|
||||
"type": "pm2-system-app",
|
||||
"engine": "bun",
|
||||
"runtime": [
|
||||
"cli"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"pub": "ev deploy . -k ddns-agent -v 1.0.1 -u",
|
||||
"pub": "ev deploy . -k ddns-agent -v 1.0.2 -u",
|
||||
"packup": "ev pack -p",
|
||||
"pm2": "pm2 start agent/corn.ts --interpreter bun --name ddns-agent"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user