群晖通过cloudflare的API自动更新DDNS
2025-01-23 18:32 ≈ 1.8k字 ≈ 7分钟

之前我一直使用freedns做ddns,其实也挺方便,freedns有一个免费的更新dns的接口,使用curl直接更新即可

curl http://sync.afraid.org/u/*******,但是证书的续订就比较麻烦了,虽然用acme.sh也能实现,但是因为freedns没有标准接口,实现起来总觉得不那么合适。再加上freedns总是把二级域名给别人使用,一咬牙干脆把域名迁移到cloudflare上去管理了

1. 实现DDNS

用cloudflare实现ddns其实比freedns要麻烦一点点,也是要写一个sh文件,但是不像freedns一句话就能搞定,需要调用标准接口。我其实也是建议放到路由器上去,因为我的群晖其实常年挂着代理,不知道为什么,系统代理之后你去ip138可以找到真实ip,但是用脚本获取ip总是获取到代理的ip,因此放到路由器上是最好的选择。当然如果你还是想用群晖的计划任务,那么就把脚本放到群晖的某个位置,然后用计划任务即可,命令用bash执行这个sh文件就行了

写脚本之前,先取下面4个值:API_TOKEN、ZONE_ID、RECORD_ID、RECORD_NAME

1.1. 取四个参数

1.1.1. API_TOKEN

首先在用户个人资料这里创建一个DNS的令牌

选择编辑权限然后保存下来,这个令牌保存好,只显示一次

1.1.2. Cloudflare Zone ID

这个在网站的概述里面,右下角能找到,Zone ID 通常是一个32位的十六进制字符串,例如:0123456789abcdef0123456789abcdef

1.1.3. DNS记录ID(需要更新的记录ID)

这个可以用开发者工具去查,不过更简单的是用api去查

1
2
3
curl -X GET "https://api.cloudflare.com/client/v4/zones/<你的Zone ID>/dns_records?name=<你的RECORD_NAME>" \
-H "X-Auth-Email: <你的Cloudflare邮箱>" \
-H "X-Auth-Key: <你的CloudflareAPI Token>"

直接替换掉Zone ID、邮箱、Token和RECORD_NAME 就能找到DNS记录的ID了

1.1.4. RECORD_NAME

这个就是你要更新IP的域名,可以是根域名,也可以是子域名,反正就是你要更新A记录的那一条

1.2. 写脚本

上面四个值拿到之后就可以写脚本了。ssh进入路由器,拿到root权限,然后选一个位置,我是保存在/usr里面了,直接用vi ddns.sh,打开编辑,按I进入编辑模式,复制下面的脚本进去,记得替换四个值

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/bin/sh
# Cloudflare API令牌
API_TOKEN=""
# Cloudflare Zone ID
ZONE_ID=""
# DNS记录ID(需要更新的记录ID)
RECORD_ID=""
# 需要更新的域名,subdomain.example.com
RECORD_NAME=""

# 获取当前公网IP
CURRENT_IP=$(curl -s http://ipv4.icanhazip.com)
echo "当前公网IP: $CURRENT_IP"

# 获取现有的DNS记录IP
DNS_RECORD_RESPONSE=$(curl -s -k -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json")

# 提取现有DNS记录的IP地址
DNS_RECORD_IP=$(echo "$DNS_RECORD_RESPONSE" | sed -n 's/.*"content":"\([^"]*\)".*/\1/p')
echo "提取到的DNS记录IP: $DNS_RECORD_IP"


# 检查IP是否需要更新
if [ "$CURRENT_IP" == "$DNS_RECORD_IP" ]; then
echo "IP地址未改变,无需更新"
else
echo "IP地址已改变,开始更新DNS记录"

# 更新DNS记录
RESPONSE=$(curl -s -k -X PUT "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${RECORD_NAME}\",\"content\":\"${CURRENT_IP}\",\"ttl\":120,\"proxied\":false}")

# 检查更新结果
if echo "$RESPONSE" | grep -q "\"success\":true"; then
echo "DNS记录已成功更新为新IP: ${CURRENT_IP}"
else
echo "更新DNS记录失败:$RESPONSE"
fi
fi

PS:如果你从其他地方复制的脚本,在小米路由器上无法运行,或者小米路由器用curl访问https的地址没有返回值,记得curl后面的-k参数要加

1.3. 计划任务

把脚本写好之后,直接crontab -e加一条计划任务即可

*/10 * * * * /usr/ddns.sh

每过10分钟执行一次,如果你的动态IP更新是一周一次,你可以酌情调整计划任务

2. 使用acme.sh自动给群晖更新证书

建议把acme.sh直接装在群晖上,不需要用docker,这样在证书续订的时候可以大大简化验证流程。

首先打开SSH端口,然后用任何工具登入群晖,我使用的是Termius,感觉mac上这个app蛮好用的。

1
2
3
4
5
6
7
sudo su
cd ~
curl https://get.acme.sh | sh -s [email protected] --force
cd .acme.sh
./acme.sh --install --nocron --home /usr/local/share/acme.sh --accountemail "[email protected]"
source ~/.profile
cd /usr/local/share/acme.sh

一行一行的输入即可,然后就是获取Account_ID和Token,Token和上面的用同一个就行,因为肯定是同一个,然后Account_ID就在Zone ID的同一个页面,有了这两个值,继续下面的操作

1
2
3
4
5
6
export CF_Account_ID="*****************" 
export CF_Token="*****************"
# 申请证书,--server letsencrypt 指定申请 letsencrypt 证书
# -d example.com 指定要申请证书的域名是 example.com
# -d *.example.com 说明申请的证书是泛域名证书
./acme.sh --issue --server letsencrypt --home . -d example.com --dns dns_cf --keylength 2048

正常来讲,这里已经能够获取到证书了,接下来,进行证书部署,这里直接部署到本地的话,可以直接使用临时管理员用户部署,这也是为什么不建议安装到docker里面的原因。如果需要给远程服务器部署证书,就比较复杂,可以参考这里

部署的时候需要注意,如果你是用ip登陆,http访问的话,那么就不需要额外的操作,如果你像我一样使用域名访问,同时开启了ssl的话,需要增加两个环境变量

1
2
3
4
5
6
7
# 设置使用临时管理员账户
export SYNO_USE_TEMP_ADMIN=1
export SYNO_SCHEME="https"
# 这里的端口号需要改成你设置的https的端口号,默认是5001
export SYNO_PORT="9530"
# 最后就是部署证书了,使用写好的hook直接搞定
./acme.sh --deploy --insecure --home . -d nas.igerm.ee --deploy-hook synology_dsm

2.1 自动续订

实际上使用自定义cronjob也是可以实现的,就像我自动更新ddns一样。但是如果你在群晖里面使用cronjob的话,群晖的安全顾问告诉你严重警告,虽然这是你自己添加的,但是看到这个警告的心情并不好,因此可以直接在群晖的计划任务里面进行续订。

在 DSM 控制面板中,打开“任务计划程序”并为用户定义的脚本创建一个新的计划任务。

选择用户账号为root,然后任务设置里面自定义用户脚本输入:

启动时间我设置的是每个月第一个周一,你也可以自行设定

1
2
# renew certificates 
/usr/local/share/acme.sh/acme.sh --cron --home /usr/local/share/acme.sh