Chuyên mục
Network

Cập nhật IP động cho tên miền qua CloudFlare để truy cập homelab tại nhà

Có hai cách để chúng ta có thể truy cập homelab từ bên ngoài:

  • Truy cập trực tiếp bằng Public IP của modem
  • Truy cập qua tên miền được cấu hình trỏ đến Public IP.

Đa số mọi người sẽ chọn cách thứ hai vì nó đơn giản, dễ nhớ hơn so với chuỗi dãy số IP xxx.xxx.xxx.xxx. Tuy nhiên nếu mạng ở nhà sử dụng IP động (đa số trường hợp), tên miền cần phải được tự động cập nhật mỗi khi Public IP thay đổi để tránh kết nối bị lỗi.

Hiện tại mình đang dùng 1 bash script trên Raspberry Pi để tự động cập nhật IP động cho tên miền qua dịch vụ CloudFlare. Nhờ vậy tên miền luôn được trỏ đến IP mới nhất được cấp cho modem mạng ở nhà.

Dưới đây là hướng dẫn chi tiết cách thiết lập

Yêu cầu

  • Đã có sẵn tên miền và đã có tài khoản Cloudflare
  • Tên miền đã được chuyển về CloudFlare quản lý
  • Máy tính / máy ảo chạy Linux (Ubuntu / Arch / CentOS / …) hoặc Raspberry Pi (đời nào cũng được)

Cài đặt Cloudflare.sh script

Bạn cần truy cập SSH vào máy tính để thiết lập script.

Tạo file cloudflare.sh trong thư mục $HOME

cd ~
mkdir cloudflare
cd cloudflare
nano cloudflare.sh

Nhập vào nội dung sau

#!/bin/bash

# Forked from benkulbertis/cloudflare-update-record.sh
# CHANGE THESE

# API Token (Recommended)                                                                      #####
auth_token="xxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain and DNS record for synchronization
zone_identifier="f1nd7h3fuck1n6z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name="ipv4.example.org"                     # Which record you want to be synced

# DO NOT CHANGE LINES BELOW

# SCRIPT START
echo -e "Check Initiated"

# Check for current external network IP
ip=$(curl -s4 https://icanhazip.com/)
if [[ ! -z "${ip}" ]]; then
  echo -e "  > Fetched current external network IP: ${ip}"
else
  >&2 echo -e "Network error, cannot fetch external network IP."
fi

# The execution of update
if [[ ! -z "${auth_token}" ]]; then
  header_auth_paramheader=( -H '"Authorization: Bearer '${auth_token}'"' )
else
  header_auth_paramheader=( -H '"X-Auth-Email: '${auth_email}'"' -H '"X-Auth-Key: '${auth_key}'"' )
fi

# Seek for the record
seek_current_dns_value_cmd=( curl -s -X GET '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records?name='${record_name}'&type=A>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )
record=`eval ${seek_current_dns_value_cmd[@]}`

# Can't do anything without the record
if [[ -z "${record}" ]]; then
  >&2 echo -e "Network error, cannot fetch DNS record."
  exit 1
elif [[ "${record}" == *'"count":0'* ]]; then
  >&2 echo -e "Record does not exist, perhaps create one first?"
  exit 1
fi

# Set the record identifier from result
record_identifier=`echo "${record}" | sed 's/.*"id":"//;s/".*//'`

# Set existing IP address from the fetched record
old_ip=`echo "${record}" | sed 's/.*"content":"//;s/".*//'`
echo -e "  > Fetched current DNS record value   : ${old_ip}"

# Compare if they're the same
if [ "${ip}" == "${old_ip}" ]; then
  echo -e "Update for A record '${record_name} (${record_identifier})' cancelled.\\n  Reason: IP has not changed."
  exit 0
else
  echo -e "  > Different IP addresses detected, synchronizing..."
fi

# The secret sause for executing the update
json_data_v4="'"'{"id":"'${zone_identifier}'","type":"A","proxied":true,"name":"'${record_name}'","content":"'${ip}'","ttl":120}'"'"
update_cmd=( curl -s -X PUT '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records/'${record_identifier}'>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )

# Execution result
update=`eval ${update_cmd[@]} --data $json_data_v4`

# The moment of truth
case "$update" in
*'"success":true'*)
  echo -e "Update for A record '${record_name} (${record_identifier})' succeeded.\\n  - Old value: ${old_ip}\\n  + New value: ${ip}";;
*)
  >&2 echo -e "Update for A record '${record_name} (${record_identifier})' failed.\\nDUMPING RESULTS:\\n${update}"
  exit 1;;
esac

Bạn cần phải điền thông tin vào các dòng sau:

  • auth_token: Cloudflare API Token
  • zone_identifier: Zone ID
  • record_name: Tên miền

Tạo Cloudflare API Token

Truy cập vào trang Cloudflare API Token để tạo Token mới

Bấm vào Create Token
Chọn Use Template ở mục Edit Zone DNS
Kéo xuống mục Zone Resources, chọn tên miền bạn muốn sử dụng để cập nhật IP sau đó bấm Continue to Summary
Bấm Create Token
Token đã tạo xong

Bạn cần lưu lại Token vừa mới tạo để lưu vào script đã tạo. Token này sẽ không hiện ra lại trên Cloudflare vì lý do bảo mật. Nếu bạn quên lưu, cần phải xoá Token và tạo lại cái mới.

Tìm thông tin Zone ID

Từ trang chủ Cloudflare, bạn bấm vào tên miền muốn sử dụng để cập nhật IP. Sau đó kéo xuống dưới sẽ thấy mục Zone ID. Lưu lại thông số này để điền vào script

Lưu lại thông tin Zone ID

Tạo A Record cho tên miền

Ví dụ mình muốn sử dụng tên miền alibaba.thuanbui.me để truy cập đến homelab ở nhà. Bấm vào Add record và tạo một A record mới.

  • Name: điền vào alibaba
  • IPv4: điền tạm 1.1.1.1, thông số này sẽ được tự động cập nhật về IP của modem sau khi chạy script.
Tạo A Record cho subdomain alibaba.thuanbui.me

Cập nhật script

Thông tin cần thiết đã có đầy đủ, mình cập nhật lại vào file cloudflare.sh và lưu lại

#!/bin/bash

# Forked from benkulbertis/cloudflare-update-record.sh
# CHANGE THESE

# API Token (Recommended)                                                                  #####
auth_token="LrAB--xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain and DNS record for synchronization
zone_identifier="48bxxxxxxxxxxxxxxxxxxxx60" # Can be found in the "Overview" tab of your domain
record_name="alibaba.thuanbui.me"                     # Which record you want to be synced

# DO NOT CHANGE LINES BELOW

# SCRIPT START
echo -e "Check Initiated"

# Check for current external network IP
ip=$(curl -s4 https://icanhazip.com/)
if [[ ! -z "${ip}" ]]; then
  echo -e "  > Fetched current external network IP: ${ip}"
else
  >&2 echo -e "Network error, cannot fetch external network IP."
fi

# The execution of update
if [[ ! -z "${auth_token}" ]]; then
  header_auth_paramheader=( -H '"Authorization: Bearer '${auth_token}'"' )
else
  header_auth_paramheader=( -H '"X-Auth-Email: '${auth_email}'"' -H '"X-Auth-Key: '${auth_key}'"' )
fi

# Seek for the record
seek_current_dns_value_cmd=( curl -s -X GET '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records?name='${record_name}'&type=A>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )
record=`eval ${seek_current_dns_value_cmd[@]}`

# Can't do anything without the record
if [[ -z "${record}" ]]; then
  >&2 echo -e "Network error, cannot fetch DNS record."
  exit 1
elif [[ "${record}" == *'"count":0'* ]]; then
  >&2 echo -e "Record does not exist, perhaps create one first?"
  exit 1
fi

# Set the record identifier from result
record_identifier=`echo "${record}" | sed 's/.*"id":"//;s/".*//'`

# Set existing IP address from the fetched record
old_ip=`echo "${record}" | sed 's/.*"content":"//;s/".*//'`
echo -e "  > Fetched current DNS record value   : ${old_ip}"

# Compare if they're the same
if [ "${ip}" == "${old_ip}" ]; then
  echo -e "Update for A record '${record_name} (${record_identifier})' cancelled.\\n  Reason: IP has not changed."
  exit 0
else
  echo -e "  > Different IP addresses detected, synchronizing..."
fi

# The secret sause for executing the update
json_data_v4="'"'{"id":"'${zone_identifier}'","type":"A","proxied":true,"name":"'${record_name}'","content":"'${ip}'","ttl":120}'"'"
update_cmd=( curl -s -X PUT '"<https://api.cloudflare.com/client/v4/zones/'${zone_identifier}'/dns_records/'${record_identifier}'>"' "${header_auth_paramheader[@]}" -H '"Content-Type: application/json"' )

# Execution result
update=`eval ${update_cmd[@]} --data $json_data_v4`

# The moment of truth
case "$update" in
*'"success":true'*)
  echo -e "Update for A record '${record_name} (${record_identifier})' succeeded.\\n  - Old value: ${old_ip}\\n  + New value: ${ip}";;
*)
  >&2 echo -e "Update for A record '${record_name} (${record_identifier})' failed.\\nDUMPING RESULTS:\\n${update}"
  exit 1;;
esac

Mặc định, script này sẽ kích hoạt chế độ proxy trên Cloudflare nhằm mục đích ẩn IP của modem, giảm nguy cơ bị tấn công vào mạng nhà. Nếu vì lý do nào đó không muốn ẩn IP của mình thì có thể thay đổi thông số "proxied":true thành "proxied":false

Chạy thử nghiệm

Thiết lập quyền thực thi cho file cloudflare.sh

chmod +X cloudflare.sh

Chạy thử xem nào

./cloudflare.sh
[email protected]:~/cloudflare $ ./cloudflare.sh
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
Check Initiated
  > Fetched current external network IP: 27.xx.xx.xx
  > Fetched current DNS record value   : 1.1.1.1
  > Different IP addresses detected, synchronizing...
Update for A record 'alibaba.thuanbui.me (48bxxxxxxxxxxxxxxxxxxxx60)' succeeded.
  - Old value: 1.1.1.1
  + New value: 27.xx.xx.xx

Quay lại Cloudflare, tên miền alibaba.thuanbui.me, mình thấy đã được tự động cập nhật với IP mới. Ngon lành!

Thiết lập cron tự động

Việc cuối cùng cần làm là thiết lập cron để script tự động chạy 24/7. Sau đó, không cần bận tâm đến nó nữa.

crontab -e

Mình chỉnh cho script tự động chạy sau mỗi 10 phút bằng cách thêm dòng này vào dưới cùng

*/10 * * * * /bin/bash /home/thuanbui/cloudflare/cloudflare.sh

Chú ý nhớ thay /home/thuanbui/cloudflare/cloudflare.sh thành đường dẫn đến nơi lưu file cloudflare.sh của bạn.

Nếu bài viết của mình mang đến thông tin, kiến thức hữu ích cho bạn, đừng ngại mời mình ly bia để có thêm động lực chia sẻ nhiều hơn nữa. Cám ơn bạn!

Bởi Thuận Bùi

Runner at Yêu Chạy Bộ. Blogger at Ba Lô & Dép Lào. Web Developer at TB's Blog.
Follow me: Facebook / Instagram

12 trả lời trong “Cập nhật IP động cho tên miền qua CloudFlare để truy cập homelab tại nhà”

bạn cho hỏi mình bị lỗi nay khi chay ./cloudflare.sh
./cloudflare.sh: command substitution: line 20: syntax error near unexpected token `)’
./cloudflare.sh: command substitution: line 20: `curl -s4 )’
Network error, cannot fetch external network IP.
Network error, cannot fetch DNS record.

Mình mới kiểm tra lại, dòng 20 bị dư 2 dấu < > nên đã cập nhật lại. Bạn chỉnh code lại theo bản mới ở trên nhé. Cám ơn bạn đã thông báo.

em chạy lệnh thì nó không hiện lỗi dòng nào, nhưng nó báo không fetch dns.

[email protected]:/home/pi/cloudflare# bash cloudflare.sh
Check Initiated
> Fetched current external network IP: 113.113.173.201
Network error, cannot fetch DNS record.

thêm nữa, phần Auth_token nó có 2 dấu [–]
nhưng trong phần token em lấy thì nó không có dấu gạch đó. Em phải điền sao cho đúng?

Nhiều khả năng là em nhập sai API Token rồi. Do sai nên nó không truy cập Cloudflare được nên báo lỗi Cannot fetch DNS Record.
Chú ý là cần lấy API Token, không phải Global API nhé.

1. Về API token, em làm theo hướng dẫn của anh để lấy, nhưng API token khi nhập trong code nó định dạng thêm –.
Trong khi API Token của em lấy thì nó không có cái dấu –.
Em phải điền sao cho đúng.
Ví dụ:
API token của em là ABCDEFGHIJKLM.
Khi nhập vào đoạn code của anh sẽ cắt 4 ký tự đầu ra ABCD–EFGH… ==> như vậy có đúng không?

2. Lỗi trả về khi em chạy file bằng 2 lệnh nó khác nhau.
Lệnh sh:
[email protected]:~/cloudflare $ sh cloudflare.sh
-e Check Initiated
cloudflare.sh: 20: cloudflare.sh: [[: not found
-e Network error, cannot fetch external network IP.
cloudflare.sh: 28: cloudflare.sh: Syntax error: “(” unexpected (expecting “fi”)

Lệnh bash
[email protected]:~/cloudflare $ bash cloudflare.sh
Check Initiated
> Fetched current external network IP: xxx.113.173.201
Network error, cannot fetch DNS record.

API Token nó hiện ra sao thì paste vô y hệt vậy, không có chia ra gì hết nha em. Thêm — vô là nó sai rồi.
Bản chất cloudflare.sh nó là câu lệnh rồi nên em gõ `sh cloudflare.sh` là sai. Chỉ cần gõ `./cloudflare.sh` là được.

Check Initiated
> Fetched current external network IP: 155.64.16.13
Network error, cannot fetch DNS record.

Admin giúp mình với ạ. Mình đã check đi check lại API token, zone ID đúng nhưng vẫn bị lỗi trên

Báo lỗi Network error, cannot fetch DNS record nghĩa là kết nối đến Cloudflare không thành công. Bạn cần kiểm tra lại các thông số: API Token, Zone ID, record name

Trang API của Cloudflare không bị gì cả nhé. Bạn có thể test curl theo lệnh này: curl -X GET "https://api.cloudflare.com/client/v4/zones/Thay-ZONE-ID-vô-đây" -H "Content-Type:application/json" -H "Authorization: Bearer Thay-API-Token-vô-đây". Lưu ý là gõ lệnh vào Terminal, không phải gõ vào trình duyệt. Nếu thông tin chính xác nó sẽ trả về kết quả tương tự như sau

{"result":{"id":"aeb40eaxxxxxxf51a0b","name":"thuanbui.me","status":"active","paused":false,"type":"full","development_mode":0,"name_servers":["ian.ns.cloudflare.com","melinda.ns.cloudflare.com"],"original_name_servers":["dns1.registrar-servers.com","dns2.registrar-servers.com","dns3.registrar-servers.com","dns4.registrar-servers.com","dns5.registrar-servers.com"],"original_registrar":"namesilo, llc r203-me","original_dnshost":null,"modified_on":"2020-04-24T22:27:18.820906Z","created_on":"2016-03-12T12:06:29.074831Z","activated_on":"2016-03-12T12:08:53.006040Z","meta":{"step":4,"custom_certificate_quota":0,"page_rule_quota":3,"phishing_detected":false,"multiple_railguns_allowed":false},"owner":{"id":"d139f5e53xxxxxxc0d8","type":"user","email":"[email protected]"},"account":{"id":"b9xxxxxx9d4bb","name":"[email protected]"},"permissions":["#access:edit","#access:read","#analytics:read","#app:edit","#auditlogs:read","#billing:edit","#billing:read","#cache_purge:edit","#dns_records:edit","#dns_records:read","#healthchecks:edit","#healthchecks:read","#image:edit","#image:read","#lb:edit","#lb:read","#legal:edit","#legal:read","#logs:edit","#logs:read","#member:edit","#member:read","#organization:edit","#organization:read","#ssl:edit","#ssl:read","#stream:edit","#stream:read","#subscription:edit","#subscription:read","#teams:edit","#teams:pii","#teams:read","#teams:report","#waf:edit","#waf:read","#waitingroom:edit","#waitingroom:read","#webhooks:edit","#webhooks:read","#worker:edit","#worker:read","#zaraz:edit","#zaraz:read","#zone:edit","#zone:read","#zone_settings:edit","#zone_settings:read"],"plan":{"id":"0feeeeeeeexxxxxxe","name":"Free Website","price":0,"currency":"USD","frequency":"","is_subscribed":false,"can_subscribe":false,"legacy_id":"free","legacy_discount":false,"externally_managed":false}},"success":true,"errors":[],"messages":[]}

Em chạy lệnh “curl” trên thì ra đúng kết quả như anh nói rồi ạ, nhưng chạy /.cloudflare.sh thì vẫn bị lỗi “Network error, cannot fetch DNS record”

Anh giúp em với ạ

Nếu chạy curl ra đúng thì có nghĩa các thông số đều đúng. Chạy file sh báo lỗi thì nhiều khả năng là phần thiết lập thông số trong file cloudflare.sh bị sai chỗ nào rồi. Kiểm tra lại 5 thông số này:

  • auth_email: điền email tài khoản Cloudflare
  • auth_key: bỏ trống
  • auth_toke: điền vào Token Key
  • zone_identifier: mã Zone
  • record_name: tên miền sub domain

Trả lời