上一篇文章无意中发现了我的公网IPv6地址是能够联通的，并且实际联机体验较为不错。但由于没钱买商宽，所以没有静态IP，而每次IP变动都会带来麻烦，因此本篇将探索如何使用Cloudflare API来更新域名DNS，四舍五入成为一个dDNS。IPv6公网地址虽然好，但毕竟是动态的。虽然Minecraft本身对于服务器的地址没什么要求，但一些mod，例如小地图，它使用服务器的地址来区分数据。也就是说，当IP变动时，即便服务器还是原来的服务器，但对于小地图mod来说，这就是一个新的服务器，因此之前加载的地图缓存和路经点也就全没了。前些日子刚好想起来我还有个域名托管在Cloudflare，主要是利用它的tunnel服务来将内网或一些临时性的服务分享到公网上（一方面是懒得更新DNS，另一方面是懒得搞SSL证书）。我原本也想用tunnel服务将Minecraft服务器映射出去，但简单测试后发现延迟波动比较大，而且延迟普遍有点高，不适合联机。这时我突然意识到，Cloudflare提供了完备的API，即便官方没有提供ddns客户端，我也可以通过调用API的方式更新域名的DNS记录。想来想去，由于我用的是Linux系统，因此实现的最佳方式便是bash，恰好Cloudflare的API文档也提供了多种多样的调用例子，其中就包含了Shell/curl：https://developers.cloudflare.com/api/operations/zones-get要访问Cloudflare API，第一步就是要获取访问密钥，也就是所谓的API Token，这一步在账户设置里就可以找到。关键点在于创建的Token不光要能Edit Zone设置，还要能Read，不然后面第一步获取id就失败了。我一开始以为Edit蕴含了Read权限，所以是我调用的方式不对，结果发现是我token的权限没给。api_email="your login email address" api_key="your api token"在Cloudflare中，网页中显示的website，实际上是DNS Zone。要操作一个DNS Zone，我们不能直接用名字，我们要用它的ID。根据文档，我们可以直接按照Zone的名字（也就是域名）进行筛选：zone_name="example.com" zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" \ -H "X-Auth-Email: $api_email" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" | jq -r .result[0].id ) echo Zone $zone_name id is: $zone_id这里我们用jq这个工具从返回的Json中获取我们想要的结果。拿到Zone ID之后，若要修改一个记录（Record），我们同样需要ID。根据文档，我们使用记录的完整域名和zone id进行查询：record_name="ddns.example.com" record_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$record_name" \ -H "X-Auth-Email: $api_email" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" | jq -r .result[0].id ) echo Record $record_name id is: $record_id同样地，使用jq来解析Json。拿到了这两个ID我们就可以更新DNS记录了。这里要先在网页面板上创建好一个AAAA记录（用于IPv6），至于实际地址可以随便写，比如::1。关于TTL，因为IP可能会随时变动，因此直接设置为最小值，以便IP更新后朋友那边可以直接获取到最新的记录。那么，IPv6怎么获取呢？经过一番awk的现学现卖，我得到了如下命令：v6Addr=$(ip -6 addr show scope global | grep /128 | awk -F '[ \t]+|/' '{print $3}' | grep -v ^fd7a)在上一篇文章中可以得知，我的NUC是能够获得一个/128的公网IP。我先通过ip addr命令的变体找出所有公网IPv6地址，然后寻找后缀是/128的地址，使用awk命令提取出IP，随后排除掉来自tailscale的fd7a开头的地址。最后将字符串形式的IP存入到v6Addr这个变量中。根据文档，我们可以使用PATCH动作来只更新我们需要的字段：result_json=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" \ -H "X-Auth-Email: $api_email" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" \ --data "{\"type\":\"AAAA\",\"content\":\"$v6Addr\"}") # check the result and print if [[ "$(echo $result_json | jq .success)" == "true" ]]; then echo Done! else echo -e "Failed to update DDNS. CF API result: $result_json" exit 1 fi这里我们确保更新的是AAAA记录，并且记录的内容是我们获取到的公网IP。最后同样使用jq判断一下返回的数据是否包含成功。成功则直接退出，若失败则将结果打印到控制台中。至此，我的朋友和我联机时就再也不用把小地图和路经点复制来复制去了。完整版脚本这个脚本非常原始，中间命令没有错误检测，并且只能设置IPv6，并且IP只能从本地网卡获得。使用前请务必确认你理解这个脚本的全部内容，使用此脚本的风险和后果由你自行承担。#!/bin/bash # basic info api_email="your login email address" api_key="your api token" zone_name="example.com" record_name="ddns.example.com" # the command to pull ipv6 address v6Addr=$(ip -6 addr show scope global | grep /128 | awk -F '[ \t]+|/' '{print $3}' | grep -v ^fd7a) echo Using IPv6 address: $v6Addr # Call CF API to get zone id and record id zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" \ -H "X-Auth-Email: $api_email" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" | jq -r .result[0].id ) echo Zone $zone_name id is: $zone_id # record id record_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?type=AAAA&name=$record_name" \ -H "X-Auth-Email: $api_email" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" | jq -r .result[0].id ) echo Record $record_name id is: $record_id # Update the record result_json=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" \ -H "X-Auth-Email: $api_email" \ -H "Authorization: Bearer $api_key" \ -H "Content-Type: application/json" \ --data "{\"type\":\"AAAA\",\"content\":\"$v6Addr\"}") # check the result and print if [[ "$(echo $result_json | jq .success)" == "true" ]]; then echo Done! else echo -e "Failed to update DDNS. CF API result: $result_json" exit 1 fi-全文完-