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.
This commit is contained in:
2026-05-25 03:31:08 +02:00
parent dbd6bf0324
commit a1a17e81a1
332 changed files with 174509 additions and 1106 deletions
+648
View File
@@ -0,0 +1,648 @@
#!/usr/bin/env bash
#########################################################################################
#### contabo-dns-manager.sh — Manage DNS zones and records via the Contabo DNS API ####
#### List zones, add/update/delete records, audit, bulk operations ####
#### Requires: bash 4+, curl, jq ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.01 ####
#### ####
#### Usage: ####
#### ./contabo-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=""
RECORD_ID=""
RECORD_TYPE=""
RECORD_NAME=""
RECORD_CONTENT=""
RECORD_TTL="3600"
RECORD_PRIO=""
CSV_FILE=""
OUTPUT_FORMAT="${CDM_FORMAT:-table}"
FORCE="false"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
# ── Credentials ───────────────────────────────────────────────────────
CONTABO_CLIENT_ID="${CONTABO_CLIENT_ID:-}"
CONTABO_CLIENT_SECRET="${CONTABO_CLIENT_SECRET:-}"
CONTABO_API_USER="${CONTABO_API_USER:-}"
CONTABO_API_PASS="${CONTABO_API_PASS:-}"
# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
START_TIME=""
ACTION_OK=0
ACTION_FAIL=0
# ── API helpers ──────────────────────────────────────────────────────
contabo_token() {
local resp
resp=$(curl -s -d "client_id=${CONTABO_CLIENT_ID}" \
-d "client_secret=${CONTABO_CLIENT_SECRET}" \
--data-urlencode "username=${CONTABO_API_USER}" \
--data-urlencode "password=${CONTABO_API_PASS}" \
-d "grant_type=password" \
"https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token")
local token
token=$(echo "$resp" | jq -r '.access_token // empty' 2>/dev/null)
if [[ -z "$token" ]]; then
die "Failed to obtain access token — check credentials"
fi
echo "$token"
}
contabo_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/cdm_resp.json -w "%{http_code}" \
-X "$method" \
-H "Authorization: Bearer $(contabo_token)" \
-H "Content-Type: application/json" \
-H "x-request-id: $(cat /proc/sys/kernel/random/uuid 2>/dev/null || date +%s%N)" \
"https://api.contabo.com/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
cat /tmp/cdm_resp.json
return 0
done
err "API request failed after ${max_attempts} attempts: ${method} ${endpoint}"
return 1
}
check_credentials() {
[[ -z "$CONTABO_CLIENT_ID" ]] && die "CONTABO_CLIENT_ID not set"
[[ -z "$CONTABO_CLIENT_SECRET" ]] && die "CONTABO_CLIENT_SECRET not set"
[[ -z "$CONTABO_API_USER" ]] && die "CONTABO_API_USER not set"
[[ -z "$CONTABO_API_PASS" ]] && die "CONTABO_API_PASS not set"
}
check_deps() {
command -v curl &>/dev/null || die "curl is required"
command -v jq &>/dev/null || die "jq is required"
}
# ══════════════════════════════════════════════════════════════════════
# ZONES
# ══════════════════════════════════════════════════════════════════════
do_zones() {
local page=1 size=100 all_data="[]"
while true; do
local resp
resp=$(contabo_api GET "/dns/zones?page=${page}&size=${size}")
local page_data
page_data=$(echo "$resp" | jq '.data // []' 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 < size )) && 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 contabo_dns_zones_total Total DNS zones
# TYPE contabo_dns_zones_total gauge
contabo_dns_zones_total ${total}
EOF
;;
*)
section_header "DNS Zones"
printf " ${BOLD}%-25s %-10s %-36s %-8s${RESET}\n" \
"ZONE_NAME" "STATUS" "DNS_ZONE_ID" "RECORDS"
printf " %s\n" "$(printf '%.0s─' {1..81})"
echo "$all_data" | jq -r \
'.[] | "\(.dnsZoneName // .name // "unknown")\t\(.status // "—")\t\(.dnsZoneId // .id // "—")\t\(.records // 0)"' \
2>/dev/null \
| while IFS=$'\t' read -r name status zid rcount; do
printf " %-25s %-10s %-36s %-8s\n" \
"${name:0:23}" "$status" "${zid:0:34}" "$rcount"
done
echo ""
field "Zones:" "$total"
;;
esac
}
# ══════════════════════════════════════════════════════════════════════
# RECORDS
# ══════════════════════════════════════════════════════════════════════
do_records() {
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
local resp
resp=$(contabo_api GET "/dns/zones/${ZONE_NAME}/records")
local records
records=$(echo "$resp" | jq '.data // []' 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 contabo_dns_records_total Total DNS records
# TYPE contabo_dns_records_total gauge
contabo_dns_records_total ${total}
# HELP contabo_dns_zone_records DNS records per zone
# TYPE contabo_dns_zone_records gauge
contabo_dns_zone_records{zone="${ZONE_NAME}"} ${total}
EOF
;;
*)
section_header "DNS Records — ${ZONE_NAME}"
printf " ${BOLD}%-36s %-6s %-10s %-26s %-6s %-5s${RESET}\n" \
"RECORD_ID" "TYPE" "NAME" "CONTENT" "TTL" "PRIO"
printf " %s\n" "$(printf '%.0s─' {1..91})"
echo "$records" | jq -r \
'.[] | "\(.recordId // .id // "—")\t\(.type // "—")\t\(.name // "@")\t\(.content // "—")\t\(.ttl // 0)\t\(.prio // "—")"' \
2>/dev/null \
| while IFS=$'\t' read -r rid rtype rname rcontent rttl rprio; do
printf " %-36s %-6s %-10s %-26s %-6s %-5s\n" \
"${rid:0:34}" "$rtype" "${rname:0:8}" "${rcontent:0:24}" "$rttl" "$rprio"
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_CONTENT" ]] && die "Specify --content CONTENT"
local payload
payload=$(jq -n \
--arg type "$RECORD_TYPE" \
--arg name "$RECORD_NAME" \
--arg content "$RECORD_CONTENT" \
--argjson ttl "$RECORD_TTL" \
'{type: $type, name: $name, content: $content, ttl: $ttl}')
if [[ -n "$RECORD_PRIO" ]]; then
payload=$(echo "$payload" | jq --argjson prio "$RECORD_PRIO" '. + {prio: $prio}')
fi
local resp
resp=$(contabo_api POST "/dns/zones/${ZONE_NAME}/records" -d "$payload")
local rid
rid=$(echo "$resp" | jq -r '.data[0].recordId // .data[0].id // empty' 2>/dev/null)
if [[ -n "$rid" ]]; then
echo -e " ${GREEN}${RESET} Record created: ${RECORD_TYPE} ${RECORD_NAME}${RECORD_CONTENT} (ID: ${rid})"
((ACTION_OK++)) || true
else
local errmsg
errmsg=$(echo "$resp" | jq -r '.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_CONTENT" ]] && die "Specify --content CONTENT"
local payload
payload=$(jq -n \
--arg type "$RECORD_TYPE" \
--arg name "$RECORD_NAME" \
--arg content "$RECORD_CONTENT" \
--argjson ttl "$RECORD_TTL" \
'{type: $type, name: $name, content: $content, ttl: $ttl}')
if [[ -n "$RECORD_PRIO" ]]; then
payload=$(echo "$payload" | jq --argjson prio "$RECORD_PRIO" '. + {prio: $prio}')
fi
local resp
resp=$(contabo_api PUT "/dns/zones/${ZONE_NAME}/records/${RECORD_ID}" -d "$payload")
local rid
rid=$(echo "$resp" | jq -r '.data[0].recordId // .data[0].id // empty' 2>/dev/null)
if [[ -n "$rid" ]]; then
echo -e " ${GREEN}${RESET} Record updated: ${RECORD_TYPE} ${RECORD_NAME}${RECORD_CONTENT} (ID: ${rid})"
((ACTION_OK++)) || true
else
local errmsg
errmsg=$(echo "$resp" | jq -r '.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"
[[ -z "$ZONE_NAME" ]] && die "Specify --zone DOMAIN"
[[ "$FORCE" != "true" ]] && die "Delete is destructive — use --force to confirm"
local resp
resp=$(contabo_api DELETE "/dns/zones/${ZONE_NAME}/records/${RECORD_ID}")
echo -e " ${GREEN}${RESET} Record deleted: ${RECORD_ID}"
((ACTION_OK++)) || true
}
# ══════════════════════════════════════════════════════════════════════
# 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}"
section_header "Bulk Add — ${ZONE_NAME}"
local line_num=0
while IFS=',' read -r rtype rname rcontent rttl rprio; do
((line_num++)) || true
[[ -z "$rtype" || "$rtype" =~ ^# ]] && continue
rtype=$(echo "$rtype" | xargs)
rname=$(echo "$rname" | xargs)
rcontent=$(echo "$rcontent" | xargs)
rttl=$(echo "${rttl:-3600}" | xargs)
rprio=$(echo "${rprio:-}" | xargs)
local payload
payload=$(jq -n \
--arg type "$rtype" \
--arg name "$rname" \
--arg content "$rcontent" \
--argjson ttl "$rttl" \
'{type: $type, name: $name, content: $content, ttl: $ttl}')
if [[ -n "$rprio" ]]; then
payload=$(echo "$payload" | jq --argjson prio "$rprio" '. + {prio: $prio}')
fi
local resp
resp=$(contabo_api POST "/dns/zones/${ZONE_NAME}/records" -d "$payload")
local rid
rid=$(echo "$resp" | jq -r '.data[0].recordId // .data[0].id // empty' 2>/dev/null)
if [[ -n "$rid" ]]; then
echo -e " ${GREEN}${RESET} ${rtype} ${rname}${rcontent} (line ${line_num})"
((ACTION_OK++)) || true
else
echo -e " ${RED}${RESET} ${rtype} ${rname}${rcontent} (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 resp
resp=$(contabo_api GET "/dns/zones/${ZONE_NAME}/records")
local records
records=$(echo "$resp" | jq '.data // []' 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 contabo_dns_audit_warnings DNS audit warnings
# TYPE contabo_dns_audit_warnings gauge
contabo_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} — Contabo DNS Manager
Manage DNS zones and records via the Contabo 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)
--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 (used as URL path parameter)
--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 or @ for apex)
--content VALUE Record content (IP address, hostname, etc.)
--ttl TTL TTL in seconds (default: 3600)
--prio PRIO Priority (for MX records)
${BOLD}OPTIONS${RESET}
--format FMT Output: table, json, prometheus (default: table)
--csv FILE CSV file for bulk add (type,name,content,ttl,prio)
--force Required for delete operations
--verbose Debug output
--no-color Disable colored output
--help Show this help message
${BOLD}ENVIRONMENT VARIABLES${RESET}
CONTABO_CLIENT_ID OAuth2 Client ID (required)
CONTABO_CLIENT_SECRET OAuth2 Client Secret (required)
CONTABO_API_USER API username / email (required)
CONTABO_API_PASS API password (required)
CDM_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 --content 203.0.113.10
# Add MX record with priority
${SCRIPT_NAME} --add --zone example.com --type MX --name @ --content mail.example.com --prio 10
# Update a record
${SCRIPT_NAME} --update --record-id a1b2c3d4 --zone example.com --type A --name www --content 203.0.113.11
# Delete a record
${SCRIPT_NAME} --delete --record-id a1b2c3d4 --zone example.com --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}NOTES${RESET}
Contabo DNS does NOT support BIND zone file export/import.
Use --content (not --value) for record data to match Contabo API.
${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 ;;
--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 ;;
--content) RECORD_CONTENT="${2:?--content requires a VALUE}"; shift 2 ;;
--ttl) RECORD_TTL="${2:?--ttl requires a TTL}"; shift 2 ;;
--prio) RECORD_PRIO="${2:?--prio requires a PRIO}"; shift 2 ;;
--csv) CSV_FILE="${2:?--csv 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 ;;
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 "$@"