From 202b5f0ee0b4e11f79056f2080ae27fb59ad4886 Mon Sep 17 00:00:00 2001 From: willemjan Date: Wed, 4 Nov 2026 20:12:31 +0100 Subject: [PATCH] feat: cloudfare-ddns updater --- apps/cloudfare-ddns.yaml | 16 +++ services/cloudfare-ddns/configmap.yaml | 10 ++ services/cloudfare-ddns/cronjob.yaml | 50 +++++++ services/cloudfare-ddns/kustomization.yaml | 11 ++ services/cloudfare-ddns/namespace.yaml | 6 + services/cloudfare-ddns/script-configmap.yaml | 127 ++++++++++++++++++ services/cloudfare-ddns/sealed-secret.yaml | 19 +++ 7 files changed, 239 insertions(+) create mode 100644 apps/cloudfare-ddns.yaml create mode 100644 services/cloudfare-ddns/configmap.yaml create mode 100644 services/cloudfare-ddns/cronjob.yaml create mode 100644 services/cloudfare-ddns/kustomization.yaml create mode 100644 services/cloudfare-ddns/namespace.yaml create mode 100644 services/cloudfare-ddns/script-configmap.yaml create mode 100644 services/cloudfare-ddns/sealed-secret.yaml diff --git a/apps/cloudfare-ddns.yaml b/apps/cloudfare-ddns.yaml new file mode 100644 index 0000000..05d077b --- /dev/null +++ b/apps/cloudfare-ddns.yaml @@ -0,0 +1,16 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: cloudfare-ddns + namespace: argocd +spec: + project: default + source: + repoURL: https://gitea.zb-kw.duckdns.org/willemjan/cluster.git + targetRevision: main + path: services/cloudfare-ddns + destination: + server: https://kubernetes.default.svc + namespace: cloudfare-ddns + syncPolicy: + automated: {prune: true, selfHeal: true} \ No newline at end of file diff --git a/services/cloudfare-ddns/configmap.yaml b/services/cloudfare-ddns/configmap.yaml new file mode 100644 index 0000000..cb91946 --- /dev/null +++ b/services/cloudfare-ddns/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: cloudflare-ddns-config + namespace: cloudflare-ddns +data: + # Comma-separated list of domains to update + # Format: subdomain.domain.tld,subdomain2.domain.tld + DOMAINS: "*.labzand.com" + ZONE_ID: "6093605ad5a8db13e9c9d9faad240ca6" \ No newline at end of file diff --git a/services/cloudfare-ddns/cronjob.yaml b/services/cloudfare-ddns/cronjob.yaml new file mode 100644 index 0000000..2cbc563 --- /dev/null +++ b/services/cloudfare-ddns/cronjob.yaml @@ -0,0 +1,50 @@ +piVersion: batch/v1 +kind: CronJob +metadata: + name: cloudflare-ddns + namespace: cloudflare-ddns +spec: + # Run every hour at the top of the hour + schedule: "0 * * * *" + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + metadata: + labels: + app: cloudflare-ddns + spec: + restartPolicy: OnFailure + containers: + - name: cloudflare-ddns + image: curlimages/curl:latest + command: + - /bin/sh + - /scripts/update-dns.sh + env: + - name: DOMAINS + valueFrom: + configMapKeyRef: + name: cloudflare-ddns-config + key: DOMAINS + - name: ZONE_ID + valueFrom: + configMapKeyRef: + name: cloudflare-ddns-config + key: ZONE_ID + - name: API_TOKEN + valueFrom: + secretKeyRef: + name: cloudflare-ddns-secret + key: API_TOKEN + volumeMounts: + - name: scripts + mountPath: /scripts + readOnly: true + volumes: + - name: scripts + configMap: + name: cloudflare-ddns-script + defaultMode: 0755 \ No newline at end of file diff --git a/services/cloudfare-ddns/kustomization.yaml b/services/cloudfare-ddns/kustomization.yaml new file mode 100644 index 0000000..f1d775e --- /dev/null +++ b/services/cloudfare-ddns/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: cloudflare-ddns + +resources: + - namespace.yaml + - configmap.yaml + - script-configmap.yaml + - secret.yaml + - cronjob.yaml \ No newline at end of file diff --git a/services/cloudfare-ddns/namespace.yaml b/services/cloudfare-ddns/namespace.yaml new file mode 100644 index 0000000..0ebe08a --- /dev/null +++ b/services/cloudfare-ddns/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: cloudflare-ddns + labels: + name: cloudflare-ddns \ No newline at end of file diff --git a/services/cloudfare-ddns/script-configmap.yaml b/services/cloudfare-ddns/script-configmap.yaml new file mode 100644 index 0000000..fc7d57e --- /dev/null +++ b/services/cloudfare-ddns/script-configmap.yaml @@ -0,0 +1,127 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: cloudflare-ddns-script + namespace: cloudflare-ddns +data: + update-dns.sh: | + #!/bin/sh + + set -e + + # Colors for output + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + NC='\033[0m' # No Color + + echo "Starting Cloudflare DDNS update..." + echo "Timestamp: $(date)" + + # Check required environment variables + if [ -z "$API_TOKEN" ]; then + echo "${RED}ERROR: API_TOKEN is not set${NC}" + exit 1 + fi + + if [ -z "$ZONE_ID" ]; then + echo "${RED}ERROR: ZONE_ID is not set${NC}" + exit 1 + fi + + if [ -z "$DOMAINS" ]; then + echo "${RED}ERROR: DOMAINS is not set${NC}" + exit 1 + fi + + # Get current public IP address + echo "Fetching current public IP address..." + PUBLIC_IP=$(curl -s https://api.ipify.org) + + if [ -z "$PUBLIC_IP" ]; then + echo "First attempt failed, trying alternative service..." + PUBLIC_IP=$(curl -s https://ifconfig.me) + fi + + if [ -z "$PUBLIC_IP" ]; then + echo "${RED}ERROR: Could not determine public IP address${NC}" + exit 1 + fi + + echo "${GREEN}Current public IP: $PUBLIC_IP${NC}" + + # Process each domain + echo "$DOMAINS" | tr ',' '\n' | while IFS= read -r DOMAIN; do + # Trim whitespace + DOMAIN=$(echo "$DOMAIN" | xargs) + + echo "" + echo "Processing domain: $DOMAIN" + + # Get DNS record ID for the domain + echo "Fetching DNS record for $DOMAIN..." + RECORD_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$DOMAIN&type=A" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json") + + # Check if the request was successful + SUCCESS=$(echo "$RECORD_RESPONSE" | grep -o '"success":[^,]*' | cut -d':' -f2) + + if [ "$SUCCESS" != "true" ]; then + echo "${RED}ERROR: Failed to fetch DNS record for $DOMAIN${NC}" + echo "$RECORD_RESPONSE" + continue + fi + + # Extract record ID and current IP + RECORD_ID=$(echo "$RECORD_RESPONSE" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) + CURRENT_IP=$(echo "$RECORD_RESPONSE" | grep -o '"content":"[^"]*"' | head -1 | cut -d'"' -f4) + + if [ -z "$RECORD_ID" ]; then + echo "${YELLOW}WARNING: DNS record not found for $DOMAIN. Creating new record...${NC}" + + # Create new DNS record + CREATE_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ + -H "Authorization: Bearer $API_TOKEN" \ + -H "Content-Type: application/json" \ + --data "{\"type\":\"A\",\"name\":\"$DOMAIN\",\"content\":\"$PUBLIC_IP\",\"ttl\":120,\"proxied\":false}") + + CREATE_SUCCESS=$(echo "$CREATE_RESPONSE" | grep -o '"success":[^,]*' | cut -d':' -f2) + + if [ "$CREATE_SUCCESS" = "true" ]; then + echo "${GREEN}Successfully created DNS record for $DOMAIN with IP $PUBLIC_IP${NC}" + else + echo "${RED}ERROR: Failed to create DNS record for $DOMAIN${NC}" + echo "$CREATE_RESPONSE" + fi + continue + fi + + echo "Current DNS IP: $CURRENT_IP" + + # Check if IP needs to be updated + if [ "$CURRENT_IP" = "$PUBLIC_IP" ]; then + echo "${GREEN}IP address is already up to date for $DOMAIN${NC}" + continue + fi + + echo "${YELLOW}Updating DNS record for $DOMAIN from $CURRENT_IP to $PUBLIC_IP${NC}" + + # Update DNS record + UPDATE_RESPONSE=$(curl -s -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\":\"$DOMAIN\",\"content\":\"$PUBLIC_IP\",\"ttl\":120,\"proxied\":false}") + + UPDATE_SUCCESS=$(echo "$UPDATE_RESPONSE" | grep -o '"success":[^,]*' | cut -d':' -f2) + + if [ "$UPDATE_SUCCESS" = "true" ]; then + echo "${GREEN}Successfully updated DNS record for $DOMAIN to $PUBLIC_IP${NC}" + else + echo "${RED}ERROR: Failed to update DNS record for $DOMAIN${NC}" + echo "$UPDATE_RESPONSE" + fi + done + + echo "" + echo "Cloudflare DDNS update completed at $(date)" \ No newline at end of file diff --git a/services/cloudfare-ddns/sealed-secret.yaml b/services/cloudfare-ddns/sealed-secret.yaml new file mode 100644 index 0000000..4fc86ff --- /dev/null +++ b/services/cloudfare-ddns/sealed-secret.yaml @@ -0,0 +1,19 @@ +{ + "kind": "SealedSecret", + "apiVersion": "bitnami.com/v1alpha1", + "metadata": { + "name": "cloudfare-ddns-secret", + "namespace": "cloudfare-ddns" + }, + "spec": { + "template": { + "metadata": { + "name": "cloudfare-ddns-secret", + "namespace": "cloudfare-ddns" + } + }, + "encryptedData": { + "API_TOKEN": "AgAHCYYKi6dkQ5IltIQ0nCSyO5pbTKWNWhyqfNYEjSQYLKbCq2KZCqB3AbFgnwM6kIgtnJ7dYoqZtDFYp+WohTdxi6BnGVzMNbmWQ5luugJ88QxlmNMcoYv4ooHgCbD20riBFMXiDxALmLswJsNA3CuJBWpG8N3Ac51zyqccZAbyDzTq+0M5ltv5AL5sPVmKSClK96rt42p1KZVeprZlSoeJ9ZJnBgNMOTtEBF8xGYSLxxoaTOJZTCMtCzQp1975q93qUY/OLuPUKdgccYMEPESo9md+KpvkHQ87jtDC0sdcg7bYmHIoQgeNOzE5Y18wBQSSWkuARkKz1p6+uJo+3OvmcRCf+Gi5n8vmedrX3xGzKYXHZRFEPkbLFmkI/cT8hu914hctUd7YhS5UU5qqMOLSecKATLj1U4bxIvysw7aDLTB5F7G5q0um4TczaKx5XKHxw5xB5ldk7gOKrzztnKXNd3wK4SPYrCI0RKPSvESOZFBlU76hU8ZColkKE5H4dE9CtTxvKqwaje5qzoup2+SqtlQ4EytGZ0+ww1rKJCLR46vzgvSItB40q6pyDAUaLkLSw7iBLcma9QXelf1RX8Dynk76dajB+TXJjMWYDuLJMkHaB5GgQCH8R9Y4WtTA64WTlg8DPDhEOnFNwBsCdn3jsMjuguTo4SoCe9wK+eSETOrt/t8Aokj0hnvdHnHFBMlutvvA1c6Zy/PSpRhG6g325yEgMnMcyuy6pVKy3PqCe3But+yQ51Yh3wAdo6p+oAsKeFEBPA==" + } + } +}