docs: split old Notes into 09.3.N, fix 00 (news bot), move check scripts to scripts/checks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Бобырев Андрей
2026-10-23 01:44:20 +03:00
parent dbab96f7fd
commit cb230fe50e
22 changed files with 498 additions and 1 deletions

53
scripts/checks/audit2.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Сверяем ПОДНЯТЫЕ интерфейсы с их собственными конфигами.
Первый заход сравнивал wg0.conf с интерфейсом wg0, а в контейнере amnezia-awg
поднят awg0 — отсюда ложные «расхождения».
"""
import json, sys
sys.path.insert(0, "/app")
from managers.ssh_manager import SSHManager
PROBE = r"""
for c in $(docker ps --format '{{.Names}}' 2>/dev/null | grep '^amnezia-' | grep -vE 'nginx|frontend|backend|landing|xray|socks|ipsec|openvpn'); do
for bin in awg wg; do
docker exec $c sh -c "command -v $bin >/dev/null" 2>/dev/null || continue
for i in $(docker exec $c sh -c "$bin show interfaces 2>/dev/null"); do
live=$(docker exec $c sh -c "$bin show $i peers 2>/dev/null | grep -c ." | tr -d '\n')
conf=""
for f in /opt/amnezia/awg/$i.conf /opt/amnezia/awg/awg0.conf /opt/amnezia/awg/wg0.conf; do
docker exec $c test -f $f 2>/dev/null && { conf=$f; break; }
done
[ -n "$conf" ] || { echo "$c $i live=$live конфиг=НЕ НАЙДЕН"; continue; }
infile=$(docker exec $c grep -c '^\[Peer\]' $conf 2>/dev/null | tr -d '\n')
echo "$c $i live=$live файл=$infile ($conf)"
done
done
done
"""
d = json.load(open("/app/data/data.json"))
плохо = 0
for s in d["servers"]:
try:
with SSHManager(s["host"], s.get("ssh_port", 22), s["username"],
password=s.get("password") or None,
private_key=s.get("private_key") or None,
connect_timeout=8) as ssh:
out, err, _ = ssh.run_command(PROBE, timeout=60)
строки = [x.strip() for x in (out or "").splitlines() if x.strip()]
if not строки:
print(" ? %-26s нет поднятых интерфейсов" % s["name"][:26])
continue
for стр in строки:
беда = False
if "live=" in стр and "файл=" in стр:
l = int(стр.split("live=")[1].split()[0])
f = int(стр.split("файл=")[1].split()[0])
беда = (l != f)
плохо += 1 if беда else 0
print(" %s %-26s %s" % ("!!" if беда else "ok", s["name"][:26], стр))
except Exception as e:
плохо += 1
print(" !! %-26s НЕ ДОСТУЧАЛСЯ: %s" % (s["name"][:26], str(e)[:60]))
print("\nрасхождений:", плохо)

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Целостность конфигов awg по всему парку: склейки, финальный перевод строки,
проходит ли awg-quick strip и совпадает ли число пиров с живым интерфейсом."""
import json, sys
sys.path.insert(0, "/app")
from managers.ssh_manager import SSHManager
PROBE = r"""
for c in amnezia-awg2 amnezia-awg amnezia-awg31 amnezia-awg3 amnezia-awg-legacy; do
docker ps --format '{{.Names}}' 2>/dev/null | grep -qx $c || continue
for f in awg0.conf wg0.conf; do
p=/opt/amnezia/awg/$f
docker exec $c test -f $p 2>/dev/null || continue
glue=$(docker exec $c grep -c '/32\[Peer\]' $p 2>/dev/null | tr -d '\n')
end=$(docker exec $c sh -c "tail -c 1 $p | od -An -c | tr -d ' \n'")
peers=$(docker exec $c grep -c '^\[Peer\]' $p 2>/dev/null | tr -d '\n')
mode=$(docker exec $c stat -c %a $p 2>/dev/null | tr -d '\n')
bin=wg; iface=wg0
case $f in awg0.conf) bin=awg; iface=awg0;; esac
if docker exec $c sh -c "$bin-quick strip $p > /dev/null 2>&1"; then strip=ok; else strip=СБОЙ; fi
live=$(docker exec $c sh -c "$bin show $iface peers 2>/dev/null | grep -c ." | tr -d '\n')
echo "$c/$f peers=$peers live=$live glue=$glue end=$end mode=$mode strip=$strip"
done
done
"""
d = json.load(open("/app/data/data.json"))
беда = 0
for s in d["servers"]:
try:
with SSHManager(s["host"], s.get("ssh_port", 22), s["username"],
password=s.get("password") or None,
private_key=s.get("private_key") or None,
connect_timeout=8) as ssh:
out, err, _ = ssh.run_command(PROBE, timeout=60)
строки = [x.strip() for x in (out or "").splitlines() if x.strip()]
if not строки:
print(" ? %-26s нет контейнеров awg (%s)" % (s["name"][:26], (err or "")[:40]))
continue
for стр in строки:
плохо = ("glue=0" not in стр) or ("strip=ok" not in стр) or ("end=\\n" not in стр)
if "peers=" in стр and "live=" in стр:
try:
p = int(стр.split("peers=")[1].split()[0])
l = int(стр.split("live=")[1].split()[0])
if p != l:
плохо = True
стр += " ← файл и интерфейс расходятся"
except Exception:
pass
беда += 1 if плохо else 0
print(" %s %-26s %s" % ("!!" if плохо else "ok", s["name"][:26], стр))
except Exception as e:
беда += 1
print(" !! %-26s НЕ ДОСТУЧАЛСЯ: %s" % (s["name"][:26], str(e)[:60]))
print("\nпроблемных строк:", беда)

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Ищем склеенные строки в конфигах awg на всех серверах панели.
Имена переменных в пробе латиницей: sh внутри контейнеров кириллицу не берёт.
"""
import json, sys
sys.path.insert(0, "/app")
from managers.ssh_manager import SSHManager
ПРОБА = (
"for c in amnezia-awg2 amnezia-awg amnezia-awg31 amnezia-awg3 amnezia-awg-legacy; do "
" docker ps --format '{{.Names}}' 2>/dev/null | grep -qx $c || continue; "
" for f in awg0.conf wg0.conf; do "
" p=/opt/amnezia/awg/$f; "
" docker exec $c test -f $p 2>/dev/null || continue; "
" glue=$(docker exec $c grep -c '/32\\[Peer\\]' $p 2>/dev/null || echo 0); "
" endnl=$(docker exec $c sh -c \"tail -c 1 $p | od -An -c | tr -d ' '\" 2>/dev/null); "
" peers=$(docker exec $c grep -c '^\\[Peer\\]' $p 2>/dev/null || echo 0); "
" echo \"$c/$f peers=$peers glue=$glue end=$endnl\"; "
" done; done"
)
d = json.load(open("/app/data/data.json"))
for s in d["servers"]:
try:
with SSHManager(s["host"], s.get("ssh_port", 22), s["username"],
password=s.get("password") or None,
private_key=s.get("private_key") or None,
connect_timeout=8) as ssh:
out, err, _ = ssh.run_command(ПРОБА, timeout=40)
for строка in (out or err).splitlines():
строка = строка.strip()
if not строка:
continue
плохо = ("glue=0" not in строка) or ("end=\\n" not in строка)
print("%s %-26s %s" % (" !!" if плохо else " ", s["name"][:26], строка))
except Exception as e:
print(" %-26s СБОЙ: %s" % (s["name"][:26], str(e)[:50]))

View File

@@ -0,0 +1,30 @@
"""Не осталось ли от проверок тестовых клиентов в clientsTable и в data.json."""
import json, sys
sys.path.insert(0, "/app")
from managers.ssh_manager import SSHManager
МЕТКА = "__проверка_связи"
d = json.load(open("/app/data/data.json"))
в_панели = [c for c in json.dumps(d, ensure_ascii=False).split('"') if МЕТКА in c]
print("следов в data.json панели:", len(в_панели))
ПРОБА = (
"for c in $(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'amnezia-(awg|wireguard|xray)'); do "
" n=$(docker exec $c sh -c \"grep -o '%s[^\\\"]*' /opt/amnezia/awg/clientsTable /opt/amnezia/xray/clientsTable 2>/dev/null | wc -l\"); "
" [ \"$n\" != \"0\" ] && echo \"$c: $n\"; done; echo готово" % МЕТКА
)
всего = 0
for s in d["servers"]:
try:
with SSHManager(s["host"], s.get("ssh_port", 22), s["username"],
password=s.get("password") or None,
private_key=s.get("private_key") or None, connect_timeout=8) as ssh:
o, _, _ = ssh.run_command(ПРОБА, timeout=40)
строки = [x for x in (o or "").splitlines() if x.strip() and "готово" not in x]
if строки:
всего += len(строки)
print(" !!", s["name"], строки)
except Exception as e:
print(" ?", s["name"][:26], str(e)[:40])
print("серверов с остатками:", всего)

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Боевая проверка: на каждом сервере и каждом протоколе создаём тестового
клиента и тут же удаляем. Именно так работает кнопка «Добавить подключение».
Считаем пиров до и после: если после удаления число не вернулось — значит
за нами остался мусор, и это надо видеть.
"""
import json, sys, time
sys.path.insert(0, "/app")
from managers.ssh_manager import SSHManager
from managers.awg_manager import AWGManager
ИМЯ = "__проверка_связи_" + time.strftime("%d%m_%H%M")
ПРОТОКОЛЫ = ("awg", "awg2", "awg3", "awg31", "wireguard")
d = json.load(open("/app/data/data.json"))
итог = {"ok": 0, "сбой": 0, "мусор": 0}
for s in d["servers"]:
протоколы = [p for p in ПРОТОКОЛЫ
if (s.get("protocols", {}).get(p) or {}).get("installed")]
if not протоколы:
print(" – %-26s протоколов AWG не заведено" % s["name"][:26])
continue
for proto in протоколы:
метка = "%s / %s" % (s["name"][:22], proto)
try:
ssh = SSHManager(s["host"], s.get("ssh_port", 22), s["username"],
password=s.get("password") or None,
private_key=s.get("private_key") or None,
connect_timeout=10, connect_retries=1)
ssh.connect()
try:
m = AWGManager(ssh)
было = len(m._get_used_ips(proto))
порт = (s["protocols"][proto] or {}).get("port", "55424")
r = m.add_client(proto, ИМЯ, s["host"], порт)
cid = r.get("client_id") or r.get("clientId")
есть_конфиг = bool(r.get("config"))
стало = len(m._get_used_ips(proto))
m.remove_client(proto, cid)
после = len(m._get_used_ips(proto))
finally:
ssh.disconnect()
если_чисто = (после == было)
итог["ok" if (есть_конфиг and стало == было + 1) else "сбой"] += 1
if not если_чисто:
итог["мусор"] += 1
print(" %s %-30s пиров %d → %d → %d конфиг=%s" % (
"ok" if (есть_конфиг and стало == было + 1 and если_чисто) else "!!",
метка, было, стало, после, "есть" if есть_конфиг else "НЕТ"))
except Exception as e:
итог["сбой"] += 1
print(" !! %-30s %s" % (метка, str(e)[:70]))
print("\nсоздалось и убралось начисто: %d, сбоев: %d, осталось мусора: %d"
% (итог["ok"], итог["сбой"], итог["мусор"]))