Files
linux-scripts/hetzner-dns-manager.sh
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

722 lines
29 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#########################################################################################
#### hetzner-dns-manager.sh — Manage DNS zones and records via the Hetzner DNS API ####
#### List zones, add/update/delete records, BIND export/import, audit, bulk ops ####
#### Requires: bash 4+, curl, jq ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.01 ####
#### ####
#### Usage: ####
#### ./hetzner-dns-manager.sh --zones ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -euo pipefail
# ── Colors (pre-initialized) ─────────────────────────────────────────
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
setup_colors() {
if [[ "${COLOR:-auto}" == "never" ]]; then
return
fi
if [[ "${COLOR:-auto}" == "always" ]] || [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
fi
}
# ── Logging ───────────────────────────────────────────────────────────
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
die() { err "$*"; exit 1; }
section_header() {
echo ""
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
echo ""
}
field() {
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
}
field_color() {
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
}
elapsed() {
local end_time
end_time=$(date +%s)
echo "$(( end_time - START_TIME ))s"
}
# ── Defaults ──────────────────────────────────────────────────────────
RUN_MODE=""
ZONE_NAME=""
ZONE_ID=""
RECORD_ID=""
RECORD_TYPE=""
RECORD_NAME=""
RECORD_VALUE=""
RECORD_TTL="3600"
CSV_FILE=""
OUTPUT_FORMAT="${HDM_FORMAT:-table}"
FORCE="false"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
IMPORT_FILE=""
EXPORT_FILE=""
# ── Credentials ───────────────────────────────────────────────────────
HETZNER_DNS_TOKEN="${HETZNER_DNS_TOKEN:-}"
# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
START_TIME=""
ACTION_OK=0
ACTION_FAIL=0
# ── API helpers ──────────────────────────────────────────────────────
hdns_api() {
local method="$1" endpoint="$2"
shift 2
local attempt=0 max_attempts=3
while (( attempt < max_attempts )); do
local http_code
http_code=$(curl -s -o /tmp/hdm_resp.json -w "%{http_code}" \
-X "$method" \
-H "Auth-API-Token: ${HETZNER_DNS_TOKEN}" \
-H "Content-Type: application/json" \
"https://dns.hetzner.com/api/v1${endpoint}" "$@")
verbose "API ${method} ${endpoint} → HTTP ${http_code}"
if [[ "$http_code" == "429" ]]; then
((attempt++)) || true
local wait=$(( attempt * 5 ))
warn "Rate limited — retrying in ${wait}s (attempt ${attempt}/${max_attempts})"
sleep "$wait"
continue
fi
if [[ "$http_code" =~ ^[45] ]]; then
local errmsg
errmsg=$(jq -r '.error.message // .message // empty' /tmp/hdm_resp.json 2>/dev/null)
[[ -n "$errmsg" ]] && verbose "API error: ${errmsg}"
fi
cat /tmp/hdm_resp.json
return 0
done
err "API request failed after ${max_attempts} attempts: ${method} ${endpoint}"
return 1
}
hdns_api_raw() {
local method="$1" endpoint="$2"
shift 2
curl -s \
-X "$method" \
-H "Auth-API-Token: ${HETZNER_DNS_TOKEN}" \
"https://dns.hetzner.com/api/v1${endpoint}" "$@"
}
check_credentials() {
[[ -z "$HETZNER_DNS_TOKEN" ]] && die "HETZNER_DNS_TOKEN not set"
}
check_deps() {
command -v curl &>/dev/null || die "curl is required"
command -v jq &>/dev/null || die "jq is required"
}
# ── Zone helpers ─────────────────────────────────────────────────────
resolve_zone_id() {
local name="$1"
local resp
resp=$(hdns_api GET "/zones?name=${name}")
local zid
zid=$(echo "$resp" | jq -r '.zones[0].id // empty' 2>/dev/null)
if [[ -z "$zid" ]]; then
die "Zone not found: ${name}"
fi
echo "$zid"
}
# ══════════════════════════════════════════════════════════════════════
# ZONES
# ══════════════════════════════════════════════════════════════════════
do_zones() {
local page=1 per_page=100 all_data="[]"
while true; do
local resp
resp=$(hdns_api GET "/zones?page=${page}&per_page=${per_page}")
local page_data
page_data=$(echo "$resp" | jq '.zones // []' 2>/dev/null)
local page_count
page_count=$(echo "$page_data" | jq 'length' 2>/dev/null || echo 0)
[[ "$page_count" -eq 0 ]] && break
all_data=$(echo -e "${all_data}\n${page_data}" | jq -s 'add' 2>/dev/null)
(( page_count < per_page )) && break
((page++)) || true
done
local total
total=$(echo "$all_data" | jq 'length' 2>/dev/null || echo 0)
[[ "$total" -eq 0 ]] && die "No zones found"
case "$OUTPUT_FORMAT" in
json)
echo "$all_data" | jq '.'
;;
prometheus)
cat <<EOF
# HELP hetzner_dns_zones_total Total DNS zones
# TYPE hetzner_dns_zones_total gauge
hetzner_dns_zones_total ${total}
EOF
;;
*)
section_header "DNS Zones"
printf " ${BOLD}%-34s %-18s %-10s %-8s %-8s${RESET}\n" \
"ZONE_ID" "NAME" "STATUS" "RECORDS" "TTL"
printf " %s\n" "$(printf '%.0s─' {1..80})"
echo "$all_data" | jq -r \
'.[] | "\(.id)\t\(.name // "unknown")\t\(.status // "—")\t\(.records_count // 0)\t\(.ttl // 0)"' \
2>/dev/null \
| while IFS=$'\t' read -r zid name status rcount ttl; do
printf " %-34s %-18s %-10s %-8s %-8s\n" \
"${zid:0:32}" "${name:0:16}" "$status" "$rcount" "$ttl"
done
echo ""
field "Zones:" "$total"
;;
esac
}
# ══════════════════════════════════════════════════════════════════════
# RECORDS
# ══════════════════════════════════════════════════════════════════════
do_records() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
local resp
resp=$(hdns_api GET "/records?zone_id=${zid}")
local records
records=$(echo "$resp" | jq '.records // []' 2>/dev/null)
local total
total=$(echo "$records" | jq 'length' 2>/dev/null || echo 0)
case "$OUTPUT_FORMAT" in
json)
echo "$records" | jq '.'
;;
prometheus)
cat <<EOF
# HELP hetzner_dns_records_total Total DNS records
# TYPE hetzner_dns_records_total gauge
hetzner_dns_records_total ${total}
# HELP hetzner_dns_zone_records DNS records per zone
# TYPE hetzner_dns_zone_records gauge
hetzner_dns_zone_records{zone="${ZONE_NAME}"} ${total}
EOF
;;
*)
section_header "DNS Records — ${ZONE_NAME}"
printf " ${BOLD}%-34s %-6s %-18s %-26s %-6s${RESET}\n" \
"RECORD_ID" "TYPE" "NAME" "VALUE" "TTL"
printf " %s\n" "$(printf '%.0s─' {1..92})"
echo "$records" | jq -r \
'.[] | "\(.id)\t\(.type)\t\(.name // "@")\t\(.value // "—")\t\(.ttl // 0)"' \
2>/dev/null \
| while IFS=$'\t' read -r rid rtype rname rvalue rttl; do
printf " %-34s %-6s %-18s %-26s %-6s\n" \
"${rid:0:32}" "$rtype" "${rname:0:16}" "${rvalue:0:24}" "$rttl"
done
echo ""
field "Records:" "$total"
;;
esac
}
# ══════════════════════════════════════════════════════════════════════
# ADD
# ══════════════════════════════════════════════════════════════════════
do_add() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
[[ -z "$RECORD_TYPE" ]] && die "Specify --type TYPE"
[[ -z "$RECORD_NAME" ]] && die "Specify --name NAME"
[[ -z "$RECORD_VALUE" ]] && die "Specify --value VALUE"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
local payload
payload=$(jq -n \
--arg zid "$zid" \
--arg type "$RECORD_TYPE" \
--arg name "$RECORD_NAME" \
--arg value "$RECORD_VALUE" \
--argjson ttl "$RECORD_TTL" \
'{zone_id: $zid, type: $type, name: $name, value: $value, ttl: $ttl}')
local resp
resp=$(hdns_api POST "/records" -d "$payload")
local rid
rid=$(echo "$resp" | jq -r '.record.id // empty' 2>/dev/null)
if [[ -n "$rid" ]]; then
echo -e " ${GREEN}${RESET} Record created: ${RECORD_TYPE} ${RECORD_NAME}${RECORD_VALUE} (ID: ${rid})"
((ACTION_OK++)) || true
else
local errmsg
errmsg=$(echo "$resp" | jq -r '.error.message // .message // "unknown error"' 2>/dev/null)
echo -e " ${RED}${RESET} Failed to create record: ${errmsg}"
((ACTION_FAIL++)) || true
fi
}
# ══════════════════════════════════════════════════════════════════════
# UPDATE
# ══════════════════════════════════════════════════════════════════════
do_update() {
[[ -z "$RECORD_ID" ]] && die "Specify --record-id ID"
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
[[ -z "$RECORD_TYPE" ]] && die "Specify --type TYPE"
[[ -z "$RECORD_NAME" ]] && die "Specify --name NAME"
[[ -z "$RECORD_VALUE" ]] && die "Specify --value VALUE"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
local payload
payload=$(jq -n \
--arg zid "$zid" \
--arg type "$RECORD_TYPE" \
--arg name "$RECORD_NAME" \
--arg value "$RECORD_VALUE" \
--argjson ttl "$RECORD_TTL" \
'{zone_id: $zid, type: $type, name: $name, value: $value, ttl: $ttl}')
local resp
resp=$(hdns_api PUT "/records/${RECORD_ID}" -d "$payload")
local rid
rid=$(echo "$resp" | jq -r '.record.id // empty' 2>/dev/null)
if [[ -n "$rid" ]]; then
echo -e " ${GREEN}${RESET} Record updated: ${RECORD_TYPE} ${RECORD_NAME}${RECORD_VALUE} (ID: ${rid})"
((ACTION_OK++)) || true
else
local errmsg
errmsg=$(echo "$resp" | jq -r '.error.message // .message // "unknown error"' 2>/dev/null)
echo -e " ${RED}${RESET} Failed to update record: ${errmsg}"
((ACTION_FAIL++)) || true
fi
}
# ══════════════════════════════════════════════════════════════════════
# DELETE
# ══════════════════════════════════════════════════════════════════════
do_delete() {
[[ -z "$RECORD_ID" ]] && die "Specify --record-id ID"
[[ "$FORCE" != "true" ]] && die "Delete is destructive — use --force to confirm"
local resp
resp=$(hdns_api DELETE "/records/${RECORD_ID}")
echo -e " ${GREEN}${RESET} Record deleted: ${RECORD_ID}"
((ACTION_OK++)) || true
}
# ══════════════════════════════════════════════════════════════════════
# EXPORT
# ══════════════════════════════════════════════════════════════════════
do_export() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
local zone_data
zone_data=$(hdns_api_raw GET "/zones/${zid}/export")
if [[ -n "$EXPORT_FILE" ]]; then
echo "$zone_data" > "$EXPORT_FILE"
echo -e " ${GREEN}${RESET} Zone exported to ${EXPORT_FILE}"
else
echo "$zone_data"
fi
}
# ══════════════════════════════════════════════════════════════════════
# IMPORT
# ══════════════════════════════════════════════════════════════════════
do_import() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
[[ -z "$IMPORT_FILE" ]] && die "Specify --file FILE"
[[ ! -f "$IMPORT_FILE" ]] && die "File not found: ${IMPORT_FILE}"
[[ "$FORCE" != "true" ]] && die "Import replaces ALL zone records — use --force to confirm"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
local zone_data
zone_data=$(cat "$IMPORT_FILE")
local http_code
http_code=$(curl -s -o /tmp/hdm_resp.json -w "%{http_code}" \
-X POST \
-H "Auth-API-Token: ${HETZNER_DNS_TOKEN}" \
-H "Content-Type: text/plain" \
"https://dns.hetzner.com/api/v1/zones/${zid}/import" \
--data-binary "$zone_data")
if [[ "$http_code" =~ ^2 ]]; then
echo -e " ${GREEN}${RESET} Zone imported from ${IMPORT_FILE}"
((ACTION_OK++)) || true
else
local errmsg
errmsg=$(jq -r '.error.message // .message // "unknown error"' /tmp/hdm_resp.json 2>/dev/null)
echo -e " ${RED}${RESET} Import failed: ${errmsg}"
((ACTION_FAIL++)) || true
fi
}
# ══════════════════════════════════════════════════════════════════════
# BULK ADD
# ══════════════════════════════════════════════════════════════════════
do_bulk_add() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
[[ -z "$CSV_FILE" ]] && die "Specify --csv FILE"
[[ ! -f "$CSV_FILE" ]] && die "CSV file not found: ${CSV_FILE}"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
section_header "Bulk Add — ${ZONE_NAME}"
local line_num=0
while IFS=',' read -r rtype rname rvalue rttl; do
((line_num++)) || true
[[ -z "$rtype" || "$rtype" =~ ^# ]] && continue
rtype=$(echo "$rtype" | xargs)
rname=$(echo "$rname" | xargs)
rvalue=$(echo "$rvalue" | xargs)
rttl=$(echo "${rttl:-3600}" | xargs)
local payload
payload=$(jq -n \
--arg zid "$zid" \
--arg type "$rtype" \
--arg name "$rname" \
--arg value "$rvalue" \
--argjson ttl "$rttl" \
'{zone_id: $zid, type: $type, name: $name, value: $value, ttl: $ttl}')
local resp
resp=$(hdns_api POST "/records" -d "$payload")
local rid
rid=$(echo "$resp" | jq -r '.record.id // empty' 2>/dev/null)
if [[ -n "$rid" ]]; then
echo -e " ${GREEN}${RESET} ${rtype} ${rname}${rvalue} (line ${line_num})"
((ACTION_OK++)) || true
else
echo -e " ${RED}${RESET} ${rtype} ${rname}${rvalue} (line ${line_num})"
((ACTION_FAIL++)) || true
fi
sleep 0.5
done < "$CSV_FILE"
echo ""
field_color "Succeeded:" "${GREEN}${ACTION_OK}${RESET}"
if [[ "$ACTION_FAIL" -gt 0 ]]; then
field_color "Failed:" "${RED}${ACTION_FAIL}${RESET}"
fi
}
# ══════════════════════════════════════════════════════════════════════
# AUDIT
# ══════════════════════════════════════════════════════════════════════
do_audit() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
local zid
zid=$(resolve_zone_id "$ZONE_NAME")
local resp
resp=$(hdns_api GET "/records?zone_id=${zid}")
local records
records=$(echo "$resp" | jq '.records // []' 2>/dev/null)
local total
total=$(echo "$records" | jq 'length' 2>/dev/null || echo 0)
local warnings=0
if [[ "$OUTPUT_FORMAT" != "prometheus" ]]; then
section_header "DNS Audit — ${ZONE_NAME}"
field "Records:" "$total"
echo ""
fi
# Check SOA
local soa_count
soa_count=$(echo "$records" | jq '[.[] | select(.type == "SOA")] | length' 2>/dev/null || echo 0)
if [[ "$soa_count" -eq 0 ]]; then
((warnings++)) || true
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${YELLOW}${RESET} No SOA record found"
else
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${GREEN}${RESET} SOA record present"
fi
# Check NS
local ns_count
ns_count=$(echo "$records" | jq '[.[] | select(.type == "NS")] | length' 2>/dev/null || echo 0)
if [[ "$ns_count" -eq 0 ]]; then
((warnings++)) || true
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${RED}${RESET} No NS records found"
elif [[ "$ns_count" -lt 2 ]]; then
((warnings++)) || true
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${YELLOW}${RESET} Only ${ns_count} NS record(s) — recommend at least 2"
else
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${GREEN}${RESET} ${ns_count} NS records"
fi
# Check common types
for rtype in A AAAA MX TXT; do
local rcount
rcount=$(echo "$records" | jq --arg t "$rtype" '[.[] | select(.type == $t)] | length' 2>/dev/null || echo 0)
if [[ "$rcount" -eq 0 ]]; then
((warnings++)) || true
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${YELLOW}${RESET} No ${rtype} records found"
else
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${GREEN}${RESET} ${rcount} ${rtype} record(s)"
fi
done
# Check low TTLs
local low_ttl
low_ttl=$(echo "$records" | jq '[.[] | select(.ttl < 300 and .ttl > 0)] | length' 2>/dev/null || echo 0)
if [[ "$low_ttl" -gt 0 ]]; then
((warnings++)) || true
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${YELLOW}${RESET} ${low_ttl} record(s) with TTL < 300s"
else
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${GREEN}${RESET} All TTLs ≥ 300s"
fi
# Check wildcards
local wildcard
wildcard=$(echo "$records" | jq '[.[] | select(.name | startswith("*"))] | length' 2>/dev/null || echo 0)
if [[ "$wildcard" -gt 0 ]]; then
[[ "$OUTPUT_FORMAT" != "prometheus" ]] && echo -e " ${CYAN}${RESET} ${wildcard} wildcard record(s)"
fi
if [[ "$OUTPUT_FORMAT" == "prometheus" ]]; then
cat <<EOF
# HELP hetzner_dns_audit_warnings DNS audit warnings
# TYPE hetzner_dns_audit_warnings gauge
hetzner_dns_audit_warnings{zone="${ZONE_NAME}"} ${warnings}
EOF
return
fi
echo ""
if [[ "$warnings" -eq 0 ]]; then
field_color "Warnings:" "${GREEN}0${RESET}"
else
field_color "Warnings:" "${YELLOW}${warnings}${RESET}"
fi
}
# ══════════════════════════════════════════════════════════════════════
# HELP
# ══════════════════════════════════════════════════════════════════════
show_help() {
cat <<EOF
${BOLD}${SCRIPT_NAME}${RESET} — Hetzner DNS Manager
Manage DNS zones and records via the Hetzner DNS API.
${BOLD}MODES${RESET}
--zones List all DNS zones
--records List records for a zone (requires --zone)
--add Add a DNS record
--update Update a DNS record (requires --record-id)
--delete Delete a DNS record (requires --record-id, --force)
--export Export zone in BIND format
--import Import zone from BIND file (requires --file, --force)
--bulk-add Bulk add records from CSV (requires --csv)
--audit Audit zone for common issues
${BOLD}TARGETING${RESET}
--zone DOMAIN Target zone by domain name (resolved to ID internally)
--record-id ID Target a specific record by ID
${BOLD}RECORD FIELDS${RESET}
--type TYPE Record type (A, AAAA, CNAME, MX, TXT, SRV, etc.)
--name NAME Record name — subdomain part (e.g., "www", "@" for apex)
--value VALUE Record value (e.g., IP address, hostname)
--ttl TTL TTL in seconds (default: 3600)
${BOLD}OPTIONS${RESET}
--format FMT Output: table, json, prometheus (default: table)
--csv FILE CSV file for bulk add (type,name,value,ttl)
--file FILE BIND zone file for import
--output FILE Write export to file instead of stdout
--force Required for delete and import operations
--verbose Debug output
--no-color Disable colored output
--help Show this help message
${BOLD}ENVIRONMENT VARIABLES${RESET}
HETZNER_DNS_TOKEN Hetzner DNS API token (required, separate from HCLOUD_TOKEN)
HDM_FORMAT Default output format
VERBOSE Enable verbose output (true/false)
COLOR Color mode: auto, always, never
${BOLD}EXAMPLES${RESET}
# List all zones
${SCRIPT_NAME} --zones
# List records for a zone
${SCRIPT_NAME} --records --zone example.com
# Add an A record
${SCRIPT_NAME} --add --zone example.com --type A --name www --value 168.119.10.50
# Add MX record with custom TTL
${SCRIPT_NAME} --add --zone example.com --type MX --name @ --value "10 mail.example.com" --ttl 7200
# Update a record
${SCRIPT_NAME} --update --record-id abc123 --zone example.com --type A --name www --value 168.119.10.51
# Delete a record
${SCRIPT_NAME} --delete --record-id abc123 --force
# Export zone as BIND file
${SCRIPT_NAME} --export --zone example.com --output example.com.zone
# Import BIND zone file
${SCRIPT_NAME} --import --zone example.com --file example.com.zone --force
# Bulk add from CSV
${SCRIPT_NAME} --bulk-add --zone example.com --csv records.csv
# Audit zone
${SCRIPT_NAME} --audit --zone example.com
# Prometheus metrics
${SCRIPT_NAME} --zones --format prometheus
${BOLD}EXIT CODES${RESET}
0 Success
1 Runtime error
EOF
}
# ══════════════════════════════════════════════════════════════════════
# PARSE ARGS
# ══════════════════════════════════════════════════════════════════════
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--zones) RUN_MODE="zones"; shift ;;
--records) RUN_MODE="records"; shift ;;
--add) RUN_MODE="add"; shift ;;
--update) RUN_MODE="update"; shift ;;
--delete) RUN_MODE="delete"; shift ;;
--export) RUN_MODE="export"; shift ;;
--import) RUN_MODE="import"; shift ;;
--bulk-add) RUN_MODE="bulk-add"; shift ;;
--audit) RUN_MODE="audit"; shift ;;
--zone) ZONE_NAME="${2:?--zone requires a DOMAIN}"; shift 2 ;;
--record-id) RECORD_ID="${2:?--record-id requires an ID}"; shift 2 ;;
--type) RECORD_TYPE="${2:?--type requires a TYPE}"; shift 2 ;;
--name) RECORD_NAME="${2:?--name requires a NAME}"; shift 2 ;;
--value) RECORD_VALUE="${2:?--value requires a VALUE}"; shift 2 ;;
--ttl) RECORD_TTL="${2:?--ttl requires a TTL}"; shift 2 ;;
--csv) CSV_FILE="${2:?--csv requires a FILE}"; shift 2 ;;
--file) IMPORT_FILE="${2:?--file requires a FILE}"; shift 2 ;;
--output) EXPORT_FILE="${2:?--output requires a FILE}"; shift 2 ;;
--format) OUTPUT_FORMAT="${2:?--format requires a value}"; shift 2 ;;
--force) FORCE="true"; shift ;;
--verbose) VERBOSE="true"; shift ;;
--no-color) COLOR="never"; shift ;;
--help|-h) setup_colors; show_help; exit 0 ;;
*) die "Unknown option: $1 (see --help)" ;;
esac
done
}
# ══════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════
main() {
parse_args "$@"
setup_colors
if [[ -z "$RUN_MODE" ]]; then
err "No mode specified"
echo ""
show_help
exit 1
fi
check_deps
check_credentials
START_TIME=$(date +%s)
case "$RUN_MODE" in
zones) do_zones ;;
records) do_records ;;
add) do_add ;;
update) do_update ;;
delete) do_delete ;;
export) do_export ;;
import) do_import ;;
bulk-add) do_bulk_add ;;
audit) do_audit ;;
*) die "Unknown mode: ${RUN_MODE}" ;;
esac
if [[ "$OUTPUT_FORMAT" != "prometheus" ]]; then
echo ""
field "Duration:" "$(elapsed)"
fi
}
main "$@"