From f6e340a6bb96fa5c7daaae15812409d19627df00 Mon Sep 17 00:00:00 2001 From: Scotte Zinn Date: Wed, 26 Feb 2025 13:06:21 -0500 Subject: [PATCH] feat(ddns): DDNS is now done on artemis --- .taskfiles/Machine/Taskfile.yaml | 14 +- .../files/artemis/cloudflare-ddns.crontab | 1 + .../Machine/files/artemis/cloudflare-ddns.sh | 123 ++++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 .taskfiles/Machine/files/artemis/cloudflare-ddns.crontab create mode 100755 .taskfiles/Machine/files/artemis/cloudflare-ddns.sh diff --git a/.taskfiles/Machine/Taskfile.yaml b/.taskfiles/Machine/Taskfile.yaml index a54613b0ea..0d5195ce1d 100644 --- a/.taskfiles/Machine/Taskfile.yaml +++ b/.taskfiles/Machine/Taskfile.yaml @@ -114,11 +114,21 @@ tasks: install-artemis-docker: desc: Install docker cmds: - - ssh root@artemis.zinn.tech apt update -y && sudo apt upgrade -y - - ssh root@artemis.zinn.tech 'curl -fsSL https://get.docker.com -o /opt/get-docker.sh && sudo sh /opt/get-docker.sh && rm /opt/get-docker.sh' + - ssh root@artemis.zinn.tech 'apt update -y && apt upgrade -y' + - ssh root@artemis.zinn.tech 'curl -fsSL https://get.docker.com -o /opt/get-docker.sh && sh /opt/get-docker.sh && rm /opt/get-docker.sh' - ssh root@artemis.zinn.tech apt install -y docker-compose - ssh root@artemis.zinn.tech systemctl restart docker.service + install-artemis-ddns: + desc: Install Cloudflare DDNS + cmds: + - op inject --in-file "{{.FILES_DIR}}/artemis/cloudflare-ddns.sh" 2>/dev/null > /tmp/cloudflare-ddns.sh + - scp /tmp/cloudflare-ddns.sh root@artemis.zinn.tech:/usr/local/bin/cloudflare-ddns.sh + - rm /tmp/cloudflare-ddns.sh + - ssh root@artemis.zinn.tech 'chmod +x /usr/local/bin/cloudflare-ddns.sh && chmod og+rx /usr/local/bin/cloudflare-ddns.sh' + - scp "{{.FILES_DIR}}/artemis/cloudflare-ddns.crontab" root@artemis.zinn.tech:/etc/cron.d/cloudflare-ddns + - ssh root@artemis.zinn.tech chmod -x /etc/cron.d/cloudflare-ddns + restart-artemis-adguard: desc: Restart AdGuardHome cmds: diff --git a/.taskfiles/Machine/files/artemis/cloudflare-ddns.crontab b/.taskfiles/Machine/files/artemis/cloudflare-ddns.crontab new file mode 100644 index 0000000000..9250b62f06 --- /dev/null +++ b/.taskfiles/Machine/files/artemis/cloudflare-ddns.crontab @@ -0,0 +1 @@ +*/10 * * * * root /usr/local/bin/cloudflare-ddns.sh diff --git a/.taskfiles/Machine/files/artemis/cloudflare-ddns.sh b/.taskfiles/Machine/files/artemis/cloudflare-ddns.sh new file mode 100755 index 0000000000..27524d6660 --- /dev/null +++ b/.taskfiles/Machine/files/artemis/cloudflare-ddns.sh @@ -0,0 +1,123 @@ +#!/bin/bash +## change to "bin/sh" when necessary + +auth_email="op://Kubernetes/cloudflare/USERNAME" # The email used to login 'https://dash.cloudflare.com' +auth_method="token" # Set to "global" for Global API Key or "token" for Scoped API Token +auth_key="op://Kubernetes/cloudflare/CLOUDFLARE_DNS_TOKEN" # Your API Token or Global API Key +zone_identifier="op://Kubernetes/cloudflare/TECH_ZONE_ID" # Can be found in the "Overview" tab of your domain +record_name="op://Kubernetes/cloudflare/TECH_ZONE" # Which record you want to be synced +ttl=3600 # Set the DNS TTL (seconds) +proxy="false" # Set the proxy to true or false +sitename="op://Kubernetes/cloudflare/TECH_ZONE" # Title of site "Example Site" +slackchannel="" # Slack Channel #example +slackuri="" # URI for Slack WebHook "https://hooks.slack.com/services/xxxxx" +discorduri="op://Kubernetes/discord/DISCORD_K8S_MAIN_WEBHOOK" # URI for Discord WebHook "https://discordapp.com/api/webhooks/xxxxx" + + +########################################### +## Check if we have a public IP +########################################### +ipv4_regex='([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])' +ip=$(curl -s -4 https://cloudflare.com/cdn-cgi/trace | grep -E '^ip'); ret=$? +if [[ ! $ret == 0 ]]; then # In the case that cloudflare failed to return an ip. + # Attempt to get the ip from other websites. + ip=$(curl -s https://api.ipify.org || curl -s https://ipv4.icanhazip.com) +else + # Extract just the ip from the ip line from cloudflare. + ip=$(echo $ip | sed -E "s/^ip=($ipv4_regex)$/\1/") +fi + +# Use regex to check for proper IPv4 format. +if [[ ! $ip =~ ^$ipv4_regex$ ]]; then + logger -s "DDNS Updater: Failed to find a valid IP." + exit 2 +fi + +########################################### +## Check and set the proper auth header +########################################### +if [[ "${auth_method}" == "global" ]]; then + auth_header="X-Auth-Key:" +else + auth_header="Authorization: Bearer" +fi + +########################################### +## Seek for the A record +########################################### + +logger "DDNS Updater: Check Initiated" +record=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?type=A&name=$record_name" \ + -H "X-Auth-Email: $auth_email" \ + -H "$auth_header $auth_key" \ + -H "Content-Type: application/json") + +########################################### +## Check if the domain has an A record +########################################### +if [[ $record == *"\"count\":0"* ]]; then + logger -s "DDNS Updater: Record does not exist, perhaps create one first? (${ip} for ${record_name})" + exit 1 +fi + +########################################### +## Get existing IP +########################################### +old_ip=$(echo "$record" | sed -E 's/.*"content":"(([0-9]{1,3}\.){3}[0-9]{1,3})".*/\1/') +# Compare if they're the same +if [[ $ip == $old_ip ]]; then + logger "DDNS Updater: IP ($ip) for ${record_name} has not changed." + exit 0 +fi + +########################################### +## Set the record identifier from result +########################################### +record_identifier=$(echo "$record" | sed -E 's/.*"id":"([A-Za-z0-9_]+)".*/\1/') + +########################################### +## Change the IP@Cloudflare using the API +########################################### +update=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" \ + -H "X-Auth-Email: $auth_email" \ + -H "$auth_header $auth_key" \ + -H "Content-Type: application/json" \ + --data "{\"type\":\"A\",\"name\":\"$record_name\",\"content\":\"$ip\",\"ttl\":$ttl,\"proxied\":${proxy}}") + +########################################### +## Report the status +########################################### +case "$update" in +*"\"success\":false"*) + echo -e "DDNS Updater: $ip $record_name DDNS failed for $record_identifier ($ip). DUMPING RESULTS:\n$update" | logger -s + if [[ $slackuri != "" ]]; then + curl -L -X POST $slackuri \ + --data-raw '{ + "channel": "'$slackchannel'", + "text" : "'"$sitename"' DDNS Update Failed: '$record_name': '$record_identifier' ('$ip')." + }' + fi + if [[ $discorduri != "" ]]; then + curl -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST \ + --data-raw '{ + "content" : "'"$sitename"' DDNS Update Failed: '$record_name': '$record_identifier' ('$ip')." + }' $discorduri + fi + exit 1;; +*) + logger "DDNS Updater: $ip $record_name DDNS updated." + if [[ $slackuri != "" ]]; then + curl -L -X POST $slackuri \ + --data-raw '{ + "channel": "'$slackchannel'", + "text" : "'"$sitename"' Updated: '$record_name''"'"'s'""' new IP Address is '$ip'" + }' + fi + if [[ $discorduri != "" ]]; then + curl -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST \ + --data-raw '{ + "content" : "'"$sitename"' Updated: '$record_name''"'"'s'""' new IP Address is '$ip'" + }' $discorduri + fi + exit 0;; +esac