diff --git a/skills/namecheap/SKILL.md b/skills/namecheap/SKILL.md new file mode 100644 index 00000000..426b3b48 --- /dev/null +++ b/skills/namecheap/SKILL.md @@ -0,0 +1,109 @@ +--- +name: namecheap +description: 'Manage DNS records for domains registered with Namecheap via their API. List domains, view/add/update/remove DNS host entries (A, AAAA, CNAME, MX, TXT, etc.), and guide users through API setup including public IP detection and credential configuration. Use when the user mentions Namecheap, DNS records, domain management, or wants to add/change/remove A records, CNAME records, MX records, or TXT records for their domains.' +--- + +# Namecheap DNS Management + +**UTILITY SKILL** — manages DNS records via the Namecheap API. +USE FOR: "add DNS record", "update A record", "manage Namecheap domains", "set CNAME", "add MX record", "add TXT record", "list my domains", "show DNS records", "namecheap setup", "configure namecheap API", "what is my public IP" +DO NOT USE FOR: domain registration/purchase, SSL certificate management, hosting configuration, non-Namecheap DNS providers + +## Workflow + +### First-time Setup + +Before executing any API commands, verify credentials are configured: + +1. **Check for existing config** — look for `~/.namecheap-api` +2. If not configured, guide the user through setup: + a. **Show public IP** — run `curl -s https://api.ipify.org` to display the user's public IP + b. **Instruct IP whitelisting** — tell the user to go to https://ap.www.namecheap.com/settings/tools/apiaccess/, enable API (select ON), and whitelist the displayed IP + c. **Collect credentials** — use `ask_user` to get their Namecheap username, then their API key + d. **Save config** — write credentials to `~/.namecheap-api` with `chmod 600` + e. **Validate** — run a test API call to confirm access works + +### DNS Operations + +Use the `namecheap.sh` script (bundled in this skill's directory) for all API interactions: + +```bash +# Show public IP (for setup) +bash namecheap.sh public-ip + +# Run setup flow +bash namecheap.sh setup + +# List domains +bash namecheap.sh domains.getList + +# Get nameservers for a domain (shows if using Namecheap DNS or custom) +bash namecheap.sh domains.dns.getList --domain example.com + +# Get DNS records for a domain +bash namecheap.sh domains.dns.getHosts --domain example.com + +# Add a single record (preserves existing records) +bash namecheap.sh dns.addHost --domain example.com --type A --name www --address 1.2.3.4 --ttl 1800 + +# Remove a single record +bash namecheap.sh dns.removeHost --domain example.com --type A --name www --address 1.2.3.4 + +# Replace all records from a JSON file +bash namecheap.sh domains.dns.setHosts --domain example.com --hosts records.json + +# Switch to Namecheap default DNS +bash namecheap.sh domains.dns.setDefault --domain example.com + +# Switch to custom nameservers +bash namecheap.sh domains.dns.setCustom --domain example.com --nameservers ns1.cloudflare.com,ns2.cloudflare.com + +# Get email forwarding rules +bash namecheap.sh domains.dns.getEmailForwarding --domain example.com + +# Set email forwarding (single rule) +bash namecheap.sh domains.dns.setEmailForwarding --domain example.com --mailbox info --forward-to user@gmail.com + +# Set email forwarding (from JSON file) +bash namecheap.sh domains.dns.setEmailForwarding --domain example.com --forwards forwards.json + +# Create a child nameserver (glue record) +bash namecheap.sh domains.ns.create --domain example.com --nameserver ns1.example.com --ip 1.2.3.4 + +# Delete a child nameserver +bash namecheap.sh domains.ns.delete --domain example.com --nameserver ns1.example.com + +# Get nameserver info +bash namecheap.sh domains.ns.getInfo --domain example.com --nameserver ns1.example.com + +# Update nameserver IP +bash namecheap.sh domains.ns.update --domain example.com --nameserver ns1.example.com --old-ip 1.2.3.4 --ip 5.6.7.8 +``` + +## Behavior + +- **Always check credentials first.** Before any API operation, verify `~/.namecheap-api` exists and is readable. If not, run the setup flow. +- **Show current records before modifying.** Before adding or removing records, always fetch and display the current DNS records so the user can confirm the change. +- **Use `ask_user` to confirm destructive changes.** Before removing records or replacing all records with `setHosts`, confirm with the user. +- **The Namecheap `setHosts` API replaces ALL records.** Never call `domains.dns.setHosts` directly unless you have fetched all existing records first. Use `dns.addHost` and `dns.removeHost` for safe single-record operations — they handle the fetch-modify-write cycle internally. +- **Explain TTL in human terms.** When the user asks about TTL, explain that 1800 = 30 minutes, 3600 = 1 hour, etc. +- **Handle multi-part TLDs.** Domains like `example.co.uk` have SLD=example and TLD=co.uk. The script handles this automatically. + +## Credential Storage + +Credentials are stored in `~/.namecheap-api`: + +```bash +NAMECHEAP_API_USER="username" +NAMECHEAP_API_KEY="api-key-here" +``` + +This file must have `600` permissions (owner read/write only). + +## Supported Record Types + +A, AAAA, CNAME, MX, MXE, TXT, URL, URL301, FRAME + +## References + +See `references/namecheap-api.md` for full API documentation including request/response formats. diff --git a/skills/namecheap/namecheap.sh b/skills/namecheap/namecheap.sh new file mode 100755 index 00000000..8cc246bc --- /dev/null +++ b/skills/namecheap/namecheap.sh @@ -0,0 +1,919 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Namecheap API CLI wrapper +# Usage: ./namecheap.sh [options] + +NAMECHEAP_API_URL="https://api.namecheap.com/xml.response" +CONFIG_FILE="$HOME/.namecheap-api" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +print_error() { echo -e "${RED}Error:${NC} $1" >&2; } +print_success() { echo -e "${GREEN}✓${NC} $1"; } +print_info() { echo -e "${CYAN}ℹ${NC} $1"; } +print_warn() { echo -e "${YELLOW}⚠${NC} $1"; } + +# Load configuration +load_config() { + if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck source=/dev/null + source "$CONFIG_FILE" + fi +} + +# Check if credentials are configured +check_credentials() { + load_config + if [[ -z "${NAMECHEAP_API_USER:-}" || -z "${NAMECHEAP_API_KEY:-}" ]]; then + print_error "Namecheap API credentials not configured." + echo "" + echo "Run './namecheap.sh setup' to configure your credentials." + echo "" + echo "You need:" + echo " 1. Your Namecheap username" + echo " 2. An API key from: https://ap.www.namecheap.com/settings/tools/apiaccess/" + echo " 3. Your public IP whitelisted in the API settings" + exit 1 + fi +} + +# Get public IP address +get_public_ip() { + curl -s https://api.ipify.org 2>/dev/null || curl -s https://ifconfig.me 2>/dev/null || echo "unknown" +} + +# Make API request +api_request() { + local command="$1" + shift + local extra_params=("$@") + + check_credentials + local client_ip + client_ip=$(get_public_ip) + + local url="${NAMECHEAP_API_URL}?ApiUser=${NAMECHEAP_API_USER}&ApiKey=${NAMECHEAP_API_KEY}&UserName=${NAMECHEAP_API_USER}&Command=namecheap.${command}&ClientIp=${client_ip}" + + for param in "${extra_params[@]}"; do + url="${url}&${param}" + done + + local response + response=$(curl -s "$url") + + # Check for errors in the response + if echo "$response" | grep -q 'Status="ERROR"'; then + local error_msg + error_msg=$(echo "$response" | grep -oP '(?<=]*>\K[^<]+' 2>/dev/null || echo "$response" | sed -n 's/.*]*>\(.*\)<\/Err>.*/\1/p') + print_error "API returned error: $error_msg" + return 1 + fi + + echo "$response" +} + +# Parse domain into SLD and TLD +parse_domain() { + local domain="$1" + local tld sld + + # Handle multi-part TLDs (e.g., co.uk, com.br) + if echo "$domain" | grep -qE '\.(co|com|net|org|gov)\.[a-z]{2}$'; then + tld=$(echo "$domain" | grep -oE '\.[^.]+\.[^.]+$' | sed 's/^\.//') + sld=$(echo "$domain" | sed "s/\.${tld}$//") + else + tld="${domain##*.}" + sld="${domain%.*}" + fi + + echo "$sld" "$tld" +} + +# Format XML DNS records as a table +format_dns_records() { + local xml="$1" + + # Extract host records + echo "" + printf "%-20s %-8s %-40s %-8s %-6s\n" "HOST" "TYPE" "ADDRESS" "TTL" "MXPREF" + printf "%-20s %-8s %-40s %-8s %-6s\n" "----" "----" "-------" "---" "------" + + echo "$xml" | grep -oP '' | while read -r line; do + local name type address ttl mxpref + name=$(echo "$line" | grep -oP 'Name="\K[^"]+' || echo "") + type=$(echo "$line" | grep -oP 'Type="\K[^"]+' || echo "") + address=$(echo "$line" | grep -oP 'Address="\K[^"]+' || echo "") + ttl=$(echo "$line" | grep -oP 'TTL="\K[^"]+' || echo "1800") + mxpref=$(echo "$line" | grep -oP 'MXPref="\K[^"]+' || echo "-") + + printf "%-20s %-8s %-40s %-8s %-6s\n" "$name" "$type" "$address" "$ttl" "$mxpref" + done + echo "" +} + +# Format domains list as a table +format_domains_list() { + local xml="$1" + + echo "" + printf "%-30s %-12s %-12s %-10s\n" "DOMAIN" "EXPIRES" "LOCKED" "AUTO-RENEW" + printf "%-30s %-12s %-12s %-10s\n" "------" "-------" "------" "----------" + + echo "$xml" | grep -oP '' | while read -r line; do + local name expires locked autorenew + name=$(echo "$line" | grep -oP 'Name="\K[^"]+' || echo "") + expires=$(echo "$line" | grep -oP 'Expires="\K[^"]+' || echo "") + locked=$(echo "$line" | grep -oP 'IsLocked="\K[^"]+' || echo "") + autorenew=$(echo "$line" | grep -oP 'AutoRenew="\K[^"]+' || echo "") + + printf "%-30s %-12s %-12s %-10s\n" "$name" "$expires" "$locked" "$autorenew" + done + echo "" +} + +# Commands + +cmd_setup() { + echo "=== Namecheap API Setup ===" + echo "" + + # Show public IP + local public_ip + public_ip=$(get_public_ip) + print_info "Your public IP address is: ${CYAN}${public_ip}${NC}" + echo "" + echo "Make sure this IP is whitelisted at:" + echo " https://ap.www.namecheap.com/settings/tools/apiaccess/" + echo "" + + # Check existing config + if [[ -f "$CONFIG_FILE" ]]; then + load_config + if [[ -n "${NAMECHEAP_API_USER:-}" ]]; then + print_info "Existing configuration found for user: ${NAMECHEAP_API_USER}" + echo "" + + # Test the connection + echo "Testing API connection..." + if api_request "domains.getList" "PageSize=1" > /dev/null 2>&1; then + print_success "API connection successful!" + else + print_error "API connection failed. Please check your credentials and IP whitelist." + fi + return 0 + fi + fi + + # Prompt for credentials + echo "Enter your Namecheap credentials:" + echo "" + read -rp " API Username: " api_user + read -rsp " API Key: " api_key + echo "" + echo "" + + if [[ -z "$api_user" || -z "$api_key" ]]; then + print_error "Both username and API key are required." + exit 1 + fi + + # Save configuration + cat > "$CONFIG_FILE" << EOF +NAMECHEAP_API_USER="${api_user}" +NAMECHEAP_API_KEY="${api_key}" +EOF + chmod 600 "$CONFIG_FILE" + print_success "Credentials saved to ${CONFIG_FILE}" + echo "" + + # Test connection + load_config + echo "Testing API connection..." + if api_request "domains.getList" "PageSize=1" > /dev/null 2>&1; then + print_success "API connection successful!" + else + print_warn "API connection failed. Please verify:" + echo " 1. API access is enabled (ON) at the Namecheap settings page" + echo " 2. IP address ${public_ip} is whitelisted" + echo " 3. Your API key is correct" + fi +} + +cmd_domains_list() { + local list_type="ALL" + local search_term="" + local page="1" + local page_size="20" + + while [[ $# -gt 0 ]]; do + case "$1" in + --type) list_type="$2"; shift 2 ;; + --search) search_term="$2"; shift 2 ;; + --page) page="$2"; shift 2 ;; + --page-size) page_size="$2"; shift 2 ;; + *) shift ;; + esac + done + + local params=("ListType=${list_type}" "Page=${page}" "PageSize=${page_size}") + if [[ -n "$search_term" ]]; then + params+=("SearchTerm=${search_term}") + fi + + print_info "Fetching domain list..." + local response + response=$(api_request "domains.getList" "${params[@]}") + format_domains_list "$response" +} + +cmd_dns_get_hosts() { + local domain="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" ]]; then + print_error "Domain is required. Usage: ./namecheap.sh domains.dns.getHosts --domain example.com" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Fetching DNS records for ${domain} (SLD=${sld}, TLD=${tld})..." + local response + response=$(api_request "domains.dns.getHosts" "SLD=${sld}" "TLD=${tld}") + format_dns_records "$response" +} + +cmd_dns_set_hosts() { + local domain="" + local hosts_file="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --hosts) hosts_file="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$hosts_file" ]]; then + print_error "Both --domain and --hosts are required." + echo "Usage: ./namecheap.sh domains.dns.setHosts --domain example.com --hosts hosts.json" + exit 1 + fi + + if [[ ! -f "$hosts_file" ]]; then + print_error "Hosts file not found: ${hosts_file}" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + # Build host parameters from JSON file + local params=("SLD=${sld}" "TLD=${tld}") + local i=1 + + while IFS= read -r line; do + local hostname recordtype address ttl mxpref + hostname=$(echo "$line" | grep -oP '"HostName"\s*:\s*"\K[^"]+' || echo "") + recordtype=$(echo "$line" | grep -oP '"RecordType"\s*:\s*"\K[^"]+' || echo "") + address=$(echo "$line" | grep -oP '"Address"\s*:\s*"\K[^"]+' || echo "") + ttl=$(echo "$line" | grep -oP '"TTL"\s*:\s*"\K[^"]+' || echo "1800") + mxpref=$(echo "$line" | grep -oP '"MXPref"\s*:\s*"\K[^"]+' || echo "") + + if [[ -n "$hostname" && -n "$recordtype" && -n "$address" ]]; then + params+=("HostName${i}=${hostname}") + params+=("RecordType${i}=${recordtype}") + params+=("Address${i}=${address}") + params+=("TTL${i}=${ttl}") + if [[ -n "$mxpref" ]]; then + params+=("MXPref${i}=${mxpref}") + fi + ((i++)) + fi + done < <(python3 -c " +import json, sys +with open('${hosts_file}') as f: + records = json.load(f) +for r in records: + print(json.dumps(r)) +" 2>/dev/null || jq -c '.[]' "$hosts_file") + + if [[ $i -eq 1 ]]; then + print_error "No valid host records found in ${hosts_file}" + exit 1 + fi + + print_info "Setting $((i-1)) DNS records for ${domain}..." + local response + response=$(api_request "domains.dns.setHosts" "${params[@]}") + + if echo "$response" | grep -q 'IsSuccess="true"'; then + print_success "DNS records updated successfully for ${domain}!" + else + print_error "Failed to update DNS records." + echo "$response" + fi +} + +cmd_dns_add_host() { + local domain="" record_type="" name="" address="" ttl="1800" mxpref="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --type) record_type="$2"; shift 2 ;; + --name) name="$2"; shift 2 ;; + --address) address="$2"; shift 2 ;; + --ttl) ttl="$2"; shift 2 ;; + --mxpref) mxpref="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$record_type" || -z "$name" || -z "$address" ]]; then + print_error "Missing required parameters." + echo "Usage: ./namecheap.sh dns.addHost --domain example.com --type A --name \"@\" --address \"1.2.3.4\" [--ttl 1800] [--mxpref 10]" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + # Fetch existing records + print_info "Fetching existing DNS records for ${domain}..." + local response + response=$(api_request "domains.dns.getHosts" "SLD=${sld}" "TLD=${tld}") + + # Build params with existing records + new one + local params=("SLD=${sld}" "TLD=${tld}") + local i=1 + + # Parse existing records + while IFS= read -r line; do + if [[ -z "$line" ]]; then continue; fi + local h_name h_type h_address h_ttl h_mxpref + h_name=$(echo "$line" | grep -oP 'Name="\K[^"]+' || echo "") + h_type=$(echo "$line" | grep -oP 'Type="\K[^"]+' || echo "") + h_address=$(echo "$line" | grep -oP 'Address="\K[^"]+' || echo "") + h_ttl=$(echo "$line" | grep -oP 'TTL="\K[^"]+' || echo "1800") + h_mxpref=$(echo "$line" | grep -oP 'MXPref="\K[^"]+' || echo "") + + if [[ -n "$h_name" && -n "$h_type" && -n "$h_address" ]]; then + params+=("HostName${i}=${h_name}") + params+=("RecordType${i}=${h_type}") + params+=("Address${i}=${h_address}") + params+=("TTL${i}=${h_ttl}") + if [[ -n "$h_mxpref" && "$h_mxpref" != "0" ]]; then + params+=("MXPref${i}=${h_mxpref}") + fi + ((i++)) + fi + done < <(echo "$response" | grep -oP '') + + # Add the new record + params+=("HostName${i}=${name}") + params+=("RecordType${i}=${record_type}") + params+=("Address${i}=${address}") + params+=("TTL${i}=${ttl}") + if [[ -n "$mxpref" ]]; then + params+=("MXPref${i}=${mxpref}") + fi + + print_info "Adding ${record_type} record: ${name} -> ${address}" + local set_response + set_response=$(api_request "domains.dns.setHosts" "${params[@]}") + + if echo "$set_response" | grep -q 'IsSuccess="true"'; then + print_success "DNS record added successfully!" + else + print_error "Failed to add DNS record." + echo "$set_response" + fi +} + +cmd_dns_remove_host() { + local domain="" record_type="" name="" address="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --type) record_type="$2"; shift 2 ;; + --name) name="$2"; shift 2 ;; + --address) address="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$record_type" || -z "$name" ]]; then + print_error "Missing required parameters." + echo "Usage: ./namecheap.sh dns.removeHost --domain example.com --type A --name \"@\" [--address \"1.2.3.4\"]" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + # Fetch existing records + print_info "Fetching existing DNS records for ${domain}..." + local response + response=$(api_request "domains.dns.getHosts" "SLD=${sld}" "TLD=${tld}") + + # Build params excluding the record to remove + local params=("SLD=${sld}" "TLD=${tld}") + local i=1 + local removed=false + + while IFS= read -r line; do + if [[ -z "$line" ]]; then continue; fi + local h_name h_type h_address h_ttl h_mxpref + h_name=$(echo "$line" | grep -oP 'Name="\K[^"]+' || echo "") + h_type=$(echo "$line" | grep -oP 'Type="\K[^"]+' || echo "") + h_address=$(echo "$line" | grep -oP 'Address="\K[^"]+' || echo "") + h_ttl=$(echo "$line" | grep -oP 'TTL="\K[^"]+' || echo "1800") + h_mxpref=$(echo "$line" | grep -oP 'MXPref="\K[^"]+' || echo "") + + # Check if this is the record to remove + if [[ "$h_name" == "$name" && "$h_type" == "$record_type" && "$removed" == "false" ]]; then + if [[ -z "$address" || "$h_address" == "$address" ]]; then + removed=true + print_info "Removing record: ${h_name} ${h_type} ${h_address}" + continue + fi + fi + + if [[ -n "$h_name" && -n "$h_type" && -n "$h_address" ]]; then + params+=("HostName${i}=${h_name}") + params+=("RecordType${i}=${h_type}") + params+=("Address${i}=${h_address}") + params+=("TTL${i}=${h_ttl}") + if [[ -n "$h_mxpref" && "$h_mxpref" != "0" ]]; then + params+=("MXPref${i}=${h_mxpref}") + fi + ((i++)) + fi + done < <(echo "$response" | grep -oP '') + + if [[ "$removed" == "false" ]]; then + print_error "No matching record found to remove." + exit 1 + fi + + # If no records left, we still need at least one (Namecheap requirement) + if [[ $i -eq 1 ]]; then + print_error "Cannot remove the last DNS record. Namecheap requires at least one record." + exit 1 + fi + + print_info "Updating DNS records for ${domain}..." + local set_response + set_response=$(api_request "domains.dns.setHosts" "${params[@]}") + + if echo "$set_response" | grep -q 'IsSuccess="true"'; then + print_success "DNS record removed successfully!" + else + print_error "Failed to remove DNS record." + echo "$set_response" + fi +} + +cmd_public_ip() { + local ip + ip=$(get_public_ip) + echo "$ip" +} + +cmd_dns_get_list() { + local domain="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" ]]; then + print_error "Domain is required. Usage: ./namecheap.sh domains.dns.getList --domain example.com" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Fetching nameservers for ${domain}..." + local response + response=$(api_request "domains.dns.getList" "SLD=${sld}" "TLD=${tld}") + + local using_our_dns + using_our_dns=$(echo "$response" | grep -oP 'IsUsingOurDNS="\K[^"]+' || echo "unknown") + echo "" + print_info "Using Namecheap DNS: ${using_our_dns}" + echo "" + echo "Nameservers:" + echo "$response" | grep -oP '\K[^<]+' | while read -r ns; do + echo " - ${ns}" + done + echo "" +} + +cmd_dns_set_default() { + local domain="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" ]]; then + print_error "Domain is required. Usage: ./namecheap.sh domains.dns.setDefault --domain example.com" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Setting ${domain} to use Namecheap default DNS..." + local response + response=$(api_request "domains.dns.setDefault" "SLD=${sld}" "TLD=${tld}") + + if echo "$response" | grep -q 'Updated="true"'; then + print_success "Domain ${domain} now uses Namecheap default DNS!" + else + print_error "Failed to set default DNS." + echo "$response" + fi +} + +cmd_dns_set_custom() { + local domain="" nameservers="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --nameservers) nameservers="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$nameservers" ]]; then + print_error "Both --domain and --nameservers are required." + echo "Usage: ./namecheap.sh domains.dns.setCustom --domain example.com --nameservers ns1.cloudflare.com,ns2.cloudflare.com" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Setting ${domain} to use custom nameservers: ${nameservers}" + local response + response=$(api_request "domains.dns.setCustom" "SLD=${sld}" "TLD=${tld}" "Nameservers=${nameservers}") + + if echo "$response" | grep -q 'Updated="true"'; then + print_success "Domain ${domain} now uses custom nameservers!" + else + print_error "Failed to set custom nameservers." + echo "$response" + fi +} + +cmd_dns_get_email_forwarding() { + local domain="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" ]]; then + print_error "Domain is required. Usage: ./namecheap.sh domains.dns.getEmailForwarding --domain example.com" + exit 1 + fi + + print_info "Fetching email forwarding for ${domain}..." + local response + response=$(api_request "domains.dns.getEmailForwarding" "DomainName=${domain}") + + echo "" + printf "%-20s %-40s\n" "MAILBOX" "FORWARDS TO" + printf "%-20s %-40s\n" "-------" "-----------" + + echo "$response" | grep -oP '' | while read -r line; do + local mailbox forward_to + mailbox=$(echo "$line" | grep -oP 'mailbox="\K[^"]+' || echo "") + forward_to=$(echo "$line" | grep -oP 'ForwardTo="\K[^"]+' || echo "") + printf "%-20s %-40s\n" "${mailbox}@${domain}" "$forward_to" + done + echo "" +} + +cmd_dns_set_email_forwarding() { + local domain="" forwards_file="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --forwards) forwards_file="$2"; shift 2 ;; + --mailbox) + # Inline single forwarding rule + local inline_mailbox="$2"; shift 2 ;; + --forward-to) + local inline_forward_to="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" ]]; then + print_error "Domain is required." + echo "Usage: ./namecheap.sh domains.dns.setEmailForwarding --domain example.com --mailbox info --forward-to user@gmail.com" + echo " or: ./namecheap.sh domains.dns.setEmailForwarding --domain example.com --forwards forwards.json" + exit 1 + fi + + local params=("DomainName=${domain}") + + if [[ -n "${inline_mailbox:-}" && -n "${inline_forward_to:-}" ]]; then + # Single inline rule + params+=("MailBox1=${inline_mailbox}" "ForwardTo1=${inline_forward_to}") + elif [[ -n "$forwards_file" ]]; then + if [[ ! -f "$forwards_file" ]]; then + print_error "Forwards file not found: ${forwards_file}" + exit 1 + fi + local i=1 + while IFS= read -r line; do + local mailbox forward_to + mailbox=$(echo "$line" | grep -oP '"MailBox"\s*:\s*"\K[^"]+' || echo "") + forward_to=$(echo "$line" | grep -oP '"ForwardTo"\s*:\s*"\K[^"]+' || echo "") + if [[ -n "$mailbox" && -n "$forward_to" ]]; then + params+=("MailBox${i}=${mailbox}" "ForwardTo${i}=${forward_to}") + ((i++)) + fi + done < <(python3 -c " +import json, sys +with open('${forwards_file}') as f: + rules = json.load(f) +for r in rules: + print(json.dumps(r)) +" 2>/dev/null || jq -c '.[]' "$forwards_file") + else + print_error "Provide either --mailbox/--forward-to or --forwards " + exit 1 + fi + + print_info "Setting email forwarding for ${domain}..." + local response + response=$(api_request "domains.dns.setEmailForwarding" "${params[@]}") + + if echo "$response" | grep -q 'IsSuccess="true"'; then + print_success "Email forwarding updated for ${domain}!" + else + print_error "Failed to set email forwarding." + echo "$response" + fi +} + +cmd_ns_create() { + local domain="" nameserver="" ip="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --nameserver) nameserver="$2"; shift 2 ;; + --ip) ip="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$nameserver" || -z "$ip" ]]; then + print_error "Missing required parameters." + echo "Usage: ./namecheap.sh domains.ns.create --domain example.com --nameserver ns1.example.com --ip 1.2.3.4" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Creating nameserver ${nameserver} -> ${ip}..." + local response + response=$(api_request "domains.ns.create" "SLD=${sld}" "TLD=${tld}" "Nameserver=${nameserver}" "IP=${ip}") + + if echo "$response" | grep -q 'IsSuccess="true"'; then + print_success "Nameserver ${nameserver} created!" + else + print_error "Failed to create nameserver." + echo "$response" + fi +} + +cmd_ns_delete() { + local domain="" nameserver="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --nameserver) nameserver="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$nameserver" ]]; then + print_error "Missing required parameters." + echo "Usage: ./namecheap.sh domains.ns.delete --domain example.com --nameserver ns1.example.com" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Deleting nameserver ${nameserver}..." + local response + response=$(api_request "domains.ns.delete" "SLD=${sld}" "TLD=${tld}" "Nameserver=${nameserver}") + + if echo "$response" | grep -q 'IsSuccess="true"'; then + print_success "Nameserver ${nameserver} deleted!" + else + print_error "Failed to delete nameserver." + echo "$response" + fi +} + +cmd_ns_get_info() { + local domain="" nameserver="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --nameserver) nameserver="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$nameserver" ]]; then + print_error "Missing required parameters." + echo "Usage: ./namecheap.sh domains.ns.getInfo --domain example.com --nameserver ns1.example.com" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Fetching info for nameserver ${nameserver}..." + local response + response=$(api_request "domains.ns.getInfo" "SLD=${sld}" "TLD=${tld}" "Nameserver=${nameserver}") + + local ns_ip + ns_ip=$(echo "$response" | grep -oP 'IP="\K[^"]+' || echo "unknown") + echo "" + echo "Nameserver: ${nameserver}" + echo "IP Address: ${ns_ip}" + local statuses + statuses=$(echo "$response" | grep -oP '\K[^<]+' | tr '\n' ', ' | sed 's/,$//') + if [[ -n "$statuses" ]]; then + echo "Status: ${statuses}" + fi + echo "" +} + +cmd_ns_update() { + local domain="" nameserver="" old_ip="" new_ip="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --domain) domain="$2"; shift 2 ;; + --nameserver) nameserver="$2"; shift 2 ;; + --old-ip) old_ip="$2"; shift 2 ;; + --ip) new_ip="$2"; shift 2 ;; + *) shift ;; + esac + done + + if [[ -z "$domain" || -z "$nameserver" || -z "$old_ip" || -z "$new_ip" ]]; then + print_error "Missing required parameters." + echo "Usage: ./namecheap.sh domains.ns.update --domain example.com --nameserver ns1.example.com --old-ip 1.2.3.4 --ip 5.6.7.8" + exit 1 + fi + + local sld tld + read -r sld tld <<< "$(parse_domain "$domain")" + + print_info "Updating nameserver ${nameserver}: ${old_ip} -> ${new_ip}..." + local response + response=$(api_request "domains.ns.update" "SLD=${sld}" "TLD=${tld}" "Nameserver=${nameserver}" "OldIP=${old_ip}" "IP=${new_ip}") + + if echo "$response" | grep -q 'IsSuccess="true"'; then + print_success "Nameserver ${nameserver} updated to ${new_ip}!" + else + print_error "Failed to update nameserver." + echo "$response" + fi +} + +# Help +cmd_help() { + echo "Namecheap DNS Management CLI" + echo "" + echo "Usage: ./namecheap.sh [options]" + echo "" + echo "Commands:" + echo " setup Configure API credentials and test connection" + echo " public-ip Show your public IP address" + echo "" + echo " domains.getList List your Namecheap domains" + echo "" + echo " domains.dns.getList Get nameservers for a domain" + echo " domains.dns.getHosts Get DNS records for a domain" + echo " domains.dns.setHosts Set all DNS records (from JSON file)" + echo " domains.dns.setDefault Use Namecheap default DNS" + echo " domains.dns.setCustom Use custom nameservers" + echo " domains.dns.getEmailForwarding Get email forwarding rules" + echo " domains.dns.setEmailForwarding Set email forwarding rules" + echo "" + echo " domains.ns.create Create a child nameserver (glue record)" + echo " domains.ns.delete Delete a child nameserver" + echo " domains.ns.getInfo Get nameserver info" + echo " domains.ns.update Update nameserver IP" + echo "" + echo " dns.addHost Add a single DNS record (preserves existing)" + echo " dns.removeHost Remove a single DNS record" + echo "" + echo "Options:" + echo " --domain Domain name (e.g., example.com)" + echo " --type Record type (A, AAAA, CNAME, MX, TXT, etc.)" + echo " --name Host name (e.g., @, www, mail)" + echo " --address Record value (IP or target)" + echo " --ttl TTL in seconds (default: 1800)" + echo " --mxpref MX preference (for MX records)" + echo " --hosts JSON file with host records" + echo " --nameservers Comma-separated nameservers" + echo " --nameserver Nameserver hostname" + echo " --ip
IP address for nameserver" + echo " --old-ip
Current IP (for ns.update)" + echo " --mailbox Email mailbox name" + echo " --forward-to Forward destination email" + echo " --forwards JSON file with forwarding rules" + echo " --search Search term for domain list" + echo " --page Page number for domain list" + echo " --page-size Page size for domain list (10-100)" + echo "" + echo "Examples:" + echo " ./namecheap.sh setup" + echo " ./namecheap.sh domains.getList" + echo " ./namecheap.sh domains.dns.getHosts --domain example.com" + echo " ./namecheap.sh dns.addHost --domain example.com --type A --name www --address 1.2.3.4" + echo " ./namecheap.sh dns.removeHost --domain example.com --type A --name www" + echo " ./namecheap.sh domains.dns.setCustom --domain example.com --nameservers ns1.cloudflare.com,ns2.cloudflare.com" + echo " ./namecheap.sh domains.dns.setEmailForwarding --domain example.com --mailbox info --forward-to user@gmail.com" + echo " ./namecheap.sh domains.ns.create --domain example.com --nameserver ns1.example.com --ip 1.2.3.4" +} + +# Main dispatch +main() { + local command="${1:-help}" + shift || true + + case "$command" in + setup) cmd_setup "$@" ;; + public-ip) cmd_public_ip "$@" ;; + domains.getList) cmd_domains_list "$@" ;; + domains.dns.getList) cmd_dns_get_list "$@" ;; + domains.dns.getHosts) cmd_dns_get_hosts "$@" ;; + domains.dns.setHosts) cmd_dns_set_hosts "$@" ;; + domains.dns.setDefault) cmd_dns_set_default "$@" ;; + domains.dns.setCustom) cmd_dns_set_custom "$@" ;; + domains.dns.getEmailForwarding) cmd_dns_get_email_forwarding "$@" ;; + domains.dns.setEmailForwarding) cmd_dns_set_email_forwarding "$@" ;; + domains.ns.create) cmd_ns_create "$@" ;; + domains.ns.delete) cmd_ns_delete "$@" ;; + domains.ns.getInfo) cmd_ns_get_info "$@" ;; + domains.ns.update) cmd_ns_update "$@" ;; + dns.addHost) cmd_dns_add_host "$@" ;; + dns.removeHost) cmd_dns_remove_host "$@" ;; + help|--help|-h) cmd_help ;; + *) + print_error "Unknown command: ${command}" + echo "" + cmd_help + exit 1 + ;; + esac +} + +main "$@" diff --git a/skills/namecheap/references/namecheap-api.md b/skills/namecheap/references/namecheap-api.md new file mode 100644 index 00000000..312121f0 --- /dev/null +++ b/skills/namecheap/references/namecheap-api.md @@ -0,0 +1,392 @@ +# Namecheap API Reference + +## Base URL + +``` +https://api.namecheap.com/xml.response +``` + +## Authentication + +All requests require these common parameters: + +| Parameter | Description | +|-----------|-------------| +| `ApiUser` | Namecheap username | +| `ApiKey` | API key from https://ap.www.namecheap.com/settings/tools/apiaccess/ | +| `UserName` | Same as ApiUser | +| `ClientIp` | The whitelisted public IP address of the client | +| `Command` | The API command prefixed with `namecheap.` | + +## Setup Requirements + +1. Log in to Namecheap +2. Go to https://ap.www.namecheap.com/settings/tools/apiaccess/ +3. Enable API Access (toggle to ON) +4. Add the client's public IP address to the whitelist +5. Copy the generated API key + +## Commands + +--- + +### namecheap.domains.getList + +Lists all domains in the account. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `ListType` | No | `ALL` (default), `EXPIRING`, or `EXPIRED` | +| `SearchTerm` | No | Keyword to filter domains | +| `Page` | No | Page number (default: 1) | +| `PageSize` | No | Results per page, 10-100 (default: 20) | +| `SortBy` | No | `NAME`, `NAME_DESC`, `EXPIREDATE`, `EXPIREDATE_DESC`, `CREATEDATE`, `CREATEDATE_DESC` | + +**Response XML:** + +```xml + + + + + + 5120 + + +``` + +--- + +### namecheap.domains.dns.getList + +Gets the list of DNS servers associated with a domain (shows whether it uses Namecheap DNS or custom nameservers). + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain (e.g., `example` for `example.com`) | +| `TLD` | Yes | Top-level domain (e.g., `com` for `example.com`) | + +**Response XML:** + +```xml + + + + dns1.registrar-servers.com + dns2.registrar-servers.com + + + +``` + +--- + +### namecheap.domains.dns.getHosts + +Gets DNS host records for a domain. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain (e.g., `example` for `example.com`) | +| `TLD` | Yes | Top-level domain (e.g., `com` for `example.com`) | + +**Response XML:** + +```xml + + + + + + + + + + +``` + +--- + +### namecheap.domains.dns.setHosts + +Sets (replaces) all DNS host records for a domain. + +**IMPORTANT:** This command replaces ALL existing records. Always fetch existing records first. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | +| `HostNameN` | Yes | Host name for record N (e.g., `@`, `www`, `mail`) | +| `RecordTypeN` | Yes | Record type for record N (A, AAAA, CNAME, MX, TXT, etc.) | +| `AddressN` | Yes | Value for record N (IP address or target hostname) | +| `MXPrefN` | No | MX priority for record N (required for MX records) | +| `TTLN` | No | TTL in seconds for record N (default: 1800) | + +Records are numbered starting from 1: `HostName1`, `RecordType1`, `Address1`, `HostName2`, `RecordType2`, `Address2`, etc. + +**Response XML:** + +```xml + + + + + +``` + +--- + +### namecheap.domains.dns.setDefault + +Sets a domain to use Namecheap's default DNS servers. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | + +**Response XML:** + +```xml + + + + + +``` + +--- + +### namecheap.domains.dns.setCustom + +Sets a domain to use custom nameservers (e.g., Cloudflare, Route53). + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | +| `Nameservers` | Yes | Comma-separated list of nameservers (max 12, no spaces) | + +**Example:** `Nameservers=ns1.cloudflare.com,ns2.cloudflare.com` + +**Response XML:** + +```xml + + + + + +``` + +--- + +### namecheap.domains.dns.getEmailForwarding + +Gets email forwarding settings for a domain. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `DomainName` | Yes | Full domain name (e.g., `example.com`) | + +**Response XML:** + +```xml + + + + + + + + +``` + +--- + +### namecheap.domains.dns.setEmailForwarding + +Sets email forwarding for a domain. Replaces all existing forwarding rules. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `DomainName` | Yes | Full domain name (e.g., `example.com`) | +| `MailBoxN` | Yes | Mailbox name for rule N (e.g., `info`, `support`) | +| `ForwardToN` | Yes | Destination email for rule N | + +Rules are numbered starting from 1: `MailBox1`, `ForwardTo1`, `MailBox2`, `ForwardTo2`, etc. +Omitting all MailBox/ForwardTo parameters deletes all forwarding rules. + +**Response XML:** + +```xml + + + + + +``` + +--- + +### namecheap.domains.ns.create + +Creates a child nameserver (glue record) for a domain. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | +| `Nameserver` | Yes | Nameserver hostname to create (e.g., `ns1.example.com`) | +| `IP` | Yes | IP address for the nameserver | + +**Response XML:** + +```xml + + + + + +``` + +--- + +### namecheap.domains.ns.delete + +Deletes a child nameserver. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | +| `Nameserver` | Yes | Nameserver hostname to delete | + +**Response XML:** + +```xml + + + + + +``` + +--- + +### namecheap.domains.ns.getInfo + +Gets information about a child nameserver. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | +| `Nameserver` | Yes | Nameserver hostname to query | + +**Response XML:** + +```xml + + + + + OK + + + + +``` + +--- + +### namecheap.domains.ns.update + +Updates the IP address of a child nameserver. + +**Additional Parameters:** + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `SLD` | Yes | Second-level domain | +| `TLD` | Yes | Top-level domain | +| `Nameserver` | Yes | Nameserver hostname to update | +| `OldIP` | Yes | Current IP address of the nameserver | +| `IP` | Yes | New IP address for the nameserver | + +**Response XML:** + +```xml + + + + + +``` + +## Error Responses + +```xml + + + Domain not found + + +``` + +Common error codes: +- `1011102` — Invalid API key +- `1011148` — IP not whitelisted +- `2019166` — Domain not found +- `2016166` — Domain not using Namecheap DNS + +## Record Types + +| Type | Description | Address Format | +|------|-------------|---------------| +| `A` | IPv4 address | `1.2.3.4` | +| `AAAA` | IPv6 address | `2001:db8::1` | +| `CNAME` | Canonical name | `target.example.com.` | +| `MX` | Mail exchange | `mail.example.com.` (requires MXPref) | +| `MXE` | MX equivalent (IP) | `1.2.3.4` | +| `TXT` | Text record | Any text value | +| `URL` | URL redirect (unmasked) | `http://example.com` | +| `URL301` | Permanent redirect | `http://example.com` | +| `FRAME` | URL redirect (masked) | `http://example.com` | + +## TTL Values + +| Seconds | Human Readable | +|---------|---------------| +| 60 | 1 minute | +| 300 | 5 minutes | +| 1800 | 30 minutes (default) | +| 3600 | 1 hour | +| 14400 | 4 hours | +| 43200 | 12 hours | +| 86400 | 1 day |