Files
knowledge-base/snippets/spaceweb-dns-api.py
Claude Code 066855ce28 mail.niikn.com: DNS настроен, сценарий задокументирован для dttb.ru
- README НИИКН: обновлён Mailcow (пароль, DNS, порты, ящик)
- changelog: добавлена запись о настройке mail.niikn.com
- credentials: добавлены Spaceweb, Mailcow НИИКН, NPM НИИКН
- decisions: сценарий настройки почтового сервера (шаблон для dttb.ru)
- snippets: скрипт spaceweb-dns-api.py для управления DNS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:34:52 +00:00

168 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Spaceweb DNS API — управление DNS записями через JSON-RPC.
Панель: https://vps.sweb.ru
Домены: niikn.com, dttb.ru, itilegent.ru
Использование:
python3 spaceweb-dns-api.py info niikn.com
python3 spaceweb-dns-api.py add-mx niikn.com mail.niikn.com 10
python3 spaceweb-dns-api.py add-txt niikn.com @ "v=spf1 mx ~all"
python3 spaceweb-dns-api.py add-txt niikn.com _dmarc "v=DMARC1; p=none"
python3 spaceweb-dns-api.py del niikn.com TXT 1
python3 spaceweb-dns-api.py zone niikn.com
"""
import urllib.request
import urllib.parse
import json
import http.cookiejar
import sys
LOGIN = "it5870yand"
PASSWORD = "1qaz!QAZ"
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
def create_session():
"""Логин в Spaceweb, возвращает opener с куками."""
cj = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
# GET login page
req = urllib.request.Request("https://mcp.sweb.ru/")
req.add_header("User-Agent", UA)
opener.open(req)
# POST auth
login_data = urllib.parse.urlencode({
"login": LOGIN, "password": PASSWORD,
"new_panel": "1", "to": "//mcp.sweb.ru/main/index/", "savepref": ""
}).encode()
req = urllib.request.Request("https://mcp.sweb.ru/main/auth_submit/",
data=login_data, method="POST")
req.add_header("Content-Type", "application/x-www-form-urlencoded")
req.add_header("User-Agent", UA)
req.add_header("Referer", "https://mcp.sweb.ru/main/auth/")
resp = opener.open(req)
if "vps.sweb.ru" not in resp.url and "mcp.sweb.ru/main/index" not in resp.url:
raise RuntimeError(f"Login failed, redirected to: {resp.url}")
return opener
def api_call(opener, method, params):
"""Вызов JSON-RPC API Spaceweb."""
payload = {
"jsonrpc": "2.0",
"id": 1,
"user": LOGIN,
"method": method,
"params": params
}
body = urllib.parse.quote(json.dumps(payload, separators=(",", ":")), safe="").encode()
req = urllib.request.Request("https://api.sweb.ru/domains/dns",
data=body, method="POST")
req.add_header("Content-Type", "application/x-www-form-urlencoded")
req.add_header("User-Agent", UA)
req.add_header("Origin", "https://vps.sweb.ru")
req.add_header("Referer", "https://vps.sweb.ru/")
resp = opener.open(req)
result = json.loads(resp.read().decode())
if "error" in result:
raise RuntimeError(f"API error: {result['error']['message']}")
return result.get("result")
def cmd_info(opener, domain):
"""Показать все DNS записи."""
records = api_call(opener, "info", {"domain": domain})
print(f"DNS записи для {domain}:")
for r in records:
t = r.get("type", "?")
n = r.get("name", "@") or "@"
v = r.get("value", "")
idx = r.get("index", "?")
extra = f" (pri: {r.get('priority')})" if r.get("priority") else ""
print(f" [{idx:>2}] {t:5s} {n:30s}{v[:100]}{extra}")
def cmd_zone(opener, domain):
"""Показать zone file."""
result = api_call(opener, "getFile", {"domain": domain})
print(result["content"])
def cmd_add_mx(opener, domain, value, priority="10"):
"""Добавить MX запись."""
if not value.endswith("."):
value += "."
result = api_call(opener, "editMx", {
"domain": domain, "subDomain": "", "action": "add",
"priority": str(priority), "value": value
})
print(f"MX добавлен: {domain}{value} (pri {priority}): {result}")
def cmd_add_txt(opener, domain, subdomain, value):
"""Добавить TXT запись."""
result = api_call(opener, "editTxt", {
"domain": domain, "action": "add",
"subDomain": subdomain, "value": value
})
print(f"TXT добавлен: {subdomain}.{domain}{value[:60]}...: {result}")
def cmd_add_a(opener, domain, name, ip):
"""Добавить A запись (субдомен)."""
result = api_call(opener, "editMain", {
"domain": domain, "action": "add",
"name": name, "type": "A", "value": ip, "prefix": ""
})
print(f"A добавлен: {name}.{domain}{ip}: {result}")
def cmd_del(opener, domain, record_type, index):
"""Удалить запись по типу и индексу."""
method_map = {
"A": "editMain", "AAAA": "editMain", "CNAME": "editMain",
"MX": "editMx", "TXT": "editTxt", "NS": "editNs",
"SRV": "editSrv", "CAA": "editCaa"
}
method = method_map.get(record_type.upper(), "editMain")
result = api_call(opener, method, {
"domain": domain, "action": "del",
"index": int(index), "type": record_type.upper()
})
print(f"Удалено: {record_type} index={index}: {result}")
def main():
if len(sys.argv) < 3:
print(__doc__)
sys.exit(1)
cmd = sys.argv[1]
domain = sys.argv[2]
opener = create_session()
if cmd == "info":
cmd_info(opener, domain)
elif cmd == "zone":
cmd_zone(opener, domain)
elif cmd == "add-mx" and len(sys.argv) >= 4:
pri = sys.argv[4] if len(sys.argv) > 4 else "10"
cmd_add_mx(opener, domain, sys.argv[3], pri)
elif cmd == "add-txt" and len(sys.argv) >= 5:
cmd_add_txt(opener, domain, sys.argv[3], sys.argv[4])
elif cmd == "add-a" and len(sys.argv) >= 5:
cmd_add_a(opener, domain, sys.argv[3], sys.argv[4])
elif cmd == "del" and len(sys.argv) >= 5:
cmd_del(opener, domain, sys.argv[3], sys.argv[4])
else:
print(__doc__)
sys.exit(1)
if __name__ == "__main__":
main()