Files
kaskad/bin/ru-domains.py
andrey271192 209caa81a8 Initial release of Kaskad
Каскадная маршрутизация русских сайтов через свой набор RU-серверов
с failover, Telegram-ботом и веб-интерфейсом.

- bin/        — failover-script, route/domain helpers, ams/ru bootstrap
- bot/        — Telegram-бот (Python, long-poll)
- webui/      — Flask single-page dashboard
- docs/       — установка, архитектура, API бота и WebUI
- examples/   — шаблоны конфигов
2026-05-02 09:13:23 +03:00

118 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Управление доменами для маршрутизации через ru-туннель."""
import json, subprocess, sys
from pathlib import Path
JSON = Path("/etc/wireguard/ru-domains.json")
ROUTES = "/usr/local/bin/ru-routes.sh"
def load():
if not JSON.exists():
JSON.write_text("{}")
JSON.chmod(0o600)
return json.loads(JSON.read_text())
def save(d):
JSON.write_text(json.dumps(d, indent=2, ensure_ascii=False))
JSON.chmod(0o600)
def resolve(dom):
try:
r = subprocess.run(["dig","+short","+time=3","+tries=2","A", dom],
capture_output=True, text=True, timeout=10)
except Exception:
return set()
out = set()
for line in r.stdout.split("\n"):
line = line.strip()
if line and line.count(".") == 3 and line.replace(".","").isdigit():
out.add(line)
return out
def routes_call(verb, ips):
if not ips: return
args = [ROUTES, verb] + [ip + "/32" for ip in ips]
subprocess.run(args, capture_output=True)
def cmd_list():
d = load()
if not d: return "(пусто)"
lines = []
for dom, ips in sorted(d.items()):
lines.append(f"{dom}: {len(ips)} IP")
return "\n".join(lines)
def cmd_show(dom):
d = load()
if dom not in d: return f"{dom}: нет в списке"
return f"{dom}:\n" + "\n".join(d[dom])
def cmd_add(domains):
d = load()
results = []
for dom in domains:
ips = resolve(dom)
if not ips:
results.append(f"{dom}: не резолвится")
continue
old = set(d.get(dom, []))
new = old | ips
d[dom] = sorted(new)
added = ips - old
routes_call("add", sorted(added))
results.append(f"{dom}: {len(ips)} IP (+{len(added)} новых)")
save(d)
return "\n".join(results)
def cmd_remove(domains):
d = load()
results = []
for dom in domains:
if dom not in d:
results.append(f"{dom}: нет в списке")
continue
ips_to_check = set(d[dom])
del d[dom]
still_owned = set()
for v in d.values(): still_owned.update(v)
to_remove = ips_to_check - still_owned
routes_call("remove", sorted(to_remove))
results.append(f"{dom}: убрано {len(to_remove)} IP из extra")
save(d)
return "\n".join(results)
def cmd_refresh():
d = load()
total_add = 0; total_rm = 0; failed = []
for dom in list(d.keys()):
new_ips = resolve(dom)
if not new_ips:
failed.append(dom); continue
old = set(d[dom])
added = new_ips - old
removed = old - new_ips
d[dom] = sorted(new_ips)
# после обновления — пересобрать карту владельцев
owners = set()
for v in d.values(): owners.update(v)
to_rm = sorted(removed - owners)
routes_call("add", sorted(added))
routes_call("remove", to_rm)
total_add += len(added)
total_rm += len(to_rm)
save(d)
msg = f"обновлено: +{total_add} / -{total_rm} IP"
if failed: msg += f"\nне резолвятся: {', '.join(failed)}"
return msg
if __name__ == "__main__":
if len(sys.argv) < 2:
print("usage: ru-domains {list|show DOM|add DOM...|remove DOM...|refresh}"); sys.exit(1)
cmd = sys.argv[1]; args = sys.argv[2:]
if cmd == "list": print(cmd_list())
elif cmd == "show": print(cmd_show(args[0]) if args else "show DOM")
elif cmd == "add": print(cmd_add(args))
elif cmd in ("remove","del"): print(cmd_remove(args))
elif cmd == "refresh": print(cmd_refresh())
else: print(f"unknown: {cmd}"); sys.exit(1)