前几天借着 Github 的学生优惠在 name.com 上嫖了一个域名,这个域名是带 SSL 的,而恰巧家里的主机因为 443 端口被封的缘故无法通过 Let’s Encrypt 获得证书,所以把这个域名给家里的主机升级 HTTPS 再合适不过了。

唯一的问题是 name.com 只提供静态 DNS 服务,而服务器放在家里自然是动态 IP 的。虽然 IP 不是经常换但是如果换了 IP 没有及时更新记录就会出问题,何况手动更新记录也有点烦。有两个方案:

  1. 我目前使用的是花生壳的 DDNS,这个的记录是动态更新的。我可以在新域名下面新建一条 CNAME 记录指向动态域名。这样的好处是省事,坏处是可能会增加 DNS 解析的时间,我目前还不清楚 HTTPS 要不要求 CNAME 指向的域名也有证书,如果要求的话这个方法就更不行了。
  2. name.com 作为一家比较大的域名商有自己的 API 以及完备的文档,可以自己写一个定时更新脚本来实现类似 DDNS 的功能。

经过考虑之后我选择后者。自己写的脚本如下:

#!/bin/sh

domain='<DOMAIN>'
credential='<ACCESS TOKEN>'
ttl=300
interval=10

echo 'Querying type A record ID...'
rec_id=$(curl -su $credential "https://api.name.com/v4/domains/${domain}/records" | jq '.records|map(select(.type=="A"))|.[0].id')
echo 'Found type A record ID:' $rec_id

while true; do
    res_ip=$(host $domain | grep -ohP '\b(?:\d{1,3}\.){3}\d{1,3}\b')
    real_ip=$(curl -s myip.ipip.net | grep -ohP '\b(?:\d{1,3}\.){3}\d{1,3}\b')
    echo 'Resolution IP:' $res_ip 'Real IP:' $real_ip
    if [[ $real_ip != $res_ip ]]; then
        echo "Resolution mismatch! Updating record..."  
        req_data="{\"type\":\"A\",\"fqdn\":\"${domain}.\",\"answer\":\"${real_ip}\",\"ttl\":${ttl}}"
        echo "Request data:" $(echo $req_data | jq '.')
        req_res=$(curl -m 30 -su $credential "https://api.name.com/v4/domains/${domain}/records/${rec_id}" \
            -X PUT -H 'Content-Type: application/json' --data $req_data | jq '.')
        echo "Request result:" $req_res 
        sleep $ttl
    fi
    sleep $interval
done

其实很简单,主要分为一下几个部分:

  1. 记录 id 的获取。name.com 对每一个记录都分配了一个 id 以便于 API 操作,这个 id 在网站管理面板上是不可见的,因此需要在运行时查询,命令为 curl -su $credential "https://api.name.com/v4/domains/${domain}/records"。查询之后需要解析 JSON,这里我使用的是 jq 这个第三方 JSON parser。
  2. 当前 DNS 解析的 IP。这个使用 host 结合 grep 提取 IP 字符串即可。
  3. 获取本机真实 IP。这个接口就多了,我这里用的是 myip.ipip.net 的接口。据我所知还有接口是直接返回 IP 字符串的,还可以后处理的功夫。
  4. 如果解析 IP 和真实 IP 不符,那就调用 API 更新记录。注意在更新完之后最好等待 TTL 的时间以避免更新生效前多次更新。

写完这个脚本再写一个配套的 systemd service file,然后 systemctl 挂在后台运行就好了。运行到现在效果非常好。如果有 name.com 的域名同时也有类似需求的或许可以参考一下。