mirror of
https://github.com/github/awesome-copilot.git
synced 2026-06-14 11:54:54 +00:00
Rewrite namecheap skill in Python to address PR review
Replace the Bash namecheap.sh with a stdlib-only Python CLI (namecheap.py) that resolves all Copilot PR review feedback: - Cache the public IP once per run instead of per request - Build API requests in-process via urllib so the API key never appears in process argv or shell history - Broaden multi-part TLD detection (co.uk, com.au, etc.) and document the limitation - Allow setup to update existing stored credentials - Stop soliciting the API key via chat; use terminal getpass or env vars - Remove non-portable Bash constructs (inline local, grep -oP) Also normalize domain casing, preserve MX priority 0, accept case-insensitive record types, and handle mixed-case email-forwarding attributes. Update SKILL.md and regenerate the skills README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+23
-24
@@ -17,67 +17,66 @@ 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
|
||||
a. **Show public IP** — run `python3 namecheap.py public-ip` 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
|
||||
c. **Have the user run setup themselves** — ask the user to run `python3 namecheap.py setup` directly **in their own terminal**. The script prompts for the username and reads the API key with a hidden prompt (`getpass`), writes `~/.namecheap-api` with `chmod 600`, and validates the connection. **Never ask the user to paste their API key into the chat, and never log, echo, or display the API key value.** If you cannot run an interactive terminal for the user, instruct them to run `setup` themselves, or to export `NAMECHEAP_API_USER` and `NAMECHEAP_API_KEY` as environment variables in their own shell — rather than collecting the secret via `ask_user`.
|
||||
d. **Confirm** — once the user reports setup succeeded, proceed with DNS operations.
|
||||
|
||||
### DNS Operations
|
||||
|
||||
Use the `namecheap.sh` script (bundled in this skill's directory) for all API interactions:
|
||||
Use the `namecheap.py` script (bundled in this skill's directory) for all API interactions. It requires only Python 3 (standard library only — no `pip install` needed) and works the same on macOS, Linux, and Windows:
|
||||
|
||||
```bash
|
||||
# Show public IP (for setup)
|
||||
bash namecheap.sh public-ip
|
||||
python3 namecheap.py public-ip
|
||||
|
||||
# Run setup flow
|
||||
bash namecheap.sh setup
|
||||
python3 namecheap.py setup
|
||||
|
||||
# List domains
|
||||
bash namecheap.sh domains.getList
|
||||
python3 namecheap.py domains.getList
|
||||
|
||||
# Get nameservers for a domain (shows if using Namecheap DNS or custom)
|
||||
bash namecheap.sh domains.dns.getList --domain example.com
|
||||
python3 namecheap.py domains.dns.getList --domain example.com
|
||||
|
||||
# Get DNS records for a domain
|
||||
bash namecheap.sh domains.dns.getHosts --domain example.com
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py domains.dns.setHosts --domain example.com --hosts records.json
|
||||
|
||||
# Switch to Namecheap default DNS
|
||||
bash namecheap.sh domains.dns.setDefault --domain example.com
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py 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
|
||||
python3 namecheap.py domains.ns.update --domain example.com --nameserver ns1.example.com --old-ip 1.2.3.4 --ip 5.6.7.8
|
||||
```
|
||||
|
||||
## Behavior
|
||||
@@ -87,7 +86,7 @@ bash namecheap.sh domains.ns.update --domain example.com --nameserver ns1.exampl
|
||||
- **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.
|
||||
- **Handle multi-part TLDs.** Domains like `example.co.uk` have SLD=example and TLD=co.uk. The script recognizes a built-in list of common second-level suffixes (e.g. `co.uk`, `com.au`, `co.jp`, `com.br`). This list is best-effort and not a full public-suffix database — if a domain with an unlisted multi-part suffix returns a `2019166` ("Domain not found") error, the SLD/TLD split was likely wrong. In that case, confirm the registered domain with the user and report the limitation.
|
||||
|
||||
## Credential Storage
|
||||
|
||||
@@ -98,7 +97,7 @@ NAMECHEAP_API_USER="username"
|
||||
NAMECHEAP_API_KEY="api-key-here"
|
||||
```
|
||||
|
||||
This file must have `600` permissions (owner read/write only).
|
||||
This file must have `600` permissions (owner read/write only). Alternatively, the script reads credentials from the `NAMECHEAP_API_USER` and `NAMECHEAP_API_KEY` environment variables, which take precedence over the file when both are set.
|
||||
|
||||
## Supported Record Types
|
||||
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Namecheap API CLI wrapper for DNS management.
|
||||
|
||||
Uses only the Python standard library (no third-party dependencies). Credentials
|
||||
are read from ``~/.namecheap-api`` (or env vars) and are never passed on the
|
||||
command line, so they cannot leak via ``ps``/shell history.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
API_URL = "https://api.namecheap.com/xml.response"
|
||||
CONFIG_FILE = os.path.join(os.path.expanduser("~"), ".namecheap-api")
|
||||
|
||||
# Known multi-part (second-level) public suffixes. Best-effort list, not a full
|
||||
# public-suffix database. For unlisted suffixes the domain is split on the last
|
||||
# dot, which is correct for single-label TLDs (.com, .io, .dev, ...).
|
||||
MULTI_PART_SUFFIXES = {
|
||||
"co.uk", "org.uk", "me.uk", "ac.uk", "gov.uk", "net.uk", "ltd.uk", "plc.uk",
|
||||
"com.au", "net.au", "org.au", "id.au",
|
||||
"co.nz", "net.nz", "org.nz",
|
||||
"co.za", "org.za",
|
||||
"co.jp", "ne.jp", "or.jp", "ac.jp", "go.jp",
|
||||
"co.kr", "or.kr", "ne.kr",
|
||||
"com.br", "net.br", "org.br",
|
||||
"com.cn", "net.cn", "org.cn",
|
||||
"co.in", "net.in", "org.in",
|
||||
"com.mx", "org.mx",
|
||||
"com.sg", "edu.sg",
|
||||
"com.tr",
|
||||
"co.il", "org.il",
|
||||
}
|
||||
|
||||
# Cached public IP for the lifetime of the process.
|
||||
_public_ip = None
|
||||
|
||||
|
||||
# --- Output helpers -------------------------------------------------------
|
||||
|
||||
_USE_COLOR = sys.stdout.isatty()
|
||||
|
||||
|
||||
def _c(code, text):
|
||||
return f"\033[{code}m{text}\033[0m" if _USE_COLOR else text
|
||||
|
||||
|
||||
def err(msg):
|
||||
print(_c("0;31", "Error:") + " " + msg, file=sys.stderr)
|
||||
|
||||
|
||||
def success(msg):
|
||||
print(_c("0;32", "\u2713") + " " + msg)
|
||||
|
||||
|
||||
def info(msg):
|
||||
print(_c("0;36", "\u2139") + " " + msg)
|
||||
|
||||
|
||||
def warn(msg):
|
||||
print(_c("1;33", "\u26a0") + " " + msg)
|
||||
|
||||
|
||||
class NamecheapError(Exception):
|
||||
"""Raised when the API returns a Status="ERROR" response."""
|
||||
|
||||
|
||||
# --- Configuration --------------------------------------------------------
|
||||
|
||||
def load_config():
|
||||
"""Return (api_user, api_key), preferring env vars then the config file."""
|
||||
api_user = os.environ.get("NAMECHEAP_API_USER")
|
||||
api_key = os.environ.get("NAMECHEAP_API_KEY")
|
||||
if api_user and api_key:
|
||||
return api_user, api_key
|
||||
|
||||
if os.path.isfile(CONFIG_FILE):
|
||||
with open(CONFIG_FILE, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
# File uses shell-style KEY="value" lines for backward compatibility.
|
||||
pattern = re.compile(r'^\s*([A-Z_]+)\s*=\s*"?([^"\n]*)"?\s*$', re.MULTILINE)
|
||||
values = {m.group(1): m.group(2) for m in pattern.finditer(content)}
|
||||
api_user = api_user or values.get("NAMECHEAP_API_USER")
|
||||
api_key = api_key or values.get("NAMECHEAP_API_KEY")
|
||||
|
||||
return api_user, api_key
|
||||
|
||||
|
||||
def check_credentials():
|
||||
api_user, api_key = load_config()
|
||||
if not api_user or not api_key:
|
||||
err("Namecheap API credentials not configured.")
|
||||
print()
|
||||
print("Run 'python3 namecheap.py setup' to configure your credentials.")
|
||||
print()
|
||||
print("You need:")
|
||||
print(" 1. Your Namecheap username")
|
||||
print(" 2. An API key from: https://ap.www.namecheap.com/settings/tools/apiaccess/")
|
||||
print(" 3. Your public IP whitelisted in the API settings")
|
||||
sys.exit(1)
|
||||
return api_user, api_key
|
||||
|
||||
|
||||
def save_config(api_user, api_key):
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as fh:
|
||||
fh.write(f'NAMECHEAP_API_USER="{api_user}"\n')
|
||||
fh.write(f'NAMECHEAP_API_KEY="{api_key}"\n')
|
||||
os.chmod(CONFIG_FILE, stat.S_IRUSR | stat.S_IWUSR) # 600
|
||||
|
||||
|
||||
# --- Networking -----------------------------------------------------------
|
||||
|
||||
def _http_get(url, timeout=15):
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "namecheap-skill/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310 (https only)
|
||||
return resp.read().decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def get_public_ip():
|
||||
"""Resolve the public IP once and cache it for subsequent calls."""
|
||||
global _public_ip
|
||||
if _public_ip:
|
||||
return _public_ip
|
||||
for url in ("https://api.ipify.org", "https://ifconfig.me"):
|
||||
try:
|
||||
ip = _http_get(url, timeout=10).strip()
|
||||
if ip:
|
||||
_public_ip = ip
|
||||
return ip
|
||||
except Exception:
|
||||
continue
|
||||
_public_ip = "unknown"
|
||||
return _public_ip
|
||||
|
||||
|
||||
def _strip_namespaces(root):
|
||||
for elem in root.iter():
|
||||
if isinstance(elem.tag, str) and "}" in elem.tag:
|
||||
elem.tag = elem.tag.split("}", 1)[1]
|
||||
return root
|
||||
|
||||
|
||||
def _check_error(root):
|
||||
if (root.attrib.get("Status") or "").upper() == "ERROR":
|
||||
messages = []
|
||||
for e in root.iter("Err"):
|
||||
code = e.attrib.get("Number") or e.attrib.get("Code") or ""
|
||||
text = (e.text or "").strip()
|
||||
messages.append(f"[{code}] {text}" if code else text)
|
||||
raise NamecheapError("; ".join(m for m in messages if m) or "Unknown API error")
|
||||
|
||||
|
||||
def api_request(command, params=None):
|
||||
"""Issue a Namecheap API GET request and return the parsed (ns-stripped) root.
|
||||
|
||||
The API key is encoded into the request URL inside this process; it is never
|
||||
passed as a command-line argument, so it cannot leak via ``ps`` or shell
|
||||
history. Values are URL-encoded by ``urllib``.
|
||||
"""
|
||||
api_user, api_key = check_credentials()
|
||||
query = {
|
||||
"ApiUser": api_user,
|
||||
"ApiKey": api_key,
|
||||
"UserName": api_user,
|
||||
"Command": f"namecheap.{command}",
|
||||
"ClientIp": get_public_ip(),
|
||||
}
|
||||
for key, value in (params or {}).items():
|
||||
if value is not None and value != "":
|
||||
query[key] = value
|
||||
|
||||
url = f"{API_URL}?{urllib.parse.urlencode(query)}"
|
||||
body = _http_get(url, timeout=30)
|
||||
root = _strip_namespaces(ET.fromstring(body))
|
||||
_check_error(root)
|
||||
return root
|
||||
|
||||
|
||||
def _attr(root, tag, name, default=""):
|
||||
for elem in root.iter(tag):
|
||||
return elem.attrib.get(name, default)
|
||||
return default
|
||||
|
||||
|
||||
# --- Domain parsing -------------------------------------------------------
|
||||
|
||||
def parse_domain(domain):
|
||||
"""Split a registered domain into (SLD, TLD), handling multi-part TLDs."""
|
||||
domain = domain.strip().rstrip(".").lower()
|
||||
labels = domain.split(".")
|
||||
if len(labels) >= 3 and ".".join(labels[-2:]) in MULTI_PART_SUFFIXES:
|
||||
tld = ".".join(labels[-2:])
|
||||
sld = ".".join(labels[:-2])
|
||||
elif len(labels) >= 2:
|
||||
tld = labels[-1]
|
||||
sld = ".".join(labels[:-1])
|
||||
else:
|
||||
tld = ""
|
||||
sld = domain
|
||||
return sld, tld
|
||||
|
||||
|
||||
# --- Commands -------------------------------------------------------------
|
||||
|
||||
def cmd_public_ip(_args):
|
||||
print(get_public_ip())
|
||||
|
||||
|
||||
def cmd_setup(_args):
|
||||
print("=== Namecheap API Setup ===\n")
|
||||
public_ip = get_public_ip()
|
||||
info("Your public IP address is: " + _c("0;36", public_ip))
|
||||
print()
|
||||
print("Make sure this IP is whitelisted at:")
|
||||
print(" https://ap.www.namecheap.com/settings/tools/apiaccess/")
|
||||
print()
|
||||
|
||||
existing_user, existing_key = load_config()
|
||||
if existing_user and existing_key:
|
||||
info(f"Existing configuration found for user: {existing_user}")
|
||||
print("\nTesting API connection...")
|
||||
try:
|
||||
api_request("domains.getList", {"PageSize": "1"})
|
||||
success("API connection successful!")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
err(f"API connection failed: {exc}")
|
||||
print()
|
||||
answer = input("Update stored credentials? [y/N]: ").strip().lower()
|
||||
if answer not in ("y", "yes"):
|
||||
info("Keeping existing credentials.")
|
||||
return
|
||||
print()
|
||||
|
||||
print("Enter your Namecheap credentials:\n")
|
||||
api_user = input(" API Username: ").strip()
|
||||
api_key = getpass.getpass(" API Key (hidden): ").strip()
|
||||
print()
|
||||
|
||||
if not api_user or not api_key:
|
||||
err("Both username and API key are required.")
|
||||
sys.exit(1)
|
||||
|
||||
save_config(api_user, api_key)
|
||||
success(f"Credentials saved to {CONFIG_FILE}")
|
||||
print("\nTesting API connection...")
|
||||
try:
|
||||
# Use the just-entered credentials directly for the validation call.
|
||||
os.environ["NAMECHEAP_API_USER"] = api_user
|
||||
os.environ["NAMECHEAP_API_KEY"] = api_key
|
||||
api_request("domains.getList", {"PageSize": "1"})
|
||||
success("API connection successful!")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
warn("API connection failed. Please verify:")
|
||||
print(" 1. API access is enabled (ON) at the Namecheap settings page")
|
||||
print(f" 2. IP address {public_ip} is whitelisted")
|
||||
print(" 3. Your API key is correct")
|
||||
print(f" (details: {exc})")
|
||||
|
||||
|
||||
def cmd_domains_list(args):
|
||||
params = {"ListType": args.type, "Page": str(args.page), "PageSize": str(args.page_size)}
|
||||
if args.search:
|
||||
params["SearchTerm"] = args.search
|
||||
info("Fetching domain list...")
|
||||
root = api_request("domains.getList", params)
|
||||
|
||||
print()
|
||||
print(f"{'DOMAIN':<30} {'EXPIRES':<12} {'LOCKED':<12} {'AUTO-RENEW':<10}")
|
||||
print(f"{'------':<30} {'-------':<12} {'------':<12} {'----------':<10}")
|
||||
for d in root.iter("Domain"):
|
||||
print("{:<30} {:<12} {:<12} {:<10}".format(
|
||||
d.attrib.get("Name", ""),
|
||||
d.attrib.get("Expires", ""),
|
||||
d.attrib.get("IsLocked", ""),
|
||||
d.attrib.get("AutoRenew", ""),
|
||||
))
|
||||
print()
|
||||
|
||||
|
||||
def _print_hosts(root):
|
||||
print()
|
||||
print(f"{'HOST':<20} {'TYPE':<8} {'ADDRESS':<40} {'TTL':<8} {'MXPREF':<6}")
|
||||
print(f"{'----':<20} {'----':<8} {'-------':<40} {'---':<8} {'------':<6}")
|
||||
for h in root.iter("host"):
|
||||
print("{:<20} {:<8} {:<40} {:<8} {:<6}".format(
|
||||
h.attrib.get("Name", ""),
|
||||
h.attrib.get("Type", ""),
|
||||
h.attrib.get("Address", ""),
|
||||
h.attrib.get("TTL", "1800"),
|
||||
h.attrib.get("MXPref", "-"),
|
||||
))
|
||||
print()
|
||||
|
||||
|
||||
def cmd_dns_get_hosts(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Fetching DNS records for {args.domain} (SLD={sld}, TLD={tld})...")
|
||||
root = api_request("domains.dns.getHosts", {"SLD": sld, "TLD": tld})
|
||||
_print_hosts(root)
|
||||
|
||||
|
||||
def _existing_hosts(root):
|
||||
"""Return existing host records as a list of dicts."""
|
||||
records = []
|
||||
for h in root.iter("host"):
|
||||
records.append({
|
||||
"name": h.attrib.get("Name", ""),
|
||||
"type": h.attrib.get("Type", ""),
|
||||
"address": h.attrib.get("Address", ""),
|
||||
"ttl": h.attrib.get("TTL", "1800"),
|
||||
"mxpref": h.attrib.get("MXPref", ""),
|
||||
})
|
||||
return records
|
||||
|
||||
|
||||
def _hosts_to_params(sld, tld, records):
|
||||
params = {"SLD": sld, "TLD": tld}
|
||||
i = 1
|
||||
for r in records:
|
||||
if not (r["name"] and r["type"] and r["address"]):
|
||||
continue
|
||||
params[f"HostName{i}"] = r["name"]
|
||||
params[f"RecordType{i}"] = r["type"]
|
||||
params[f"Address{i}"] = r["address"]
|
||||
params[f"TTL{i}"] = r.get("ttl") or "1800"
|
||||
mxpref = r.get("mxpref")
|
||||
if mxpref not in (None, ""):
|
||||
# MX priority 0 is valid, so always send MXPref for MX records;
|
||||
# for other record types only forward a non-zero value.
|
||||
if r["type"].upper() == "MX" or mxpref != "0":
|
||||
params[f"MXPref{i}"] = mxpref
|
||||
i += 1
|
||||
return params, i - 1
|
||||
|
||||
|
||||
def cmd_dns_set_hosts(args):
|
||||
if not os.path.isfile(args.hosts):
|
||||
err(f"Hosts file not found: {args.hosts}")
|
||||
sys.exit(1)
|
||||
with open(args.hosts, "r", encoding="utf-8") as fh:
|
||||
raw = json.load(fh)
|
||||
|
||||
records = []
|
||||
for r in raw:
|
||||
records.append({
|
||||
"name": r.get("HostName", ""),
|
||||
"type": r.get("RecordType", ""),
|
||||
"address": r.get("Address", ""),
|
||||
"ttl": str(r.get("TTL", "") or ""),
|
||||
"mxpref": str(r.get("MXPref", "") or ""),
|
||||
})
|
||||
|
||||
sld, tld = parse_domain(args.domain)
|
||||
params, count = _hosts_to_params(sld, tld, records)
|
||||
if count == 0:
|
||||
err(f"No valid host records found in {args.hosts}")
|
||||
sys.exit(1)
|
||||
|
||||
info(f"Setting {count} DNS records for {args.domain}...")
|
||||
root = api_request("domains.dns.setHosts", params)
|
||||
if _attr(root, "DomainDNSSetHostsResult", "IsSuccess").lower() == "true":
|
||||
success(f"DNS records updated successfully for {args.domain}!")
|
||||
else:
|
||||
err("Failed to update DNS records.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_dns_add_host(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Fetching existing DNS records for {args.domain}...")
|
||||
root = api_request("domains.dns.getHosts", {"SLD": sld, "TLD": tld})
|
||||
records = _existing_hosts(root)
|
||||
records.append({
|
||||
"name": args.name,
|
||||
"type": args.type.upper(),
|
||||
"address": args.address,
|
||||
"ttl": args.ttl,
|
||||
"mxpref": args.mxpref or "",
|
||||
})
|
||||
params, _ = _hosts_to_params(sld, tld, records)
|
||||
|
||||
info(f"Adding {args.type.upper()} record: {args.name} -> {args.address}")
|
||||
result = api_request("domains.dns.setHosts", params)
|
||||
if _attr(result, "DomainDNSSetHostsResult", "IsSuccess").lower() == "true":
|
||||
success(f"DNS record added: {args.name} {args.type} {args.address}")
|
||||
else:
|
||||
err("Failed to add DNS record.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_dns_remove_host(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Fetching existing DNS records for {args.domain}...")
|
||||
root = api_request("domains.dns.getHosts", {"SLD": sld, "TLD": tld})
|
||||
|
||||
kept = []
|
||||
removed = False
|
||||
for r in _existing_hosts(root):
|
||||
if (not removed and r["name"] == args.name
|
||||
and r["type"].upper() == args.type.upper()
|
||||
and (not args.address or r["address"] == args.address)):
|
||||
removed = True
|
||||
info(f"Removing record: {r['name']} {r['type']} {r['address']}")
|
||||
continue
|
||||
kept.append(r)
|
||||
|
||||
if not removed:
|
||||
err("No matching record found to remove.")
|
||||
sys.exit(1)
|
||||
|
||||
params, count = _hosts_to_params(sld, tld, kept)
|
||||
if count == 0:
|
||||
err("Cannot remove the last DNS record. Namecheap requires at least one record.")
|
||||
sys.exit(1)
|
||||
|
||||
info(f"Updating DNS records for {args.domain}...")
|
||||
result = api_request("domains.dns.setHosts", params)
|
||||
if _attr(result, "DomainDNSSetHostsResult", "IsSuccess").lower() == "true":
|
||||
success("DNS record removed successfully!")
|
||||
else:
|
||||
err("Failed to remove DNS record.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_dns_get_list(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Fetching nameservers for {args.domain}...")
|
||||
root = api_request("domains.dns.getList", {"SLD": sld, "TLD": tld})
|
||||
|
||||
using = _attr(root, "DomainDNSGetListResult", "IsUsingOurDNS", "unknown")
|
||||
print()
|
||||
info(f"Using Namecheap DNS: {using}")
|
||||
print("\nNameservers:")
|
||||
for ns in root.iter("Nameserver"):
|
||||
if ns.text:
|
||||
print(f" - {ns.text.strip()}")
|
||||
print()
|
||||
|
||||
|
||||
def cmd_dns_set_default(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Setting {args.domain} to use Namecheap default DNS...")
|
||||
root = api_request("domains.dns.setDefault", {"SLD": sld, "TLD": tld})
|
||||
if _attr(root, "DomainDNSSetDefaultResult", "Updated").lower() == "true":
|
||||
success(f"Domain {args.domain} now uses Namecheap default DNS!")
|
||||
else:
|
||||
err("Failed to set default DNS.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_dns_set_custom(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Setting {args.domain} to use custom nameservers: {args.nameservers}")
|
||||
root = api_request(
|
||||
"domains.dns.setCustom",
|
||||
{"SLD": sld, "TLD": tld, "Nameservers": args.nameservers},
|
||||
)
|
||||
if _attr(root, "DomainDNSSetCustomResult", "Updated").lower() == "true":
|
||||
success(f"Domain {args.domain} now uses custom nameservers!")
|
||||
else:
|
||||
err("Failed to set custom nameservers.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_dns_get_email_forwarding(args):
|
||||
info(f"Fetching email forwarding for {args.domain}...")
|
||||
root = api_request("domains.dns.getEmailForwarding", {"DomainName": args.domain})
|
||||
|
||||
print()
|
||||
print(f"{'MAILBOX':<20} {'FORWARDS TO':<40}")
|
||||
print(f"{'-------':<20} {'-----------':<40}")
|
||||
for fwd in root.iter("Forward"):
|
||||
mailbox = (fwd.attrib.get("mailbox") or fwd.attrib.get("MailBox")
|
||||
or fwd.attrib.get("Mailbox") or "")
|
||||
forward_to = (fwd.attrib.get("ForwardTo") or fwd.attrib.get("forwardto")
|
||||
or (fwd.text or "").strip())
|
||||
print(f"{mailbox + '@' + args.domain:<20} {forward_to:<40}")
|
||||
print()
|
||||
|
||||
|
||||
def cmd_dns_set_email_forwarding(args):
|
||||
params = {"DomainName": args.domain}
|
||||
|
||||
if args.mailbox and args.forward_to:
|
||||
params["MailBox1"] = args.mailbox
|
||||
params["ForwardTo1"] = args.forward_to
|
||||
elif args.forwards:
|
||||
if not os.path.isfile(args.forwards):
|
||||
err(f"Forwards file not found: {args.forwards}")
|
||||
sys.exit(1)
|
||||
with open(args.forwards, "r", encoding="utf-8") as fh:
|
||||
rules = json.load(fh)
|
||||
i = 1
|
||||
for rule in rules:
|
||||
mailbox = rule.get("MailBox") or rule.get("mailbox") or ""
|
||||
forward_to = rule.get("ForwardTo") or rule.get("forwardto") or ""
|
||||
if mailbox and forward_to:
|
||||
params[f"MailBox{i}"] = mailbox
|
||||
params[f"ForwardTo{i}"] = forward_to
|
||||
i += 1
|
||||
else:
|
||||
err("Provide either --mailbox/--forward-to or --forwards <file.json>")
|
||||
sys.exit(1)
|
||||
|
||||
info(f"Setting email forwarding for {args.domain}...")
|
||||
root = api_request("domains.dns.setEmailForwarding", params)
|
||||
if _attr(root, "DomainDNSSetEmailForwardingResult", "IsSuccess").lower() == "true":
|
||||
success(f"Email forwarding updated for {args.domain}!")
|
||||
else:
|
||||
err("Failed to set email forwarding.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_ns_create(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Creating nameserver {args.nameserver} -> {args.ip}...")
|
||||
root = api_request(
|
||||
"domains.ns.create",
|
||||
{"SLD": sld, "TLD": tld, "Nameserver": args.nameserver, "IP": args.ip},
|
||||
)
|
||||
if _attr(root, "DomainNSCreateResult", "IsSuccess").lower() == "true":
|
||||
success(f"Nameserver {args.nameserver} created!")
|
||||
else:
|
||||
err("Failed to create nameserver.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_ns_delete(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Deleting nameserver {args.nameserver}...")
|
||||
root = api_request(
|
||||
"domains.ns.delete",
|
||||
{"SLD": sld, "TLD": tld, "Nameserver": args.nameserver},
|
||||
)
|
||||
if _attr(root, "DomainNSDeleteResult", "IsSuccess").lower() == "true":
|
||||
success(f"Nameserver {args.nameserver} deleted!")
|
||||
else:
|
||||
err("Failed to delete nameserver.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def cmd_ns_get_info(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Fetching info for nameserver {args.nameserver}...")
|
||||
root = api_request(
|
||||
"domains.ns.getInfo",
|
||||
{"SLD": sld, "TLD": tld, "Nameserver": args.nameserver},
|
||||
)
|
||||
ns_ip = _attr(root, "DomainNSInfoResult", "IP", "unknown")
|
||||
print()
|
||||
print(f"Nameserver: {args.nameserver}")
|
||||
print(f"IP Address: {ns_ip}")
|
||||
statuses = [s.text.strip() for s in root.iter("Status") if s.text and s.text.strip()]
|
||||
if statuses:
|
||||
print(f"Status: {', '.join(statuses)}")
|
||||
print()
|
||||
|
||||
|
||||
def cmd_ns_update(args):
|
||||
sld, tld = parse_domain(args.domain)
|
||||
info(f"Updating nameserver {args.nameserver}: {args.old_ip} -> {args.ip}...")
|
||||
root = api_request(
|
||||
"domains.ns.update",
|
||||
{"SLD": sld, "TLD": tld, "Nameserver": args.nameserver,
|
||||
"OldIP": args.old_ip, "IP": args.ip},
|
||||
)
|
||||
if _attr(root, "DomainNSUpdateResult", "IsSuccess").lower() == "true":
|
||||
success(f"Nameserver {args.nameserver} updated to {args.ip}!")
|
||||
else:
|
||||
err("Failed to update nameserver.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# --- Argument parsing -----------------------------------------------------
|
||||
|
||||
def build_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="namecheap.py",
|
||||
description="Namecheap DNS Management CLI",
|
||||
)
|
||||
sub = parser.add_subparsers(dest="command", metavar="<command>")
|
||||
|
||||
sub.add_parser("setup", help="Configure API credentials and test connection").set_defaults(func=cmd_setup)
|
||||
sub.add_parser("public-ip", help="Show your public IP address").set_defaults(func=cmd_public_ip)
|
||||
|
||||
p = sub.add_parser("domains.getList", help="List your Namecheap domains")
|
||||
p.add_argument("--type", default="ALL")
|
||||
p.add_argument("--search", default="")
|
||||
p.add_argument("--page", type=int, default=1)
|
||||
p.add_argument("--page-size", type=int, default=20)
|
||||
p.set_defaults(func=cmd_domains_list)
|
||||
|
||||
p = sub.add_parser("domains.dns.getList", help="Get nameservers for a domain")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.set_defaults(func=cmd_dns_get_list)
|
||||
|
||||
p = sub.add_parser("domains.dns.getHosts", help="Get DNS records for a domain")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.set_defaults(func=cmd_dns_get_hosts)
|
||||
|
||||
p = sub.add_parser("domains.dns.setHosts", help="Set all DNS records (from JSON file)")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--hosts", required=True)
|
||||
p.set_defaults(func=cmd_dns_set_hosts)
|
||||
|
||||
p = sub.add_parser("domains.dns.setDefault", help="Use Namecheap default DNS")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.set_defaults(func=cmd_dns_set_default)
|
||||
|
||||
p = sub.add_parser("domains.dns.setCustom", help="Use custom nameservers")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--nameservers", required=True)
|
||||
p.set_defaults(func=cmd_dns_set_custom)
|
||||
|
||||
p = sub.add_parser("domains.dns.getEmailForwarding", help="Get email forwarding rules")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.set_defaults(func=cmd_dns_get_email_forwarding)
|
||||
|
||||
p = sub.add_parser("domains.dns.setEmailForwarding", help="Set email forwarding rules")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--mailbox", default="")
|
||||
p.add_argument("--forward-to", default="")
|
||||
p.add_argument("--forwards", default="")
|
||||
p.set_defaults(func=cmd_dns_set_email_forwarding)
|
||||
|
||||
p = sub.add_parser("domains.ns.create", help="Create a child nameserver (glue record)")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--nameserver", required=True)
|
||||
p.add_argument("--ip", required=True)
|
||||
p.set_defaults(func=cmd_ns_create)
|
||||
|
||||
p = sub.add_parser("domains.ns.delete", help="Delete a child nameserver")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--nameserver", required=True)
|
||||
p.set_defaults(func=cmd_ns_delete)
|
||||
|
||||
p = sub.add_parser("domains.ns.getInfo", help="Get nameserver info")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--nameserver", required=True)
|
||||
p.set_defaults(func=cmd_ns_get_info)
|
||||
|
||||
p = sub.add_parser("domains.ns.update", help="Update nameserver IP")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--nameserver", required=True)
|
||||
p.add_argument("--old-ip", required=True)
|
||||
p.add_argument("--ip", required=True)
|
||||
p.set_defaults(func=cmd_ns_update)
|
||||
|
||||
p = sub.add_parser("dns.addHost", help="Add a single DNS record (preserves existing)")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--type", required=True)
|
||||
p.add_argument("--name", required=True)
|
||||
p.add_argument("--address", required=True)
|
||||
p.add_argument("--ttl", default="1800")
|
||||
p.add_argument("--mxpref", default="")
|
||||
p.set_defaults(func=cmd_dns_add_host)
|
||||
|
||||
p = sub.add_parser("dns.removeHost", help="Remove a single DNS record")
|
||||
p.add_argument("--domain", required=True)
|
||||
p.add_argument("--type", required=True)
|
||||
p.add_argument("--name", required=True)
|
||||
p.add_argument("--address", default="")
|
||||
p.set_defaults(func=cmd_dns_remove_host)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
if not getattr(args, "command", None):
|
||||
parser.print_help()
|
||||
return 1
|
||||
try:
|
||||
args.func(args)
|
||||
except NamecheapError as exc:
|
||||
err(f"API returned error: {exc}")
|
||||
return 1
|
||||
except urllib.error.URLError as exc:
|
||||
err(f"Network error: {exc}")
|
||||
return 1
|
||||
except (OSError, ET.ParseError, json.JSONDecodeError) as exc:
|
||||
err(str(exc))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,919 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Namecheap API CLI wrapper
|
||||
# Usage: ./namecheap.sh <command> [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 '(?<=<Err Code=")[^"]*"[^>]*>\K[^<]+' 2>/dev/null || echo "$response" | sed -n 's/.*<Err[^>]*>\(.*\)<\/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 '<host[^/]*/>' | 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 '<Domain[^/]*/>' | 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 '<host[^/]*/>')
|
||||
|
||||
# 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 '<host[^/]*/>')
|
||||
|
||||
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 '<Nameserver>\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 '<Forward[^/]*/>' | 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 <file.json>"
|
||||
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 '<Status>\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 <command> [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> Domain name (e.g., example.com)"
|
||||
echo " --type <type> Record type (A, AAAA, CNAME, MX, TXT, etc.)"
|
||||
echo " --name <hostname> Host name (e.g., @, www, mail)"
|
||||
echo " --address <value> Record value (IP or target)"
|
||||
echo " --ttl <seconds> TTL in seconds (default: 1800)"
|
||||
echo " --mxpref <priority> MX preference (for MX records)"
|
||||
echo " --hosts <file.json> JSON file with host records"
|
||||
echo " --nameservers <ns,...> Comma-separated nameservers"
|
||||
echo " --nameserver <ns> Nameserver hostname"
|
||||
echo " --ip <address> IP address for nameserver"
|
||||
echo " --old-ip <address> Current IP (for ns.update)"
|
||||
echo " --mailbox <name> Email mailbox name"
|
||||
echo " --forward-to <email> Forward destination email"
|
||||
echo " --forwards <file.json> JSON file with forwarding rules"
|
||||
echo " --search <term> Search term for domain list"
|
||||
echo " --page <n> Page number for domain list"
|
||||
echo " --page-size <n> 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 "$@"
|
||||
Reference in New Issue
Block a user