diff --git a/.env.example b/.env.example index edcb12c..b6e39b7 100644 --- a/.env.example +++ b/.env.example @@ -7,11 +7,12 @@ NAMECHEAP_SANDBOX=true # REST API Server Auth API_SERVER_KEY=your_api_server_key -# Registrant Info +# Registrant Info (default for domain registration) REGISTRANT_ORGANIZATION= REGISTRANT_FIRST_NAME= REGISTRANT_LAST_NAME= REGISTRANT_ADDRESS1= +REGISTRANT_ADDRESS2= REGISTRANT_CITY= REGISTRANT_STATE_PROVINCE= REGISTRANT_POSTAL_CODE= diff --git a/CLAUDE.md b/CLAUDE.md index 105ddc1..8a48c2c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,7 @@ Environment variables in `.env` (copy from `.env.example`): - `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY`, `NAMECHEAP_USERNAME`, `NAMECHEAP_CLIENT_IP` - Namecheap API credentials - `NAMECHEAP_SANDBOX` - Use sandbox API (true/false) - `API_SERVER_KEY` - REST API authentication key (stored in Vault: `secret/namecheap/api-server`) +- `REGISTRANT_*` - Default registrant info for domain registration (ORGANIZATION, FIRST_NAME, LAST_NAME, ADDRESS1, ADDRESS2, CITY, STATE_PROVINCE, POSTAL_CODE, COUNTRY, PHONE, EMAIL) ### systemd Services diff --git a/README.md b/README.md index f63577a..817695a 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,12 @@ NAMECHEAP_SANDBOX=false # REST API 서버 인증 API_SERVER_KEY=your_api_server_key # Vault: secret/namecheap/api-server -# 등록자 정보 +# 등록자 정보 (도메인 등록 시 기본값) REGISTRANT_ORGANIZATION=회사명 REGISTRANT_FIRST_NAME=이름 REGISTRANT_LAST_NAME=성 -REGISTRANT_ADDRESS1=주소 +REGISTRANT_ADDRESS1=주소1 +REGISTRANT_ADDRESS2=주소2 (선택) REGISTRANT_CITY=도시 REGISTRANT_STATE_PROVINCE=시/도 REGISTRANT_POSTAL_CODE=우편번호 @@ -286,6 +287,7 @@ journalctl --user -u namecheap-api.service -f |--------|----------|------| | GET | `/domains` | 도메인 목록 | | POST | `/domains/check` | 도메인 가용성 확인 | +| POST | `/domains/register` | 도메인 등록 (과금됨) | | GET | `/domains/{domain}` | 도메인 상세 정보 | | POST | `/domains/{domain}/renew` | 도메인 갱신 | | GET | `/domains/{domain}/contacts` | 등록자 정보 조회 | diff --git a/api_server.py b/api_server.py index 456c4c8..1d46a7b 100644 --- a/api_server.py +++ b/api_server.py @@ -190,41 +190,57 @@ def get_dns_records(domain: str): @app.post("/dns/{domain}/glue") def create_glue_record(domain: str, req: GlueRecordRequest): """Create a glue record (child nameserver) for a domain""" + from namecheap import NamecheapError parts = domain.rsplit(".", 1) if len(parts) != 2: raise HTTPException(status_code=400, detail="Invalid domain format") sld, tld = parts - return api.ns_create(sld, tld, req.nameserver, req.ip) + try: + return api.ns_create(sld, tld, req.nameserver, req.ip) + except NamecheapError as e: + raise HTTPException(status_code=400, detail=str(e)) @app.get("/dns/{domain}/glue/{nameserver}") def get_glue_record(domain: str, nameserver: str): """Get info about a glue record (child nameserver)""" + from namecheap import NamecheapError parts = domain.rsplit(".", 1) if len(parts) != 2: raise HTTPException(status_code=400, detail="Invalid domain format") sld, tld = parts - return api.ns_get_info(sld, tld, nameserver) + try: + return api.ns_get_info(sld, tld, nameserver) + except NamecheapError as e: + raise HTTPException(status_code=400, detail=str(e)) @app.put("/dns/{domain}/glue") def update_glue_record(domain: str, req: GlueRecordUpdateRequest): """Update a glue record (child nameserver) IP address""" + from namecheap import NamecheapError parts = domain.rsplit(".", 1) if len(parts) != 2: raise HTTPException(status_code=400, detail="Invalid domain format") sld, tld = parts - return api.ns_update(sld, tld, req.nameserver, req.old_ip, req.ip) + try: + return api.ns_update(sld, tld, req.nameserver, req.old_ip, req.ip) + except NamecheapError as e: + raise HTTPException(status_code=400, detail=str(e)) @app.delete("/dns/{domain}/glue/{nameserver}") def delete_glue_record(domain: str, nameserver: str): """Delete a glue record (child nameserver)""" + from namecheap import NamecheapError parts = domain.rsplit(".", 1) if len(parts) != 2: raise HTTPException(status_code=400, detail="Invalid domain format") sld, tld = parts - return api.ns_delete(sld, tld, nameserver) + try: + return api.ns_delete(sld, tld, nameserver) + except NamecheapError as e: + raise HTTPException(status_code=400, detail=str(e)) # ===== Account Endpoints ===== @@ -341,12 +357,110 @@ def get_openai_schema(): "tld": {"type": "string", "description": "TLD like com, net, io"} }, "required": ["tld"] + }, + }, + { + "name": "register_domain", + "description": "Register a new domain. WARNING: This will charge your account.", + "parameters": { + "type": "object", + "properties": { + "domain": {"type": "string", "description": "Domain to register (e.g., example.com)"}, + "years": {"type": "integer", "default": 1, "description": "Registration period (1-10 years)"} + }, + "required": ["domain"] } } ] } + + +# ===== Domain Registration ===== + +class DomainRegisterRequest(BaseModel): + domain: str + years: int = 1 + add_whois_guard: bool = True + # Registrant info (optional - uses default if not provided) + first_name: Optional[str] = None + last_name: Optional[str] = None + organization: Optional[str] = None + address1: Optional[str] = None + address2: Optional[str] = None + city: Optional[str] = None + state_province: Optional[str] = None + postal_code: Optional[str] = None + country: Optional[str] = None + phone: Optional[str] = None + email: Optional[str] = None + + +# Default registrant info from environment +def get_default_registrant() -> RegistrantInfo: + return RegistrantInfo( + first_name=os.getenv("REGISTRANT_FIRST_NAME", ""), + last_name=os.getenv("REGISTRANT_LAST_NAME", ""), + organization=os.getenv("REGISTRANT_ORGANIZATION", ""), + address1=os.getenv("REGISTRANT_ADDRESS1", ""), + address2=os.getenv("REGISTRANT_ADDRESS2", ""), + city=os.getenv("REGISTRANT_CITY", ""), + state_province=os.getenv("REGISTRANT_STATE_PROVINCE", ""), + postal_code=os.getenv("REGISTRANT_POSTAL_CODE", ""), + country=os.getenv("REGISTRANT_COUNTRY", "JP"), + phone=os.getenv("REGISTRANT_PHONE", ""), + email=os.getenv("REGISTRANT_EMAIL", ""), + ) + + +@app.post("/domains/register") +def register_domain(req: DomainRegisterRequest): + """ + Register a new domain. WARNING: This will charge your account. + + If registrant info is not provided, uses default from environment. + """ + from namecheap import NamecheapError + + # Use provided info or fall back to defaults + default = get_default_registrant() + registrant = RegistrantInfo( + first_name=req.first_name or default.first_name, + last_name=req.last_name or default.last_name, + organization=req.organization or default.organization, + address1=req.address1 or default.address1, + address2=req.address2 or default.address2, + city=req.city or default.city, + state_province=req.state_province or default.state_province, + postal_code=req.postal_code or default.postal_code, + country=req.country or default.country, + phone=req.phone or default.phone, + email=req.email or default.email, + ) + + # Validate required fields + required = ["first_name", "last_name", "address1", "city", "state_province", + "postal_code", "country", "phone", "email"] + missing = [f for f in required if not getattr(registrant, f)] + if missing: + raise HTTPException( + status_code=400, + detail=f"Missing required registrant fields: {', '.join(missing)}. Set DEFAULT_* env vars or provide in request." + ) + + try: + result = api.domains_create( + domain=req.domain, + registrant=registrant, + years=req.years, + add_whois_guard=req.add_whois_guard, + ) + return result + except NamecheapError as e: + raise HTTPException(status_code=400, detail=str(e)) + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)