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>
This commit is contained in:
Claude Code
2026-03-03 22:34:52 +00:00
parent 41f46e3ac2
commit 066855ce28
5 changed files with 370 additions and 7 deletions

View File

@@ -0,0 +1,167 @@
#!/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()