Библиотеки: - миграция с aiogram 2.25.2 (EOL) на aiogram 3.31: aiohttp обновился с 3.8.6 до 3.14.3, так как aiogram 2 жёстко требовал aiohttp<3.9 - самодельный словарь состояний заменён на FSM aiogram (MemoryStorage); это убирает падение на user_state.startswith(), когда состояния нет - из requirements убраны неиспользуемые пакеты (Babel 2.9.1, humanize, aiofiles, yookassa, YooMoney); оставлены только прямые зависимости Хардкод: - пути вычисляются от расположения файлов бота (awg/config.py), больше нет привязки к /root/amnezia-bot и к запуску из конкретного каталога - вместо 'python3.11' используется sys.executable; awg-decode.py переименован в awg_decode.py и импортируется напрямую вместо запуска через subprocess - newclient.sh и removeclient.sh определяют каталог по своему расположению, а не по cwd - install.sh: каталог, репозиторий, ветка и Python настраиваются переменными окружения, зависимости ставятся из requirements.txt - секреты можно задавать переменными окружения вместо config.json Оплата (встроенные платежи Telegram, провайдер ЮKassa): - счёт, pre_checkout_query и successful_payment; публичный HTTPS-адрес и вебхук не нужны - обработчик платежей вынесен в отдельный роутер и подключён первым: successful_payment приходит без текста и иначе перехватывался бы обработчиком, ожидающим ввода в FSM - выдача ключа идемпотентна по id платежа, повторная доставка апдейта не создаёт второй ключ - если после оплаты ключ создать не удалось, уведомляются администраторы - промокоды разделены на validate/consume: использование засчитывается только после фактической выдачи ключа, а не при проверке кода - поддержаны скидочные промокоды, уменьшающие сумму счёта - опциональные чеки по 54-ФЗ через provider_data Исправленные ошибки: - db.root_add и db.deactive_user_db передавали скриптам 2 аргумента вместо 4, из-за чего создание и удаление клиентов всегда падало; публичный ключ для удаления теперь вычисляется из конфига клиента - в create_backup не был импортирован zipfile (NameError при бэкапе) - планировщик запускался без задач, подписки никогда не истекали; добавлена ежечасная проверка с отключением доступа и уведомлением - callback_data вида renew_period_<имя>_<период> разбирался неверно для имён с подчёркиваниями; разделителем стало двоеточие - статус клиента брался из status.json, который никто не создавал; теперь используется wg show latest-handshakes - продление подписки сбрасывало остаток, теперь период добавляется к нему - Markdown-разметка ломалась на именах с подчёркиваниями; используется HTML с экранированием - vpn:// ключ уходит отдельным сообщением, а не в подписи к файлу, где мог не поместиться в лимит 1024 символа - блокирующие вызовы docker и скриптов вынесены в отдельные потоки - при удалении клиента связь с Telegram ID удаляется, а не пишется None Удалён мёртвый код: awg/wg.py (нигде не импортировался, содержал подсеть 10.0.0.0/24 вместо используемой 10.8.1.0/24) и handlers/add_client.py (импортировал несуществующий модуль loader).
94 строки
2.4 KiB
Bash
Исполняемый файл
94 строки
2.4 KiB
Bash
Исполняемый файл
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
if [ "$#" -ne 4 ]; then
|
|
echo "Usage: $0 CLIENT_NAME CLIENT_PUBLIC_KEY WG_CONFIG_FILE DOCKER_CONTAINER"
|
|
exit 1
|
|
fi
|
|
|
|
CLIENT_NAME="$1"
|
|
CLIENT_PUBLIC_KEY="$2"
|
|
WG_CONFIG_FILE="$3"
|
|
DOCKER_CONTAINER="$4"
|
|
|
|
# Каталог скрипта, а не текущий каталог: бот может быть запущен откуда угодно.
|
|
pwd="$(cd "$(dirname "$0")" && pwd)"
|
|
mkdir -p "$pwd/files"
|
|
SERVER_CONF_PATH="$pwd/files/server.conf"
|
|
|
|
docker exec -i "$DOCKER_CONTAINER" cat "$WG_CONFIG_FILE" > "$SERVER_CONF_PATH"
|
|
|
|
awk -v pubkey="$CLIENT_PUBLIC_KEY" '
|
|
BEGIN {in_peer=0; skip=0}
|
|
/^\[Peer\]/ {
|
|
in_peer=1
|
|
peer_block = $0 "\n"
|
|
next
|
|
}
|
|
in_peer == 1 {
|
|
peer_block = peer_block $0 "\n"
|
|
if ($0 ~ /^PublicKey\s*=/) {
|
|
split($0, a, " = ")
|
|
if (a[2] == pubkey) {
|
|
skip=1
|
|
}
|
|
}
|
|
if ($0 ~ /^\[Peer\]/ || $0 ~ /^\[Interface\]/) {
|
|
if (skip == 1) {
|
|
skip=0
|
|
in_peer=0
|
|
next
|
|
} else {
|
|
print peer_block
|
|
in_peer=0
|
|
}
|
|
}
|
|
if ($0 == "") {
|
|
if (skip == 1) {
|
|
skip=0
|
|
in_peer=0
|
|
next
|
|
} else {
|
|
print peer_block
|
|
in_peer=0
|
|
}
|
|
}
|
|
next
|
|
}
|
|
{
|
|
print
|
|
}
|
|
END {
|
|
if (in_peer == 1 && skip == 1) {
|
|
} else if (in_peer ==1 ) {
|
|
print peer_block
|
|
}
|
|
}
|
|
' "$SERVER_CONF_PATH" > "$SERVER_CONF_PATH.tmp"
|
|
|
|
mv "$SERVER_CONF_PATH.tmp" "$SERVER_CONF_PATH"
|
|
|
|
docker exec -i "$DOCKER_CONTAINER" wg-quick strip "$WG_CONFIG_FILE" > /dev/null
|
|
|
|
docker cp "$SERVER_CONF_PATH" "$DOCKER_CONTAINER":"$WG_CONFIG_FILE"
|
|
|
|
docker exec -i "$DOCKER_CONTAINER" sh -c "wg-quick down '$WG_CONFIG_FILE' && wg-quick up '$WG_CONFIG_FILE'"
|
|
|
|
rm -f "$pwd/users/$CLIENT_NAME/$CLIENT_NAME.conf"
|
|
rmdir "$pwd/users/$CLIENT_NAME" 2>/dev/null || true
|
|
|
|
CLIENTS_TABLE_PATH="$pwd/files/clientsTable"
|
|
docker exec -i "$DOCKER_CONTAINER" cat /opt/amnezia/awg/clientsTable > "$CLIENTS_TABLE_PATH" || echo "[]" > "$CLIENTS_TABLE_PATH"
|
|
|
|
if [ -f "$CLIENTS_TABLE_PATH" ]; then
|
|
jq --arg clientId "$CLIENT_PUBLIC_KEY" 'del(.[] | select(.clientId == $clientId))' "$CLIENTS_TABLE_PATH" > "$CLIENTS_TABLE_PATH.tmp"
|
|
mv "$CLIENTS_TABLE_PATH.tmp" "$CLIENTS_TABLE_PATH"
|
|
docker cp "$CLIENTS_TABLE_PATH" "$DOCKER_CONTAINER":/opt/amnezia/awg/clientsTable
|
|
fi
|
|
|
|
traffic_file="$pwd/users/$CLIENT_NAME/traffic.json"
|
|
rm -f "$traffic_file"
|
|
|
|
echo "Client $CLIENT_NAME успешно удален из WireGuard"
|