Files
hockey_new/deploy.sh
2026-08-19 15:27:38 +03:00

262 lines
7.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <<USAGE
Использование:
sudo $0 [-r branch|tag|commit]
Примеры:
sudo $0 -r main
sudo $0 -r Barabanov_TEST
sudo $0 -r v1.2.3
USAGE
}
while getopts ":r:h" opt; do
case "$opt" in
r) RELEASE="$OPTARG" ;;
h) usage; exit 0 ;;
:) err "Для -$OPTARG требуется значение"; exit 2 ;;
\?) err "Неизвестный параметр: -$OPTARG"; usage; exit 2 ;;
esac
done
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
err "Запусти deploy от root: sudo $0 -r $RELEASE"
exit 1
fi
install_system_dependencies() {
local missing=()
for cmd in git python3 curl systemctl; do
command -v "$cmd" >/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/<release>
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" <<UNIT
[Unit]
Description=Hockey Stat2TV / UI Builder
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=root
WorkingDirectory=$TARGET_DIR
EnvironmentFile=$ENV_FILE
Environment=PYTHONUNBUFFERED=1
Environment=PYTHONDONTWRITEBYTECODE=1
# Перед стартом убеждаемся, что PostgreSQL доступен.
ExecStartPre=$TARGET_DIR/.venv/bin/python $TARGET_DIR/check_remote_database.py
# ГЛАВНАЯ КОМАНДА ЗАПУСКА ПРОЕКТА:
ExecStart=$TARGET_DIR/.venv/bin/python -m uvicorn app:app --host 0.0.0.0 --port $PORT
Restart=on-failure
RestartSec=5
TimeoutStopSec=20
KillSignal=SIGINT
StandardOutput=journal
StandardError=journal
SyslogIdentifier=KHL
[Install]
WantedBy=multi-user.target
UNIT
chmod 644 "/etc/systemd/system/$SERVICE_NAME"
systemctl daemon-reload
systemctl enable "$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://<SERVER_IP>:$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 "$@"