This repository has been archived by the owner on Jan 29, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds/updates records; uses jq instead of grep (#2)
* adds a record if it doesn't exist yet; uses jq to extract information more reliably from cloudflare responses * configuration validation, and defaults for TTL and PROXY; better cloudflare response parsing and error handling/reporting; formatting and comments * added bash to docker image; errors out when trying to update a CNAME record; creates a new record if one doesn't exist if FORCE_CREATE; runs only once w/o setting up cron if RUNONCE is set; doc updates; removed defaults of env vars (script reports what's missing instead); linted script
- Loading branch information
1 parent
62f031b
commit 4ae7175
Showing
5 changed files
with
192 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,21 @@ | ||
FROM alpine:3.6 | ||
|
||
ADD cloudflare.sh /cloudflare.sh | ||
ADD cron /var/spool/cron/crontabs/root | ||
ADD crontab /var/spool/cron/crontabs/root | ||
|
||
ENV ZONE=example.com \ | ||
HOST=example.com \ | ||
[email protected] \ | ||
API=1111111111111111 \ | ||
TTL=1 \ | ||
PROXY=true \ | ||
DEBUG=false | ||
ENV ZONE= \ | ||
HOST= \ | ||
EMAIL= \ | ||
API= \ | ||
TTL= \ | ||
PROXY= \ | ||
DEBUG= \ | ||
FORCE_CREATE= \ | ||
RUNONCE= | ||
|
||
RUN apk add --update curl && \ | ||
RUN apk add --update bash jq curl && \ | ||
rm -rf /var/cache/apk/* && \ | ||
chmod +x /cloudflare.sh | ||
|
||
CMD /cloudflare.sh && \ | ||
crond -f | ||
test -z "$RUNONCE" && crond -f |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
# joshuaavalon/cloudflare-ddns | ||
[![version](https://images.microbadger.com/badges/version/joshuaavalon/cloudflare-ddns.svg)](https://microbadger.com/images/joshuaavalon/cloudflare-ddns) | ||
[![](https://images.microbadger.com/badges/image/joshuaavalon/cloudflare-ddns.svg)](https://microbadger.com/images/joshuaavalon/cloudflare-ddns) | ||
![Docker Pulls](https://img.shields.io/docker/pulls/joshuaavalon/cloudflare-ddns.svg) | ||
![Docker Stars](https://img.shields.io/docker/stars/joshuaavalon/cloudflare-ddns.svg?colorB=dfb317) | ||
[![Docker Automated build](https://img.shields.io/docker/automated/joshuaavalon/cloudflare-ddns.svg)](https://hub.docker.com/r/joshuaavalon/cloudflare-ddns/) | ||
[![Docker build](https://img.shields.io/docker/build/joshuaavalon/cloudflare-ddns.svg)](https://hub.docker.com/r/joshuaavalon/cloudflare-ddns/) | ||
[![version](https://images.microbadger.com/badges/version/joshuaavalon/cloudflare-ddns.svg)](https://microbadger.com/images/joshuaavalon/cloudflare-ddns) | ||
[![](https://images.microbadger.com/badges/image/joshuaavalon/cloudflare-ddns.svg)](https://microbadger.com/images/joshuaavalon/cloudflare-ddns) | ||
![Docker Pulls](https://img.shields.io/docker/pulls/joshuaavalon/cloudflare-ddns.svg) | ||
![Docker Stars](https://img.shields.io/docker/stars/joshuaavalon/cloudflare-ddns.svg?colorB=dfb317) | ||
[![Docker Automated build](https://img.shields.io/docker/automated/joshuaavalon/cloudflare-ddns.svg)](https://hub.docker.com/r/joshuaavalon/cloudflare-ddns/) | ||
[![Docker build](https://img.shields.io/docker/build/joshuaavalon/cloudflare-ddns.svg)](https://hub.docker.com/r/joshuaavalon/cloudflare-ddns/) | ||
[![MIT](https://img.shields.io/github/license/joshuaavalon/docker-cloudflare.svg)](https://github.com/docker-cloudflare/blob/master/LICENSE) | ||
|
||
The is a simple docker using curl to update DNS record on [Cloudflare](https://www.cloudflare.com). Inspired by [rasmusbe/cloudflare-update-record.sh](https://gist.github.com/rasmusbe/fc2e270095f1a3b41348/) | ||
|
@@ -15,28 +15,49 @@ Compare to [nouchka/cloudflare-dyndns](https://hub.docker.com/r/nouchka/cloudfla | |
* Update when IP is changed | ||
|
||
## Usage | ||
|
||
### Running once interactively | ||
|
||
``` | ||
docker run \ | ||
-e RUNONCE=1 \ | ||
-e ZONE=example.com \ | ||
-e HOST=example.com \ | ||
-e [email protected] \ | ||
-e API=1111111111111111 \ | ||
-e TTL=1 \ | ||
-e PROXY=true \ | ||
joshuaavalon/cloudflare-ddns | ||
``` | ||
|
||
### Running as a daemon | ||
|
||
``` | ||
docker run \ | ||
-d \ | ||
-e ZONE=example.com \ | ||
-e HOST=example.com \ | ||
-e [email protected] \ | ||
-e API=1111111111111111 \ | ||
-e TTL=1 \ | ||
-e PROXY=true \ | ||
--name cloudflare \ | ||
docker run \ | ||
-d \ | ||
-e ZONE=example.com \ | ||
-e HOST=example.com \ | ||
-e [email protected] \ | ||
-e API=1111111111111111 \ | ||
-e TTL=1 \ | ||
-e PROXY=true \ | ||
joshuaavalon/cloudflare-ddns | ||
``` | ||
|
||
|
||
## Parameters | ||
`ZONE`: Domain, e.g. example.com. Default: `example.com` | ||
`ZONE`: Domain, e.g. example.com. | ||
|
||
`HOST`: DNS record to be updated, e.g. example.com, subdomain.example.com. | ||
|
||
`EMAIL`: Cloudflare Email. | ||
|
||
`HOST`: DNS record to be updated, e.g. example.com, subdomain.example.com. Default: `example.com` | ||
`API`: Cloudflare API key. | ||
|
||
`EMAIL`: Cloudflare Email. Default: `[email protected]` | ||
`TTL`: (OPTIONAL) Time to live for DNS record. Value of 1 is 'automatic'. Min value:120; Max value:2147483647. Default: `1` | ||
|
||
`API`: Cloudflare API key. Default: `1111111111111111` | ||
`PROXY`: (OPTIONAL) Whether the record is receiving the performance and security benefits of Cloudflare. `true` to enable; `false` to disable. Default: `true` | ||
|
||
`TTL`: Time to live for DNS record. Value of 1 is 'automatic'. Min value:1; Max value:2147483647. Default: `1` | ||
`FORCE_CREATE`: (OPTIONAL) When set, a record will be created if one does not exist already. | ||
|
||
`PROXY`: Whether the record is receiving the performance and security benefits of Cloudflare. `true` to enable; `false` to disable. Default: `true` | ||
`RUNONCE`: (OPTIONAL) When set, only a single update is attempted, and the script exists without setting up a cron process. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,157 @@ | ||
#!/bin/sh | ||
#!/bin/bash | ||
|
||
# Enforces required env variables | ||
required_vars=(ZONE HOST EMAIL API) | ||
for required_var in "${required_vars[@]}"; do | ||
if [[ -z ${!required_var} ]]; then | ||
error=1 | ||
echo >&2 "Error: $required_var env variable not set." | ||
fi | ||
done | ||
|
||
if [[ -n $error ]]; then | ||
exit 1 | ||
fi | ||
|
||
# PROXY defaults to true | ||
PROXY=${PROXY:-true} | ||
|
||
# TTL defaults to 1 (automatic), and is validated | ||
TTL=${TTL:-1} | ||
if [[ $TTL != 1 ]] && [[ $TTL -lt 120 || $TTL -gt 2147483647 ]]; then | ||
echo >&2 "Error: Invalid TTL value $TTL; must be either 1 (automatic) or between 120 and 2147483647 inclusive." | ||
exit 1 | ||
fi | ||
|
||
echo "Current time: $(date "+%Y-%m-%d %H:%M:%S")" | ||
ip_file="ip" | ||
|
||
# Determines the current IP address | ||
new_ip=$(curl -s http://ipecho.net/plain) | ||
|
||
# Fallbacks | ||
if [ -z "$new_ip" ]; then | ||
# IP address service fallbacks | ||
if [[ -z $new_ip ]]; then | ||
new_ip=$(curl -s http://whatismyip.akamai.com) | ||
fi | ||
if [ -z "$new_ip" ]; then | ||
if [[ -z $new_ip ]]; then | ||
new_ip=$(curl -s http://icanhazip.com/) | ||
fi | ||
if [ -z "$new_ip" ]; then | ||
if [[ -z $new_ip ]]; then | ||
new_ip=$(curl -s https://tnx.nl/ip) | ||
fi | ||
|
||
if [ -z "$new_ip" ]; then | ||
echo "Empty IP !" | ||
exit 0 | ||
if [[ -z $new_ip ]]; then | ||
echo >&2 "Error: Unable to reach any service to determine the IP address." | ||
exit 1 | ||
fi | ||
|
||
if [ -f $ip_file ]; then | ||
ip=$(cat $ip_file) | ||
if [ "$ip" = "$new_ip" ]; then | ||
echo "Same ip: $ip" | ||
exit 0 | ||
fi | ||
# Compares with last IP address set, if any | ||
ip_file="ip.dat" | ||
if [[ -f $ip_file ]]; then | ||
ip=$(<$ip_file) | ||
if [[ $ip = "$new_ip" ]]; then | ||
echo "IP is unchanged : $ip. Exiting." | ||
exit 0 | ||
fi | ||
fi | ||
|
||
ip="$new_ip" | ||
echo "IP: $ip" | ||
zone_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE" -H "X-Auth-Email: $EMAIL" -H "X-Auth-Key: $API" -H "Content-Type: application/json" | grep -Eo '"id":.?"\w*?"' |head -1|grep -o ':.*".*"'|grep -o '\w*') | ||
echo "Zone ID: $zone_id" | ||
record_id=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$HOST" -H "X-Auth-Email: $EMAIL" -H "X-Auth-Key: $API" -H "Content-Type: application/json" | grep -Eo '"id":.?"\w*?"' |head -1|grep -o ':.*".*"'|grep -o '\w*') | ||
echo "Record ID: $record_id" | ||
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" -H "X-Auth-Email: $EMAIL" -H "X-Auth-Key: $API" -H "Content-Type: application/json" --data "{\"id\":\"$zone_id\",\"type\":\"A\",\"name\":\"$HOST\",\"content\":\"$ip\",\"ttl\":$TTL,\"proxied\":$PROXY}") | ||
|
||
if $DEBUG; then | ||
echo "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$record_id" -H "X-Auth-Email: $EMAIL" -H "X-Auth-Key: $API" -H "Content-Type: application/json" --data "{\"id\":\"$zone_id\",\"type\":\"A\",\"name\":\"$HOST\",\"content\":\"$ip\",\"ttl\":$TTL,\"proxied\":$PROXY}" | ||
# Fetches the zone information for the account | ||
zone_response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE" \ | ||
-H "X-Auth-Email: $EMAIL" \ | ||
-H "X-Auth-Key: $API" \ | ||
-H "Content-Type: application/json") | ||
|
||
|
||
# If not successful, errors out | ||
if [[ $(jq <<<"$zone_response" -r '.success') != "true" ]]; then | ||
messages=$(jq <<<"$zone_response" -r '[.errors[] | .message] |join(" - ")') | ||
echo >&2 "Error: $messages" | ||
exit 1 | ||
fi | ||
|
||
# Selects the zone id | ||
zone_id=$(jq <<<"$zone_response" -r ".result[0].id") | ||
|
||
|
||
# If no zone id was found for the account, errors out. | ||
if [[ -z $zone_id ]]; then | ||
echo >&2 "Error: Could not determine Zone ID for $ZONE" | ||
exit 1 | ||
fi | ||
|
||
echo "Zone $ZONE id : $zone_id" | ||
|
||
|
||
# Tries to fetch the record of the host | ||
dns_record_response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$HOST" \ | ||
-H "X-Auth-Email: $EMAIL" \ | ||
-H "X-Auth-Key: $API" \ | ||
-H "Content-Type: application/json") | ||
|
||
if [[ $(jq <<<"$dns_record_response" -r '.success') != "true" ]]; then | ||
messages=$(jq <<<"$dns_record_response" -r '[.errors[] | .message] |join(" - ")') | ||
echo >&2 "Error: $messages" | ||
exit 1 | ||
fi | ||
|
||
# DNS record to add or update | ||
read -r -d '' new_dns_record <<EOF | ||
{ | ||
"type": "A", | ||
"name": "$HOST", | ||
"content": "$ip", | ||
"ttl": $TTL, | ||
"priority": 10, | ||
"proxied": $PROXY | ||
} | ||
EOF | ||
|
||
# Adds or updates the record | ||
dns_record_id=$(jq <<<"$dns_record_response" -r '.result[] | select(.type =="A") |.id') | ||
if [[ -z $dns_record_id ]]; then | ||
|
||
# Makes sure we don't have a CNAME by the same name first | ||
cname_dns_record=$(jq <<<"$dns_record_response" -r '.result[] | select(.type =="CNAME")') | ||
if [[ -n $(jq <<<"$cname_dns_record" -r '.id') ]]; then | ||
dns_record_content=$(jq <<<"$cname_dns_record" -r '.content') | ||
echo >&2 "Error: CNAME entry found for $HOST. Remove it or set HOST=$dns_record_content instead." | ||
exit 1 | ||
fi | ||
|
||
# If not asked to create a record, stops | ||
if [[ -z $FORCE_CREATE ]]; then | ||
echo >&2 "Error: No existing record. Set FORCE_CREATE to any value to force its creation." | ||
exit 1 | ||
fi | ||
|
||
# Creates a new record | ||
echo "Creating new record for host $HOST" | ||
|
||
dns_record_response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records" \ | ||
-H "X-Auth-Email: $EMAIL" \ | ||
-H "X-Auth-Key: $API" \ | ||
-H "Content-Type: application/json" \ | ||
--data "$new_dns_record") | ||
else | ||
# If a record is found, updates the existing record | ||
echo "Updating record $dns_record_id for host $HOST" | ||
|
||
dns_record_response=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$dns_record_id" \ | ||
-H "X-Auth-Email: $EMAIL" \ | ||
-H "X-Auth-Key: $API" \ | ||
-H "Content-Type: application/json" \ | ||
--data "$new_dns_record") | ||
fi | ||
|
||
if echo "$update" | grep -q "\"success\":true"; then | ||
echo "IP changed to: $ip" | ||
echo "$ip" > $ip_file | ||
if [[ $(jq <<<"$dns_record_response" -r '.success') = "true" ]]; then | ||
# Records the IP set set if successful | ||
echo "IP changed to: $ip" | ||
echo "$ip" > $ip_file | ||
else | ||
printf "Update failed:\\n%s" "$update" | ||
exit 1 | ||
# Prints out error messages if unsuccessful | ||
messages=$(jq <<<"$dns_record_response" -r '[.errors[] | .error.message] |join(" - ")') | ||
echo >&2 "Error: $messages" | ||
exit 1 | ||
fi |
File renamed without changes.