Skip to content

Instantly share code, notes, and snippets.

@natthasath
Created March 6, 2024 09:00
Show Gist options
  • Save natthasath/2e496c1626974a003d122a3a35178457 to your computer and use it in GitHub Desktop.
Save natthasath/2e496c1626974a003d122a3a35178457 to your computer and use it in GitHub Desktop.
ddns-cloudflare
################### Mikrotik - CF Worker DDNS #################
# #
# MIKROTIK - CLOUDFLARE WORDERS DDNS #
# build 0.9.230428 public review #
# BY: PHUKAO PONGSUWAN #
# Worker Script: https://pastebin.com/zjtfsSRZ #
# Router Script: https://pastebin.com/kz9iCSeb #
# #
# 0.9.230222
# fix compare ip method by reolve via DNS
# 0.9.230428
# fix - date to thai format - dd/mm/yyyy
# add - dontnotify value in json - IF want to slient update to some subdomain
# "false" mean alway send notify, "true" mean slient update
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
:local DDNSURL "https://<workerid>.<subdomain>.workers.dev/";
:local interfaceName "pppoe-out1";
:local domains {
{
"domain"="xxx.yoursdomain.com";
"records_id"="abcxxxxxxxxxxxxxxx";
"zone_id"="xyzzzzzzzzzzzzzzzzzzzz";
"proxied"="false";
"dontnotify"="false";
};
}
############### NOT NEED TO CHANGE BELOW ##############
:global DDNSIP;
:global DDNSURL;
:local DNSSERVER "1.1.1.1"
:local headers "content-type:application/json";
:local success "\"success\":true";
:local getWanIPAddress [:pick [/ip/address/get value-name=address [find interface=$interfaceName]] 0 ([:len [/ip/address/get value-name=address [find interface=$interfaceName]]] - 3)];
:put "WAN: $interfaceName = $getWanIPAddress";
:set ($DDNSIP->$interfaceName) $getWanIPAddress;
:local domainIP [:resolve ($domains->0->"domain") server=$DNSSERVER]
:put "Resolve: $($domains->0->"domain") = $domainIP at $DNSSERVER\n";
if (($DDNSIP->$interfaceName) != $domainIP) do={
:foreach d in=$domains do={
:local domain ($d->"domain");
:local proxied ($d->"proxied");
:local dontnotify false;
if ( ($d->"dontnotify") = true ) do={
:set $dontnotify true
}
:local fURL ($DDNSURL."?zone=".$d->"zone_id"."&id=".$d->"records_id");
:local fDATA "{\"type\":\"A\",\"name\":\"$domain\",\"content\":\"$getWanIPAddress\",\"proxied\":$proxied,\"dontnotify\":$dontnotify,\"ttl\": 60}";
## Payload debug Line
:put $fDATA
if ( [:find ([/tool/fetch url=$fURL http-method=put http-header-field=$headers http-data=$fDATA as-value output=user]->"data") ($success)] ) do={
:log warning ( "DDNS $interfaceName: $domain / $getWanIPAddress update success (Notify: ".(!$dontnotify)." )" )
:put ( "DDNS $interfaceName: $domain / $getWanIPAddress update success (Notify: ".(!$dontnotify).")" )
} else={
:log error "DDNS $interfaceName: $domain - update FAIL!!";
:put "DDNS $interfaceName: $domain - update FAIL!!\n";
}
}
} else={
#:log info "$interfaceName: IP not change";
:put "$interfaceName: IP not change, no need to update\n";
}
#################### updateDnsCloudFlare ####################
/** MIKROTIK - CLOUDFLARE WORDERS DDNS **/
/** build 0.9.230428 public review **/
/** BY: PHUKAO PONGSUWAN **/
/** Worker Script: https://pastebin.com/zjtfsSRZ **/
/** Router Script: https://pastebin.com/kz9iCSeb **/
/** API TOKEN **
*
* PUT Your TOKEN in this seciton or
* Create Environment Variables name "API_TOKEN" in setting workers page
*
* 0.9.230428
* fix - date to thai format - dd/mm/yyyy
* add - dontnotify value in json - IF want to slient update to some subdomain
*
*/
var CF_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // <============== YOUR TOKEN HERE
/**
* ACCESS URL: https://<worker-service>.<worker-subdomain>.workers.dev/?zone=<zone-id>&id=<record-id>
* METHOD PUT or POST
* JSON BODY
{
"type": "A",
"name": "<sub-domain>",
"content": "<new ip>",
"proxied": false,
"dontnotify" : false,
"ttl": 60
}
*
* Environment Variables
* [Require]
* API_TOKEN - CloudFlare DNS ZONE TOKEN
*
* [Optional]
* NOTIFY_MESSAGE - MESSAGE PATTHEN include field [DOMAIN] [IP] and [TIME]
* LINE_TOKEN - LINE notify TOKEN issue form https://notify-bot.line.me/
* TELEGRAM_TOKEN - TELEGRAM BOT TOKEN issue form https://core.telegram.org/bots/api
* TELEGRAM_CHANNEL - TELEGEAM CHANNEL to receive notify
* TIMEZONE - default Asia/Bangkok
*/
/**
*
* DO NOT EDIT BELOW
*
*/
/** CLOUDFLARE API URL**/
const cfurl = 'https://api.cloudflare.com/client/v4/zones/';
if (typeof API_TOKEN !== 'undefined') {
CF_TOKEN = API_TOKEN; // GET from Environment Variables
}
async function gatherResponse(response) {
const { headers } = response;
const contentType = headers.get('content-type') || '';
if (contentType.includes('application/json')) {
const replyJSON = await response.json()
if ( replyJSON["success"] ) {
return JSON.stringify(replyJSON);
} else {
const response = new Response('{"success":false,"errors":"CF API error."}', {
status: 400
})
return response.text();
}
}
return response.text();
}
async function handleRequest(request) {
//ALLOW Only PUT and POST
if (request.method === 'PUT' || request.method === 'POST') {
// Get the JSON data from the request body
const url = new URL(request.url);
//check query string have ZONE and ID
if (url.searchParams.get('zone') && url.searchParams.get('id') ) {
// CALL CLOUDFLARE API
try {
//const body = await request.json();
const body = await request.json();
const putAPI = cfurl + url.searchParams.get('zone') + '/dns_records/' + url.searchParams.get('id');
const putBODY = {
type: body["type"],
name: body["name"],
content: body["content"],
proxied: body["proxied"],
ttl: body["ttl"]
};
const init = {
body: JSON.stringify(putBODY),
method: 'PUT',
headers: {
'Authorization': 'Bearer '+CF_TOKEN,
'content-type': 'application/json',
},
};
const response = await fetch(putAPI, init);
const results = await gatherResponse(response);
// NOTIFY SECTION ------
if (!dontNotify) { //MAKE NOTIFY
if ((typeof LINE_TOKEN !== 'undefined') || (typeof TELEGRAM_TOKEN !== 'undefined')) {
const currentDate = new Date();
var NotifyTIMEZONE = 'Asia/Bangkok';
if (typeof TIMEZONE !== 'undefined') {
NotifyTIMEZONE = TIMEZONE;
}
const dateString = dateTimeString = currentDate.toLocaleString('th-TH', {
timeZone: NotifyTIMEZONE,
hour12: false
});
const LogMessage = dateString+': '+body["name"]+' <'+body["content"]+'>';
//Log result
console.log(LogMessage);
var NotifyMessage = body["name"]+' <'+body["content"]+'>\n'+dateString;
if (typeof NOTIFY_MESSAGE !== 'undefined') {
var NotifyMessage = NOTIFY_MESSAGE;
NotifyMessage = NotifyMessage.replace('[DOMAIN]',body["name"]);
NotifyMessage = NotifyMessage.replace('[IP]',body["content"]);
NotifyMessage = NotifyMessage.replace('[TIME]',dateString);
NotifyMessage = NotifyMessage.replace('\\n','\n');;
}
// LINE NOTIFY
if (typeof LINE_TOKEN !== 'undefined') {
const Notify_API_URL = 'https://notify-api.line.me/api/notify/';
try {
const initLineNotify = {
body: 'message='+NotifyMessage,
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
'authorization': 'Bearer '+LINE_TOKEN
}
};
const responseNotify = await fetch(Notify_API_URL, initLineNotify)
.then(response => {
if (response.status === 200) {
// Response is ok
console.log('LINE: '+NotifyMessage);
} else {
const response = new Response('Error: LINE API Response', { status: 400 });
return response
}
})
.catch(error => {
const response = new Response('Error: LINE API Calling', { status: 400 });
return response
});
} catch (error) {
const response = new Response('Error: LINE API', { status: 400 });
return response
}
} else { console.log( "LINE: NULL" ) }
// end Line API defined
// END - LINE NOTIFY -----
// TELEGRAM NOTIFY
if ((typeof TELEGRAM_TOKEN !== 'undefined') && (typeof TELEGRAM_CHANNEL !== 'undefined')) {
const Notify_TELEGRAM_URL = 'https://api.telegram.org/bot'+TELEGRAM_TOKEN+'/sendmessage?chat_id='+TELEGRAM_CHANNEL+'';
try {
const initTeleNotify = {
body: 'text='+NotifyMessage,
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
};
const responseNotify = await fetch(Notify_TELEGRAM_URL, initTeleNotify)
.then(response => {
if (response.status === 200) {
// Response is ok
console.log('TELEGRAM: '+NotifyMessage);
} else {
const response = new Response('Error: TELEGRAM Response', { status: 400 });
return response
}
})
.catch(error => {
const response = new Response('Error: TELEGRAM API Calling', { status: 400 });
return response
});
} catch (error) {
const response = new Response('Error: TELEGRAM API', { status: 400 });
return response
}
} else { console.log( "TELEGRAM: NULL" ) }
// end Telegram API defined
// END - TELEGRAM NOTIFY
} // END - NOTIFY SECTION -----
} else { //END //MAKE NOTIFY
console.log("Domain "+body["name"]+"="+body["content"])
}
// return MAIN
return new Response(results, init);
} catch (error) {
if (error instanceof SyntaxError) {
// The request body is not valid JSON
const response = new Response('Error: JSON SyntaxError',{ status: 400 });
return response
} else {
// Reference or Declaration Error
if (error instanceof ReferenceError) {
const response = new Response('Error: ReferenceError',{ status: 400 });
return response
} else {
// Some other error occurred
const response = new Response('Error: internal somting error',{ status: 400 });
return response
}
}
}
} else {
// Zone Or ID not found
const response = new Response('Error: Query string not valid pattern',{ status: 400 });
return response
}
} else {
const response = new Response('Error: Method not allow.', { status: 405 });
return response
}
}
addEventListener('fetch', event => {
return event.respondWith(handleRequest(event.request));
});
/** --- END --- **/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment