ADD CF_SHORT_NAME only for payload Signed-off-by: saifuddin <saifuddin@abyres.net>
94 lines
3.2 KiB
Bash
94 lines
3.2 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ENV_FILE="/etc/cf-ddns/cf-ddns.env"
|
|
STATE_DIR="/var/lib/cf-ddns"
|
|
API="https://api.cloudflare.com/client/v4"
|
|
|
|
log() { logger -t cf-ddns "$*"; echo "[cf-ddns] $*"; }
|
|
fail() { log "ERROR: $*"; exit 1; }
|
|
|
|
# --- Load env ---
|
|
[[ -f "$ENV_FILE" ]] || fail "Missing $ENV_FILE"
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
: "${CF_API_TOKEN:?CF_API_TOKEN required in env}"
|
|
: "${CF_PROXIED:=false}"
|
|
: "${CF_TTL:=120}"
|
|
|
|
# --- Derive zone/record from system hostname ---
|
|
CF_RECORD_NAME="$(hostname -f 2>/dev/null || true)"
|
|
CF_SHORT_NAME="$(hostname -s 2>/dev/null || true)"
|
|
CF_ZONE_NAME="$(hostname -d 2>/dev/null || true)"
|
|
|
|
[[ -n "$CF_RECORD_NAME" ]] || fail "hostname -s returned empty"
|
|
[[ -n "$CF_ZONE_NAME" ]] || fail "hostname -d returned empty (set a DNS/search domain)"
|
|
|
|
# Safe filename for state
|
|
safe_name="$(echo "$CF_RECORD_NAME" | tr '/:' '__')"
|
|
LAST_IP_FILE="${STATE_DIR}/last_ipv4_${safe_name}"
|
|
|
|
mkdir -p "$STATE_DIR"
|
|
|
|
# --- Detect local IPv4 on default route interface ---
|
|
detect_ip() {
|
|
local iface ip
|
|
|
|
# Prefer ip-route default; fallback to ip route get
|
|
iface="$(ip route show default 2>/dev/null | awk '/default/ {print $5; exit}')"
|
|
if [[ -z "$iface" ]]; then
|
|
iface="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}')"
|
|
fi
|
|
[[ -n "$iface" ]] || fail "Could not detect default route interface"
|
|
|
|
ip="$(ip -4 addr show dev "$iface" | awk '/inet / {print $2}' | cut -d/ -f1 | head -n1)"
|
|
[[ -n "$ip" ]] || fail "No IPv4 found on default interface '$iface'"
|
|
|
|
echo "$ip"
|
|
}
|
|
|
|
current_ip="$(detect_ip)"
|
|
previous_ip="$( [[ -f "$LAST_IP_FILE" ]] && cat "$LAST_IP_FILE" || echo "" )"
|
|
|
|
if [[ "$current_ip" == "$previous_ip" && -n "$current_ip" ]]; then
|
|
log "IP unchanged ($current_ip); no update needed for ${CF_RECORD_NAME}."
|
|
exit 0
|
|
fi
|
|
|
|
# --- Cloudflare helpers ---
|
|
urlenc() { local s="$1"; s="${s// /%20}"; s="${s//\//%2F}"; echo -n "$s"; }
|
|
hdr=(-H "Authorization: Bearer ${CF_API_TOKEN}" -H "Content-Type: application/json")
|
|
|
|
# 1) Get Zone ID
|
|
zone_q="name=$(urlenc "$CF_ZONE_NAME")"
|
|
zone_id="$(curl -fsS "${hdr[@]}" "${API}/zones?${zone_q}" \
|
|
| sed -n 's/.*"id":"\([^"]*\)".*"name":"'"$CF_ZONE_NAME"'".*/\1/p' | head -n1)"
|
|
[[ -n "$zone_id" ]] || fail "Could not find Zone ID for ${CF_ZONE_NAME}"
|
|
|
|
# 2) Lookup existing A record by name
|
|
rec_q="type=A&name=$(urlenc "$CF_RECORD_NAME")"
|
|
record_json="$(curl -fsS "${hdr[@]}" "${API}/zones/${zone_id}/dns_records?${rec_q}")"
|
|
record_id="$(echo "$record_json" | sed -n 's/.*"id":"\([^"]*\)".*"type":"A".*"name":"'"$CF_RECORD_NAME"'".*/\1/p' | head -n1)"
|
|
|
|
# 3) Create payload + create/update
|
|
json_payload() {
|
|
cat <<JSON
|
|
{"type":"A","name":"${CF_SHORT_NAME}","content":"${current_ip}","ttl":${CF_TTL},"proxied":${CF_PROXIED}}
|
|
JSON
|
|
}
|
|
|
|
if [[ -z "$record_id" ]]; then
|
|
log "Creating A ${CF_RECORD_NAME} -> ${current_ip}"
|
|
curl -fsS -X POST "${hdr[@]}" \
|
|
--data "$(json_payload)" \
|
|
"${API}/zones/${zone_id}/dns_records" >/dev/null
|
|
log "Created."
|
|
else
|
|
log "Updating A ${CF_RECORD_NAME} -> ${current_ip}"
|
|
curl -fsS -X PUT "${hdr[@]}" \
|
|
--data "$(json_payload)" \
|
|
"${API}/zones/${zone_id}/dns_records/${record_id}" >/dev/null
|
|
log "Updated."
|
|
fi
|
|
|
|
echo "$current_ip" > "$LAST_IP_FILE" |