mirror of
https://github.com/andrey271192/kaskad.git
synced 2026-09-21 13:51:57 +00:00
Каскадная маршрутизация русских сайтов через свой набор 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/ — шаблоны конфигов
118 lines
3.7 KiB
Python
Executable File
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)
|