реструктуризация файлов проекта
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-08-30 10:08:43 +03:00
parent adc49c285c
commit 251fa0be11
21 changed files with 619 additions and 333 deletions
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
# Использование (из корня проекта):
# bash tools/ops/copy_databases.sh <source_db> <target_db> <username>
# Пример:
# bash tools/ops/copy_databases.sh old_db new_db appuser
set -e # Завершать выполнение при любой ошибке
if [ "$#" -ne 3 ]; then
echo "Использование: $0 <исходная_база> <целевая_база> <имя_пользователя>"
exit 1
fi
SOURCE_DB="$1"
TARGET_DB="$2"
DB_USER="$3"
BACKUP_DIR="."
# Функция для запроса пароля от MariaDB один раз
read_mysql_password() {
if [ -z "$MYSQL_PWD" ]; then
read -s -p "Введите пароль MariaDB для пользователя root: " MYSQL_PWD
echo
export MYSQL_PWD
fi
}
# Функция проверки существования базы данных
db_exists() {
local db_name="$1"
if mysql -u root -e "SHOW DATABASES LIKE '$db_name';" | grep -q "^$db_name$"; then
return 0
else
return 1
fi
}
# Функция резервного копирования базы
backup_db() {
local db_name="$1"
local backup_file="${BACKUP_DIR}/${db_name}.backup.sql"
echo "Создаётся резервная копия базы '$db_name' в файл '$backup_file'..."
mysqldump -u root \
--routines \
--triggers \
--events \
--single-transaction \
"$db_name" > "$backup_file"
echo "Резервная копия '$db_name' успешно создана."
}
# === НАЧАЛО ОСНОВНОГО СЦЕНАРИЯ ===
read_mysql_password
echo "Проверка существования исходной базы '$SOURCE_DB'..."
if ! db_exists "$SOURCE_DB"; then
echo "Ошибка: исходная база данных '$SOURCE_DB' не существует."
exit 1
fi
# Резервное копирование исходной базы (всегда)
backup_db "$SOURCE_DB"
# Проверка существования целевой базы
if db_exists "$TARGET_DB"; then
echo "Целевая база '$TARGET_DB' уже существует."
backup_db "$TARGET_DB"
echo "Удаление существующей базы '$TARGET_DB'..."
mysql -u root -e "DROP DATABASE \`$TARGET_DB\`;"
else
echo "Целевая база '$TARGET_DB' не существует — будет создана."
fi
# Создание новой целевой базы
echo "Создание новой базы '$TARGET_DB'..."
mysql -u root -e "CREATE DATABASE \`$TARGET_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# Создание временного дампа без указания базы (без CREATE DATABASE и USE)
TEMP_DUMP=$(mktemp)
echo "Создание временного дампа из '$SOURCE_DB'..."
mysqldump -u root \
--routines \
--triggers \
--events \
--single-transaction \
--no-create-db \
"$SOURCE_DB" > "$TEMP_DUMP"
# Восстановление в целевую базу
echo "Восстановление данных в '$TARGET_DB'..."
mysql -u root "$TARGET_DB" < "$TEMP_DUMP"
# Удаление временного файла
rm -f "$TEMP_DUMP"
# Настройка прав пользователя
echo "Настройка прав для пользователя '$DB_USER'@'%' на базу '$TARGET_DB'..."
mysql -u root -e "CREATE USER IF NOT EXISTS \`$DB_USER\`@'%';"
mysql -u root -e "GRANT ALL PRIVILEGES ON \`$TARGET_DB\`.* TO \`$DB_USER\`@'%';"
mysql -u root -e "FLUSH PRIVILEGES;"
echo "✅ Копирование завершено успешно!"
echo "Исходная база: $SOURCE_DB → Целевая база: $TARGET_DB"
echo "Пользователь: $DB_USER@% имеет полные права на целевую базу."
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env python3
"""Генерация случайного hex-ключа для SECRET_KEY или WEBHOOK_SECRET.
Запуск из корня проекта:
python tools/ops/genkey.py
"""
import secrets
if __name__ == "__main__":
print(secrets.token_hex(32))
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""
Ручная регистрация Telegram webhook через internal_relay (с secret_token).
Запуск из корня проекта:
./venv/bin/python tools/ops/register_tg_webhook.py
Печатает полный ответ setWebhook — по нему видно, принял ли Telegram секрет.
"""
import json
import sys
import os
_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
if _PROJECT_ROOT not in sys.path:
sys.path.insert(0, _PROJECT_ROOT)
from dotenv import load_dotenv
import requests
load_dotenv(os.path.join(_PROJECT_ROOT, ".env"))
load_dotenv()
from telegram_relay import bot_api_method_url, get_relay_url
TOKEN = os.getenv("RESPONDER_BOT_TOKEN")
WEBHOOK_URL = os.getenv("WEBHOOK_URL")
WEBHOOK_SECRET = (os.getenv("WEBHOOK_SECRET") or "").strip().strip('"').strip("'")
def main() -> int:
if not TOKEN:
print("ERROR: RESPONDER_BOT_TOKEN не задан")
return 1
if not WEBHOOK_URL:
print("ERROR: WEBHOOK_URL не задан")
return 1
if not WEBHOOK_SECRET:
print("ERROR: WEBHOOK_SECRET не задан")
return 1
api_url = bot_api_method_url(TOKEN, "setWebhook")
form = {
"url": WEBHOOK_URL,
"secret_token": WEBHOOK_SECRET,
"max_connections": "40",
# Иначе Telegram может сохранить старый список без callback_query
"allowed_updates": json.dumps([
"message",
"edited_message",
"callback_query",
]),
}
print(f"RELAY_URL = {get_relay_url()}")
print(f"WEBHOOK_URL = {WEBHOOK_URL}")
print(f"secret_len = {len(WEBHOOK_SECRET)}")
print(f"POST = {api_url.replace(TOKEN, '<TOKEN>')}")
print(f"form keys = {list(form.keys())}")
resp = requests.post(api_url, data=form, timeout=60)
print(f"HTTP status = {resp.status_code}")
try:
data = resp.json()
print("JSON response:")
print(json.dumps(data, ensure_ascii=False, indent=2))
except Exception:
print("Raw response:")
print(resp.text[:1000])
return 1
if not data.get("ok"):
print("FAILED: Telegram/relay вернул ok=false")
return 1
print("OK: setWebhook принят. Нажмите «Подписка» / отправьте /start боту.")
print("В логе бота не должно быть «Секретный токен отсутствует».")
return 0
if __name__ == "__main__":
sys.exit(main())