новый лендинг

This commit is contained in:
2026-04-26 16:09:08 +04:00
parent 103e2bc4cf
commit 7701aa16c0
24 changed files with 6368 additions and 341 deletions
+300
View File
@@ -0,0 +1,300 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
BACKUP_DIR="${BACKUP_DIR:-${ROOT_DIR}/storage/backups}"
MYSQL_CLIENT_IMAGE="${MYSQL_CLIENT_IMAGE:-mysql:8.4}"
usage() {
cat <<'EOF'
Использование:
./scripts/db-backup.sh export [--target=all|app|auth|characters] [--out=DIR]
./scripts/db-backup.sh import <файл.sql[.gz]> --target=app|auth|characters
./scripts/db-backup.sh list
Опции:
--env=PATH путь к .env (по умолчанию ./.env)
--target=NAME какая БД: app (laravel), auth (acore_auth),
characters (acore_characters), all (по умолчанию для export)
--out=DIR куда писать дампы (по умолчанию storage/backups)
--gzip сжимать дамп gzip-ом (по умолчанию включено для export)
--no-gzip не сжимать
--force для import: не спрашивать подтверждение
--create-db перед импортом создать БД, если её нет
-h, --help показать справку
Переменные окружения:
ENV_FILE, BACKUP_DIR — переопределяют значения по умолчанию
MYSQL_CLIENT_IMAGE — образ для фолбэка (по умолчанию mysql:8.4)
Зависимости:
Если на хосте установлены mysqldump/mysql — используются они.
Иначе скрипт запускает их в одноразовом контейнере MYSQL_CLIENT_IMAGE.
Примеры:
./scripts/db-backup.sh export
./scripts/db-backup.sh export --target=auth --out=/tmp
./scripts/db-backup.sh import /tmp/acore_auth_2026-04-26.sql.gz --target=auth
./scripts/db-backup.sh import dump.sql.gz --target=app --create-db --force
EOF
}
log() { printf '\033[1;34m[db-backup]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[db-backup]\033[0m %s\n' "$*" >&2; }
err() { printf '\033[1;31m[db-backup]\033[0m %s\n' "$*" >&2; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || { err "Не найдена команда: $1"; exit 1; }
}
# Безопасное чтение значения из .env (поддержка кавычек)
env_get() {
local key="$1" file="$2"
local line value
line="$(grep -E "^${key}=" "$file" | tail -n1 || true)"
[[ -z "$line" ]] && return 0
value="${line#${key}=}"
# снять обрамляющие кавычки
if [[ "$value" =~ ^\".*\"$ ]] || [[ "$value" =~ ^\'.*\'$ ]]; then
value="${value:1:${#value}-2}"
fi
printf '%s' "$value"
}
load_target() {
local target="$1"
case "$target" in
app)
DB_HOST="$(env_get DB_HOST "$ENV_FILE")"
DB_PORT="$(env_get DB_PORT "$ENV_FILE")"
DB_NAME="$(env_get DB_DATABASE "$ENV_FILE")"
DB_USER="$(env_get DB_USERNAME "$ENV_FILE")"
DB_PASS="$(env_get DB_PASSWORD "$ENV_FILE")"
;;
auth)
DB_HOST="$(env_get AZEROTHCORE_AUTH_DB_HOST "$ENV_FILE")"
DB_PORT="$(env_get AZEROTHCORE_AUTH_DB_PORT "$ENV_FILE")"
DB_NAME="$(env_get AZEROTHCORE_AUTH_DB_DATABASE "$ENV_FILE")"
DB_USER="$(env_get AZEROTHCORE_AUTH_DB_USERNAME "$ENV_FILE")"
DB_PASS="$(env_get AZEROTHCORE_AUTH_DB_PASSWORD "$ENV_FILE")"
;;
characters)
DB_HOST="$(env_get AZEROTHCORE_CHARACTERS_DB_HOST "$ENV_FILE")"
DB_PORT="$(env_get AZEROTHCORE_CHARACTERS_DB_PORT "$ENV_FILE")"
DB_NAME="$(env_get AZEROTHCORE_CHARACTERS_DB_DATABASE "$ENV_FILE")"
DB_USER="$(env_get AZEROTHCORE_CHARACTERS_DB_USERNAME "$ENV_FILE")"
DB_PASS="$(env_get AZEROTHCORE_CHARACTERS_DB_PASSWORD "$ENV_FILE")"
;;
*)
err "Неизвестная цель: $target"; exit 2;;
esac
# docker-only хосты заменяем на localhost при запуске вне контейнера
if [[ "$DB_HOST" == "mysql" || "$DB_HOST" == "host.docker.internal" ]]; then
local fwd
fwd="$(env_get FORWARD_DB_PORT "$ENV_FILE")"
if [[ -n "$fwd" && "$target" == "app" ]]; then
DB_PORT="$fwd"
fi
DB_HOST="127.0.0.1"
fi
if [[ -z "$DB_NAME" || -z "$DB_USER" ]]; then
err "Не удалось определить параметры подключения для target=$target"
exit 1
fi
}
have_native_mysql() {
command -v mysqldump >/dev/null 2>&1 && command -v mysql >/dev/null 2>&1
}
# Запуск mysqldump/mysql либо на хосте, либо в одноразовом docker-контейнере.
# Стдин/стдаут пробрасываются — годится и для дампа, и для импорта по pipe.
# $1 — "mysqldump" или "mysql"; остальные — доп.аргументы тулзе.
# Подключение читается из глобалей DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME.
run_mysql_tool() {
local tool="$1"; shift
if have_native_mysql; then
local defaults
defaults="$(mktemp)"
chmod 600 "$defaults"
{
printf '[client]\n'
printf 'host=%s\n' "$DB_HOST"
printf 'port=%s\n' "${DB_PORT:-3306}"
printf 'user=%s\n' "$DB_USER"
if [[ -n "${DB_PASS:-}" && "$DB_PASS" != "null" ]]; then
printf 'password=%s\n' "$DB_PASS"
fi
} > "$defaults"
local rc=0
"$tool" --defaults-extra-file="$defaults" "$@" || rc=$?
rm -f "$defaults"
return $rc
fi
require_cmd docker
local target_host="$DB_HOST"
if [[ "$target_host" == "127.0.0.1" || "$target_host" == "localhost" ]]; then
target_host="host.docker.internal"
fi
local pwd_value=""
if [[ -n "${DB_PASS:-}" && "$DB_PASS" != "null" ]]; then
pwd_value="$DB_PASS"
fi
docker run --rm -i \
--add-host=host.docker.internal:host-gateway \
-e MYSQL_PWD="$pwd_value" \
"$MYSQL_CLIENT_IMAGE" \
"$tool" \
-h"$target_host" \
-P"${DB_PORT:-3306}" \
-u"$DB_USER" \
"$@"
}
do_export_one() {
local target="$1" out_dir="$2" use_gzip="$3"
load_target "$target"
local ts dump_path
ts="$(date +%Y-%m-%d_%H-%M-%S)"
dump_path="${out_dir}/${DB_NAME}_${ts}.sql"
log "Экспорт ${target} (${DB_NAME}@${DB_HOST}:${DB_PORT:-3306}) → ${dump_path}$([[ $use_gzip -eq 1 ]] && echo .gz)"
local -a dump_args=(
--single-transaction
--quick
--routines
--triggers
--events
--set-gtid-purged=OFF
--hex-blob
--default-character-set=utf8mb4
--no-tablespaces
"$DB_NAME"
)
if [[ "$use_gzip" -eq 1 ]]; then
require_cmd gzip
run_mysql_tool mysqldump "${dump_args[@]}" | gzip -c > "${dump_path}.gz"
log "Готово: ${dump_path}.gz ($(du -h "${dump_path}.gz" | cut -f1))"
else
run_mysql_tool mysqldump "${dump_args[@]}" > "$dump_path"
log "Готово: ${dump_path} ($(du -h "$dump_path" | cut -f1))"
fi
}
do_export() {
local target="$1" out_dir="$2" use_gzip="$3"
mkdir -p "$out_dir"
if [[ "$target" == "all" ]]; then
for t in app auth characters; do
do_export_one "$t" "$out_dir" "$use_gzip"
done
else
do_export_one "$target" "$out_dir" "$use_gzip"
fi
}
do_import() {
local file="$1" target="$2" force="$3" create_db="$4"
[[ -f "$file" ]] || { err "Файл не найден: $file"; exit 1; }
[[ -z "$target" ]] && { err "Для import обязателен --target"; exit 2; }
load_target "$target"
if [[ "$force" -ne 1 ]]; then
printf '\033[1;33m[!] Импорт перезапишет данные в %s@%s:%s. Продолжить? [y/N]: \033[0m' \
"$DB_NAME" "$DB_HOST" "${DB_PORT:-3306}"
read -r answer
[[ "$answer" =~ ^[yYдД]$ ]] || { warn "Отменено"; exit 0; }
fi
if [[ "$create_db" -eq 1 ]]; then
log "Создаю БД ${DB_NAME} при отсутствии"
local saved_name="$DB_NAME"
DB_NAME=""
run_mysql_tool mysql -e \
"CREATE DATABASE IF NOT EXISTS \`$saved_name\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
DB_NAME="$saved_name"
fi
log "Импорт ${file}${target} (${DB_NAME}@${DB_HOST}:${DB_PORT:-3306})"
if [[ "$file" == *.gz ]]; then
require_cmd gzip
gzip -dc "$file" | run_mysql_tool mysql "$DB_NAME"
else
run_mysql_tool mysql "$DB_NAME" < "$file"
fi
log "Импорт завершён"
}
do_list() {
if [[ ! -d "$BACKUP_DIR" ]]; then
warn "Директория бэкапов отсутствует: $BACKUP_DIR"
return 0
fi
log "Бэкапы в $BACKUP_DIR:"
ls -lh "$BACKUP_DIR" 2>/dev/null | awk 'NR>1 {print " " $9 " (" $5 ", " $6, $7, $8 ")"}'
}
main() {
[[ $# -lt 1 ]] && { usage; exit 2; }
local cmd="$1"; shift
local target="" out_dir="$BACKUP_DIR" use_gzip=1 force=0 create_db=0 import_file=""
while [[ $# -gt 0 ]]; do
case "$1" in
--env=*) ENV_FILE="${1#*=}";;
--target=*) target="${1#*=}";;
--out=*) out_dir="${1#*=}";;
--gzip) use_gzip=1;;
--no-gzip) use_gzip=0;;
--force) force=1;;
--create-db) create_db=1;;
-h|--help) usage; exit 0;;
-*) err "Неизвестная опция: $1"; usage; exit 2;;
*) import_file="$1";;
esac
shift
done
[[ -f "$ENV_FILE" ]] || { err ".env не найден: $ENV_FILE"; exit 1; }
case "$cmd" in
export)
[[ -z "$target" ]] && target="all"
do_export "$target" "$out_dir" "$use_gzip"
;;
import)
[[ -z "$import_file" ]] && { err "Укажите путь к дампу"; usage; exit 2; }
do_import "$import_file" "$target" "$force" "$create_db"
;;
list)
do_list
;;
-h|--help|help)
usage
;;
*)
err "Неизвестная команда: $cmd"
usage
exit 2
;;
esac
}
main "$@"