# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# 1. USER CONFIGURATION
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# OPTIONAL: Paste your Record ID here later to bypass the automatic discovery phase.
# If left empty "", the script will auto-fetch, log it, and update the DNS immediately.
:local cfRecordId ""
:local cfToken "YOUR_CLOUDFLARE_API_TOKEN"
:local cfZoneId "YOUR_CLOUDFLARE_ZONE_ID"
:local cfRecordName "ddns.domain.com"
:local wanInterface "ether1"
:local dnsServer "1.1.1.1"
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# 2. STEP 1: INITIAL IP CHECK (Zero API overhead if IP matches)
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:local currentIp [/ip address get [/ip address find interface=$wanInterface] address]
:set currentIp [:pick $currentIp 0 [:find $currentIp "/"]]
:local cloudflareIp ""
:do {
:set cloudflareIp [:resolve $cfRecordName server=$dnsServer]
} on-error={
:log info "CF-DDNS: Cannot resolve $cfRecordName via $dnsServer, assuming IP has changed."
}
# Only execute further steps if an update is genuinely required
:if ($currentIp != $cloudflareIp) do={
:log info "CF-DDNS: IP mismatch detected ($cloudflareIp vs $currentIp). Proceeding..."
:local httpHeaders "Authorization: Bearer $cfToken,Content-Type: application/json"
:local finalRecordId $cfRecordId
:local proceedWithUpdate true
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# 3. STEP 2: CONDITIONAL RECORD ID FETCH
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:if ([:len $finalRecordId] = 0) do={
:log warning "CF-DDNS: cfRecordId is empty. Fetching ID from Cloudflare..."
:local getUrl "https://api.cloudflare.com/client/v4/zones/$cfZoneId/dns_records"
:local fetchResult ""
:do {
:set fetchResult [/tool fetch url=$getUrl http-header-field=$httpHeaders as-value output=user]
} on-error={
:log error "CF-DDNS: Failed to connect to Cloudflare API during discovery phase."
:set proceedWithUpdate false
}
:if ($fetchResult != "") do={
:local responseData ($fetchResult->"data")
:local idKeyword "\"id\":\""
:local idPos [:find $responseData $idKeyword]
:if ($idPos > 0) do={
:local idStart ($idPos + [:len $idKeyword])
:local idEnd [:find $responseData "\"" $idStart]
:set finalRecordId [:pick $responseData $idStart $idEnd]
# Log the discovered ID for optional user optimization
:log warning "=========================================================="
:log warning "CF-DDNS: [AUTO-DISCOVERY SUCCESS] Your Record ID is: $finalRecordId"
:log warning "CF-DDNS: Tip: Copy this ID and paste it into ':local cfRecordId' to save 1 API call per update!"
:log warning "=========================================================="
} else={
:log error "CF-DDNS: DNS Record Name not found on Cloudflare. Update aborted."
:set proceedWithUpdate false
}
}
}
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# 4. STEP 3: PUSH UPDATE TO CLOUDFLARE
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:if ($proceedWithUpdate = true) do={
:log info "CF-DDNS: Pushing IP update to Cloudflare..."
:local putUrl ("https://api.cloudflare.com/client/v4/zones/" . $cfZoneId . "/dns_records/" . $finalRecordId)
:local putData "{\"type\":\"A\",\"name\":\"$cfRecordName\",\"content\":\"$currentIp\",\"ttl\":60,\"proxied\":false}"
:do {
/tool fetch url=$putUrl http-method=put http-data=$putData http-header-field=$httpHeaders as-value output=user
:log info "CF-DDNS: Successfully updated $cfRecordName to $currentIp"
} on-error={
:log error "CF-DDNS: Failed to update IP on Cloudflare API."
}
}
} else={
# Quiet loop: IP is still valid, script exits here silently
# :log info "CF-DDNS: IP remains unchanged ($currentIp). No action needed."
}