cloudflare ddns自用更新脚本,自建邮件+博客服务器专用,记录一下,以备下次需要的时候使用
使用方法:
在crontab里面添加如下行,每20分钟运行一次:
*/20 * * * * /usr/bin/python3 /usr/local/bin/update_all.py >> /var/log/update_all.log 2>&1
python版本:
#!/bin/env python3
# -*- coding: UTF-8 -*-
import requests
from requests.adapters import HTTPAdapter
import re
import json
import time
import sys
import os
req = requests.session()
req.mount('https://', HTTPAdapter(max_retries=9))
req.mount('http://', HTTPAdapter(max_retries=9))
base_url = "https://api.cloudflare.com/client/v4/zones"
def timeStamp():
tStamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
return tStamp
def prtMsg(msgType="None", msgConn="Not defined msg type."):
msgStamp = timeStamp()
if msgType == "err":
print(msgStamp + " [ERROR]" + ":" + msgConn)
elif msgType == "msg":
print(msgStamp + " [MESSAGE]" + ":" + msgConn)
else:
print(msgStamp + ":" + msgConn)
def get_ipv6_addr():
res = os.popen("""/sbin/ip -6 addr show $(/sbin/ip a|awk -F ":" '/ ens| eth/&&!/inet/ {print $2}'
)|awk '/inet6/&&/240e/ {print $2}'|awk -F "/" '{print $1}'""").read()
ipv6_list = [i for i in res.splitlines() if len(i) > 0]
if ipv6_list:
return (max(ipv6_list, key=len))
else:
return False
def get_ipv4_addr():
ip = ''
try:
res = req.get('https://myip.ipip.net', timeout=5).text
ip = re.findall(r'(\d+\.\d+\.\d+\.\d+)', res)
ip = ip[0] if ip else ''
except:
pass
return ip if ip else False
def get_cf_all_record(get_headers, zone_id):
api_url = "{0}/{1}/dns_records".format(base_url, zone_id)
res = req.get(api_url, headers=get_headers)
return json.loads(res.text)['result']
def cr_headers_data(email, apikey):
cf_headers = {
"Content-Type": "application/json",
"X-Auth-Email": email,
"X-Auth-Key": apikey
}
return cf_headers
def cr_record_dict(headers, zone_id):
rec_dict, zone_name = {}, ""
all_rec = get_cf_all_record(headers, zone_id)
for rec in all_rec:
if not zone_name:
zone_name = rec['zone_name']
domain_name = ""
if isinstance(rec, dict):
if "name" in rec.keys():
domain_name = "{0}-{1}".format(rec['name'], rec['type'])
if domain_name:
dom_dict = {'domain': rec['name'], 'id': rec['id'], 'type': rec['type'], 'content': rec['content'],
'proxied': rec['proxied']}
if dom_dict:
rec_dict[domain_name] = dom_dict
return (rec_dict, zone_name) if rec_dict else ({}, "")
def cr_post_data(post_type, post_domain, post_content, proxy_type):
post_data_json = {
"type": post_type,
"name": post_domain,
"content": post_content,
"ttl": 1,
"proxied": proxy_type
}
return post_data_json
def put_cf_record(zone_id, record_id, put_headers, post_data):
api_url = "{0}/{1}/dns_records/{2}".format(base_url, zone_id, record_id)
res = req.put(api_url, headers=put_headers, json=post_data)
if res.status_code == 200:
return True
else:
return False
def main():
email = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
apikey = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
zone_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
headers = cr_headers_data(email, apikey)
all_rec_dict, zone_name = cr_record_dict(headers, zone_id)
cf_list = ["www-AAAA", "gra-AAAA", "hub-AAAA", "ai-AAAA", "zbx-AAAA", "mail-TXT", "m-AAAA", "mail-A"]
ipv6 = get_ipv6_addr()
ipv4 = get_ipv4_addr()
print("-" * 20)
if ipv6 and ipv4:
prtMsg('msg', "Current IPV6 address is {0}".format(ipv6))
prtMsg('msg', "Current IPV4 address is {0}".format(ipv4))
else:
prtMsg('err', "GET IP address failed.")
sys.exit(10)
for cf in cf_list:
post_domain, post_type = cf.split("-")
if post_type == "AAAA":
post_content = ipv6
elif post_type == "A":
post_content = ipv4
else:
post_content = "v=spf1 mx ip4:{0} ip6:{1} ~all".format(ipv4, ipv6)
post_domain_name = "{0}.{1}".format(post_domain, zone_name)
if post_type == "TXT":
post_domain_name = zone_name
post_key = "{0}-{1}".format(post_domain_name, post_type)
if post_key in str(all_rec_dict):
if post_content != all_rec_dict[post_key]['content']:
proxy_type = all_rec_dict[post_key]['proxied']
record_id = all_rec_dict[post_key]['id']
post_data = cr_post_data(post_type, post_domain_name, post_content, proxy_type)
res = put_cf_record(zone_id, record_id, headers, post_data)
if res:
prtMsg('msg', 'Update {0} record for {1} success.'.format(post_domain_name, post_type))
else:
prtMsg('err', 'Update {0} record for {1} failed.'.format(post_domain_name, post_type))
else:
prtMsg('msg', 'No needs to update {0} record for {1}.'.format(post_domain_name, post_type))
else:
prtMsg('err', 'GET {0} Record for {1} failed.'.format(post_domain_name, post_type))
if __name__ == '__main__':
main()
shell版本:
#!/bin/sh
# --- Configuration ---
# Your Cloudflare credentials
EMAIL="[email protected]"
API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ZONE_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Base API URL for Cloudflare
BASE_URL="https://api.cloudflare.com/client/v4/zones"
# List of DNS records to check and update
# Format: <subdomain>-<type>
CF_LIST="www-AAAA is-AAAA mail-TXT m-AAAA mail-A"
# --- Functions ---
# Print messages with a timestamp
prt_msg() {
local msg_type="$1"
local msg_conn="$2"
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
if [ "$msg_type" = "err" ]; then
echo "$timestamp [ERROR]:$msg_conn"
elif [ "$msg_type" = "msg" ]; then
echo "$timestamp [MESSAGE]:$msg_conn"
else
echo "$timestamp:$msg_conn"
fi
}
# Get the IPv6 address
get_ipv6_addr() {
/sbin/ip -6 addr show br-lan | awk '/inet6/&&/240e/ {print $2}' | awk -F'/' '{print $1}' | sort | tail -n 1
}
# Get the IPv4 address
get_ipv4_addr() {
curl -s https://myip.ipip.net | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n 1
}
# Get Cloudflare DNS records and zone name
get_cf_records() {
local get_headers="-H Content-Type:application/json -H X-Auth-Email:$EMAIL -H X-Auth-Key:$API_KEY"
local api_url="${BASE_URL}/${ZONE_ID}/dns_records"
local zone_url="${BASE_URL}/${ZONE_ID}"
RECORDS=$(curl -s "$api_url" $get_headers)
ZONE_INFO=$(curl -s "$zone_url" $get_headers)
ZONE_NAME=$(echo "$ZONE_INFO" | jq -r '.result.name')
if [ -z "$ZONE_NAME" ]; then
prt_msg "err" "Failed to get Zone Name and DNS records."
exit 10
fi
}
# Update Cloudflare DNS record
put_cf_record() {
local record_id="$1"
local put_headers="-H Content-Type:application/json -H X-Auth-Email:$EMAIL -H X-Auth-Key:$API_KEY"
local post_data="$2"
local api_url="${BASE_URL}/${ZONE_ID}/dns_records/${record_id}"
local post_domain_name="$3" # <-- New argument
local post_type="$4" # <-- New argument
RESPONSE=$(curl -s -X PUT "$api_url" $put_headers --data-binary "$post_data")
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
if [ "$SUCCESS" = "true" ]; then
prt_msg "msg" "Update $post_domain_name record for $post_type success."
else
prt_msg "err" "Update $post_domain_name record for $post_type failed."
fi
}
# --- Main Logic ---
ipv6=$(get_ipv6_addr)
ipv4=$(get_ipv4_addr)
echo "--------------------"
if [ -z "$ipv6" ] || [ -z "$ipv4" ]; then
prt_msg "err" "Failed to get IP address."
exit 10
fi
prt_msg "msg" "Current IPv6 address is $ipv6"
prt_msg "msg" "Current IPv4 address is $ipv4"
get_cf_records
for CF_RECORD in $CF_LIST; do
POST_DOMAIN=$(echo "$CF_RECORD" | awk -F'-' '{print $1}')
POST_TYPE=$(echo "$CF_RECORD" | awk -F'-' '{print $2}')
case "$POST_TYPE" in
"AAAA")
POST_CONTENT="$ipv6"
;;
"A")
POST_CONTENT="$ipv4"
;;
"TXT")
POST_CONTENT="v=spf1 mx ip4:$ipv4 ip6:$ipv6 ~all"
;;
esac
if [ "$POST_TYPE" = "TXT" ]; then
POST_DOMAIN_NAME="$ZONE_NAME"
else
POST_DOMAIN_NAME="${POST_DOMAIN}.${ZONE_NAME}"
fi
RECORD_ID=$(echo "$RECORDS" | jq -r ".result[] | select(.name==\"$POST_DOMAIN_NAME\" and .type==\"$POST_TYPE\") | .id")
CURRENT_CONTENT=$(echo "$RECORDS" | jq -r ".result[] | select(.name==\"$POST_DOMAIN_NAME\" and .type==\"$POST_TYPE\") | .content")
PROXIED=$(echo "$RECORDS" | jq -r ".result[] | select(.name==\"$POST_DOMAIN_NAME\" and .type==\"$POST_TYPE\") | .proxied")
if [ -n "$RECORD_ID" ] && [ -n "$CURRENT_CONTENT" ]; then
if [ "$POST_CONTENT" != "$CURRENT_CONTENT" ]; then
POST_DATA=$(jq -n \
--arg type "$POST_TYPE" \
--arg name "$POST_DOMAIN_NAME" \
--arg content "$POST_CONTENT" \
--argjson proxied "$PROXIED" \
'{type: $type, name: $name, content: $content, ttl: 1, proxied: $proxied}')
put_cf_record "$RECORD_ID" "$POST_DATA"
else
prt_msg "msg" "No needs to update ${POST_DOMAIN_NAME} record for ${POST_TYPE}."
fi
else
prt_msg "err" "GET ${POST_DOMAIN_NAME} Record for ${POST_TYPE} failed."
fi
done
