#!/usr/bin/env bash set -Eeuo pipefail # === Настройки === SERVICE="bot" # имя сервиса из docker-compose.yml APP_DIR="/app" # рабочая директория кода в контейнере HOST_DB_DIR="./db" # каталог БД на хосте HOST_DB_FILE="./db/bot.db" # файл БД на хосте DB_URL_DEFAULT="sqlite+aiosqlite:////app/db/bot.db" # ЕДИНЫЙ URL БД в контейнере log(){ echo -e "[update.sh] $*"; } die(){ echo -e "[update.sh][ERROR] $*" >&2; exit 1; } # Запускать из корня репо, даже если скрипт лежит в bin/ cd "$(dirname "${BASH_SOURCE[0]}")/.." # --- Утилиты для alembic в "смонтированном" контейнере (чтобы файлы миграций попадали в репо) --- alembic_run_mounted() { # использование: alembic_run_mounted "upgrade head" docker compose run --rm -T \ -v "$PWD":/app \ -w /app \ "${SERVICE}" sh -lc "alembic $*" } # --- 0) Приводим БД и .env к единому виду --- log "Проверка каталога БД ${HOST_DB_DIR} ..." mkdir -p "${HOST_DB_DIR}" # Если в проекте остался прежний конфликтный объект ./bot.db — убираем/переносим if [[ -d ./bot.db ]]; then log "Удаляю конфликтующую ПАПКУ ./bot.db" rm -rf ./bot.db fi if [[ -f ./bot.db && ! -f "${HOST_DB_FILE}" ]]; then log "Переношу старый файл ./bot.db -> ${HOST_DB_FILE}" mv ./bot.db "${HOST_DB_FILE}" fi if [[ ! -f "${HOST_DB_FILE}" ]]; then log "Создаю пустой файл БД: ${HOST_DB_FILE}" :> "${HOST_DB_FILE}" fi # .env: зафиксировать DATABASE_URL if [[ -f .env ]]; then if grep -q '^DATABASE_URL=' .env; then sed -i "s|^DATABASE_URL=.*$|DATABASE_URL=${DB_URL_DEFAULT}|g" .env log "DATABASE_URL в .env → ${DB_URL_DEFAULT}" else echo "DATABASE_URL=${DB_URL_DEFAULT}" >> .env log "Добавил DATABASE_URL в .env → ${DB_URL_DEFAULT}" fi else echo "DATABASE_URL=${DB_URL_DEFAULT}" > .env log "Создал .env с DATABASE_URL=${DB_URL_DEFAULT}" fi # --- 1) git pull + сборка --- log "git pull --rebase --autostash ..." git pull --rebase --autostash || die "git pull не удался" log "Пересборка образа ..." docker compose build --no-cache || die "docker compose build не удался" # --- 2) Безопасный upgrade: выравниваем БД до HEAD; при «потере» ревизии чиним alembic_version --- safe_upgrade() { log "alembic upgrade head ..." set +e UPG_LOG="$(alembic_run_mounted 'upgrade head' 2>&1)" RC=$? set -e if [[ $RC -eq 0 ]]; then return 0 fi echo "$UPG_LOG" if grep -q "Can't locate revision identified by" <<< "$UPG_LOG"; then log "Обнаружена «потерянная» ревизия. Автопочинка: подшиваю БД к актуальному HEAD ..." # Узнаём актуальный HEAD из каталога миграций set +e HEADREV="$(alembic_run_mounted 'heads -v' | awk '/^Rev:/{print $2; exit}')" set -e [[ -n "${HEADREV:-}" ]] || die "Не удалось определить HEAD ревизию" # Переписываем alembic_version в файле БД (внутри контейнера сервиса) docker compose run --rm -T \ -v "$PWD":/app \ -w /app \ "${SERVICE}" sh -lc "sqlite3 /app/db/bot.db \"UPDATE alembic_version SET version_num='${HEADREV}';\" || true" # Повторяем апгрейд alembic_run_mounted 'upgrade head' || die "Повторный upgrade head не удался" else die "alembic upgrade head не удался" fi } # --- 3) Выравниваем миграции до текущего HEAD --- safe_upgrade # --- 4) (опционально) создаём ревизию с комментарием и применяем её --- MIG_MSG="${1-}" if [[ -z "${MIG_MSG}" ]]; then read -rp "[update.sh] Комментарий для новой миграции (Enter — пропустить): " MIG_MSG || true fi if [[ -n "${MIG_MSG}" ]]; then log "Создаю ревизию Alembic с комментарием: ${MIG_MSG}" alembic_run_mounted "revision --autogenerate -m \"${MIG_MSG}\"" || die "alembic revision --autogenerate не удался" # Сразу применяем safe_upgrade else log "Создание ревизии пропущено." fi # --- 5) Запуск сервиса и пост-проверки --- log "Запускаю контейнер ..." docker compose up -d || die "docker compose up -d не удался" log "Проверка переменных и таблиц внутри контейнера ..." docker compose exec -T "${SERVICE}" sh -lc " echo 'DATABASE_URL='\"\$DATABASE_URL\"; cd '${APP_DIR}'; echo 'Alembic HEADS:'; alembic heads -v || true; echo 'Alembic CURRENT:'; alembic current -v || true; if [ -f /app/db/bot.db ]; then echo 'Таблицы SQLite (/app/db/bot.db):'; sqlite3 /app/db/bot.db '.tables' || true; else echo 'Внимание: /app/db/bot.db отсутствует!'; fi " || true log "Готово ✅" log "Проверка переменных и таблиц внутри контейнера ..." docker compose exec -T "${SERVICE}" sh -lc " echo 'DATABASE_URL='\"\$DATABASE_URL\"; cd '${APP_DIR}'; echo 'Alembic HEADS:'; alembic heads -v || true; echo 'Alembic CURRENT:'; alembic current -v || true; if [ -f /app/db/bot.db ]; then echo 'Таблицы SQLite (/app/db/bot.db):'; sqlite3 /app/db/bot.db '.tables' || true; else echo 'Внимание: /app/db/bot.db отсутствует!'; fi