diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..8699c61 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,261 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# ------------------------------------------------------------ +# Hockey Web / Stat2TV deploy +# ------------------------------------------------------------ + +REPO_URL="https://git.tvstart.ru/ychernenko/hockey_new.git" +TARGET_DIR="/root/KHL" +SERVICE_NAME="khl-data.service" +ENV_FILE="/mnt/khl/.env" +PORT="8000" +RELEASE="main" + +log() { printf '\033[0;32m[INFO]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[WARN]\033[0m %s\n' "$*" >&2; } +err() { printf '\033[0;31m[ERROR]\033[0m %s\n' "$*" >&2; } + +usage() { + cat </dev/null 2>&1 || missing+=("$cmd") + done + + if ! python3 -m venv --help >/dev/null 2>&1; then + missing+=("python3-venv") + fi + + if ((${#missing[@]} > 0)); then + log "Устанавливаю системные зависимости: ${missing[*]}" + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + git python3 python3-venv python3-pip curl ca-certificates + else + log "Системные зависимости уже установлены" + fi +} + +check_env() { + if [[ ! -f "$ENV_FILE" ]]; then + err "Не найден файл окружения: $ENV_FILE" + err "Создай его до deploy. Минимально нужны HOCKEY_DATABASE_URL и WFL_DATABASE_URL." + exit 1 + fi + chmod 600 "$ENV_FILE" || true +} + +checkout_release() { + if [[ -d "$TARGET_DIR/.git" ]]; then + log "Обновляю существующий репозиторий в $TARGET_DIR" + git -C "$TARGET_DIR" remote set-url origin "$REPO_URL" + git -C "$TARGET_DIR" fetch origin --prune --tags + else + log "Клонирую $REPO_URL -> $TARGET_DIR" + mkdir -p "$(dirname "$TARGET_DIR")" + git clone "$REPO_URL" "$TARGET_DIR" + git -C "$TARGET_DIR" fetch origin --prune --tags + fi + + # Ветка origin/ + if git -C "$TARGET_DIR" show-ref --verify --quiet "refs/remotes/origin/$RELEASE"; then + log "Deploy ветки: $RELEASE" + git -C "$TARGET_DIR" checkout -B "$RELEASE" "origin/$RELEASE" + git -C "$TARGET_DIR" reset --hard "origin/$RELEASE" + + # Git tag + elif git -C "$TARGET_DIR" show-ref --verify --quiet "refs/tags/$RELEASE"; then + log "Deploy тега: $RELEASE" + git -C "$TARGET_DIR" checkout --detach "refs/tags/$RELEASE" + + # SHA / локально разрешимый commit-ish + elif git -C "$TARGET_DIR" rev-parse --verify --quiet "$RELEASE^{commit}" >/dev/null; then + log "Deploy commit: $RELEASE" + git -C "$TARGET_DIR" checkout --detach "$RELEASE" + + else + err "Не найдена ветка, тег или commit: $RELEASE" + echo "" + echo "Удалённые ветки:" + git -C "$TARGET_DIR" branch -r || true + echo "" + echo "Теги:" + git -C "$TARGET_DIR" tag -l | tail -50 || true + exit 1 + fi + + # Удаляем только обычные untracked-файлы. Игнорируемые .env/settings не трогаются. + git -C "$TARGET_DIR" clean -fd + + log "Commit: $(git -C "$TARGET_DIR" log -1 --oneline)" +} + +check_project_files() { + local required=("app.py" "requirements.txt" "check_remote_database.py" "hockey_data/__init__.py" "ui_builder/__init__.py" "khl_site/khl_data_center.py") + local f + for f in "${required[@]}"; do + if [[ ! -f "$TARGET_DIR/$f" ]]; then + err "В релизе отсутствует обязательный файл: $f" + exit 1 + fi + done +} + +prepare_env_link() { + # Приложение само читает .env из корня проекта. Оставляем секреты вне Git, + # но делаем ссылку, чтобы одинаково работал systemd и ручной запуск. + if [[ -e "$TARGET_DIR/.env" && ! -L "$TARGET_DIR/.env" ]]; then + warn "$TARGET_DIR/.env уже существует обычным файлом — оставляю его как есть" + else + ln -sfn "$ENV_FILE" "$TARGET_DIR/.env" + fi +} + +prepare_venv() { + if [[ ! -x "$TARGET_DIR/.venv/bin/python" ]]; then + log "Создаю virtualenv" + python3 -m venv "$TARGET_DIR/.venv" + fi + + log "Обновляю Python-зависимости" + "$TARGET_DIR/.venv/bin/python" -m pip install --upgrade pip setuptools wheel + "$TARGET_DIR/.venv/bin/python" -m pip install -r "$TARGET_DIR/requirements.txt" +} + +validate_code() { + log "Проверяю Python-код" + "$TARGET_DIR/.venv/bin/python" -m py_compile \ + "$TARGET_DIR/app.py" \ + "$TARGET_DIR/check_remote_database.py" + + # Проверка БД тем же кодом, который использует проект. + log "Проверяю PostgreSQL" + ( + cd "$TARGET_DIR" + "$TARGET_DIR/.venv/bin/python" "$TARGET_DIR/check_remote_database.py" + ) +} + +write_systemd_service() { + log "Создаю systemd unit: /etc/systemd/system/$SERVICE_NAME" + + cat > "/etc/systemd/system/$SERVICE_NAME" </dev/null +} + +restart_service() { + log "Перезапускаю $SERVICE_NAME" + systemctl restart "$SERVICE_NAME" + + log "Жду HTTP на 127.0.0.1:$PORT" + local ok=0 + for _ in $(seq 1 30); do + if curl -fsS --max-time 2 "http://127.0.0.1:$PORT/api/hockey/database/status" >/tmp/khl-health.json 2>/dev/null; then + ok=1 + break + fi + sleep 1 + done + + if [[ "$ok" -ne 1 ]]; then + err "Приложение не прошло HTTP-проверку" + systemctl status "$SERVICE_NAME" --no-pager -l || true + echo "" + journalctl -u "$SERVICE_NAME" -n 100 --no-pager || true + exit 1 + fi + + log "HTTP OK: $(cat /tmp/khl-health.json)" + rm -f /tmp/khl-health.json +} + +main() { + log "Deploy Hockey: release=$RELEASE" + install_system_dependencies + check_env + checkout_release + check_project_files + + # Не меняем активный venv, пока код ещё не скачан/проверен по структуре. + systemctl stop "$SERVICE_NAME" 2>/dev/null || true + + prepare_env_link + prepare_venv + validate_code + write_systemd_service + restart_service + + echo "" + log "Deploy завершён" + log "Service: $SERVICE_NAME" + log "URL: http://:$PORT/" + log "Status: systemctl status $SERVICE_NAME" + log "Logs: journalctl -u $SERVICE_NAME -f" + log "Restart: systemctl restart $SERVICE_NAME" + echo "" + log "Команда запуска приложения:" + echo " $TARGET_DIR/.venv/bin/python -m uvicorn app:app --host 0.0.0.0 --port $PORT" +} + +main "$@"