diff --git a/.env.example b/.env.example index 3e1a759..bfcd36f 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # Porkbun API credentials PORKBUN_API_KEY=pk1_your_key_here -PORKBUN_SECRET_API_KEY=sk1_your_key_here +PORKBUN_SECRET_KEY=sk1_your_key_here # K3s node token for agent join K3S_NODE_TOKEN=your_token_here \ No newline at end of file diff --git a/manifests/ddns-cronjob.yaml b/manifests/ddns-cronjob.yaml new file mode 100644 index 0000000..5fc1fcb --- /dev/null +++ b/manifests/ddns-cronjob.yaml @@ -0,0 +1,97 @@ +# DDNS CronJob — updates home.nik4nao.com on Porkbun every 5 minutes +# Requires: porkbun-ddns secret in ddns namespace +# Apply: kubectl apply -f manifests/ddns-cronjob.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: ddns +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: porkbun-ddns + namespace: ddns +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: ddns + image: alpine:latest + env: + - name: PORKBUN_API_KEY + valueFrom: + secretKeyRef: + name: porkbun-ddns + key: api-key + - name: PORKBUN_SECRET_KEY + valueFrom: + secretKeyRef: + name: porkbun-ddns + key: secret-api-key + command: + - /bin/sh + - -c + - | + set -euo pipefail + apk add --no-cache curl jq bind-tools -q + + DOMAIN="nik4nao.com" + RECORD_NAME="home" + TTL=300 + + timestamp() { date +"%Y-%m-%d %H:%M:%S%z"; } + + # Get current WAN IP + WAN_IP="$(curl -sf https://api.ipify.org)" + if [ -z "$WAN_IP" ]; then + echo "[$(timestamp)] ERROR: Could not detect WAN IP" + exit 1 + fi + echo "[$(timestamp)] WAN IP: $WAN_IP" + + # Get current DNS record via dig + DNS_IP="$(dig +short ${RECORD_NAME}.${DOMAIN} @1.1.1.1 | head -1)" + echo "[$(timestamp)] DNS IP: $DNS_IP" + + # Skip if unchanged + if [ "$WAN_IP" = "$DNS_IP" ]; then + echo "[$(timestamp)] No change, skipping." + exit 0 + fi + + echo "[$(timestamp)] IP changed $DNS_IP -> $WAN_IP, updating..." + + # Retrieve existing records to get record ID + RECORDS="$(curl -sf -X POST \ + "https://api.porkbun.com/api/json/v3/dns/retrieve/${DOMAIN}" \ + -H 'Content-Type: application/json' \ + -d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\"}")" + + RECORD_ID="$(echo "$RECORDS" | jq -r \ + --arg name "$RECORD_NAME" \ + '.records[] | select(.type=="A" and .name==$name) | .id' | head -1)" + + if [ -z "$RECORD_ID" ]; then + # Create new record + echo "[$(timestamp)] No existing record, creating..." + RESP="$(curl -sf -X POST \ + "https://api.porkbun.com/api/json/v3/dns/create/${DOMAIN}" \ + -H 'Content-Type: application/json' \ + -d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$WAN_IP\",\"ttl\":$TTL}")" + else + # Update existing record + echo "[$(timestamp)] Updating record ID $RECORD_ID..." + RESP="$(curl -sf -X POST \ + "https://api.porkbun.com/api/json/v3/dns/edit/${DOMAIN}/${RECORD_ID}" \ + -H 'Content-Type: application/json' \ + -d "{\"apikey\":\"$PORKBUN_API_KEY\",\"secretapikey\":\"$PORKBUN_SECRET_KEY\",\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$WAN_IP\",\"ttl\":$TTL}")" + fi + + echo "[$(timestamp)] Response: $(echo "$RESP" | jq -c .)" + echo "$RESP" | grep -q '"status":"SUCCESS"' && \ + echo "[$(timestamp)] Update successful" || \ + echo "[$(timestamp)] Update failed" \ No newline at end of file diff --git a/manifests/ddns-secret.sh b/manifests/ddns-secret.sh new file mode 100644 index 0000000..1336b14 --- /dev/null +++ b/manifests/ddns-secret.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Usage: bash manifests/ddns-secret.sh +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="$SCRIPT_DIR/../.env" + +if [ ! -f "$ENV_FILE" ]; then + echo "Error: .env file not found" + exit 1 +fi + +source "$ENV_FILE" + +kubectl create secret generic porkbun-ddns \ + --namespace ddns \ + --from-literal=api-key="$PORKBUN_API_KEY" \ + --from-literal=secret-api-key="$PORKBUN_SECRET_KEY" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo "Secret applied" \ No newline at end of file