Files
anti-plagiarism/scripts/deploy.sh
jze9 c152fafa70
All checks were successful
Deploy / test (push) Successful in 3m23s
Deploy / deploy (push) Successful in 13s
feat(deploy): переключить .env на проде на Infisical вместо ручного файла
deploy.sh теперь на каждом запуске логинится в Infisical (Machine Identity
Universal Auth) и генерирует .env из окружения prod перед синком кода и
поднятием контейнеров. Если Infisical недоступен или вернул подозрительно
мало ключей — деплой падает раньше, не трогая рабочий .env на сервере.

Добавлен шаг compose up -d без --build для всех бэкенд-сервисов после
сборки изменившихся — иначе правка секрета без изменения кода не попадала
бы в уже запущенные контейнеры (docker compose пересоздаёт только то, чей
эффективный конфиг реально изменился, остальное не трогает).
2026-08-27 16:25:28 +05:00

155 lines
7.5 KiB
Bash
Executable File
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
# ─────────────────────────────────────────────────────────────────────────────
# Деплой anti-plagiarism (умная пересборка — только изменённых сервисов).
#
# Запускается Gitea Actions runner'ом в host-режиме прямо на app-хосте 1.32
# (см. .gitea/workflows/deploy.yml). Ожидает:
# - cwd = ПОЛНЫЙ чекаут репозитория (нужен git-лог для diff);
# - переменную PVE_PASSWORD (секрет) для доступа к Proxmox-хосту → CT 102;
# - INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET (Machine Identity
# "claude-ai", Universal Auth) — .env на проде теперь генерируется из
# Infisical (prod) на каждом деплое, а не правится руками на сервере.
#
# Пересобирается только то, чей код изменился с прошлого деплоя (маркер SHA в
# $APP/.last_deploy_sha). Это критично: worker-gpu тянет torch/faiss (~2 ГБ),
# его нельзя пересобирать без нужды. Если менялся docker-compose.prod.yml —
# пересобираются все бэкенд-сервисы.
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
APP=/home/user/anti-plagiarism
SRC="$(pwd)"
PVE_HOST=192.168.20.254
COMPOSE="docker compose -f docker-compose.prod.yml"
ALL_BACKEND="api worker-gpu worker-indexer worker-notifier worker-gost"
MARKER="$APP/.last_deploy_sha"
CUR_SHA="$(git -C "$SRC" rev-parse HEAD)"
# ── Определить, что менялось ──────────────────────────────────────────────────
FULL=0
CHANGED=""
if [ -f "$MARKER" ] && PREV="$(cat "$MARKER")" && [ -n "$PREV" ] \
&& git -C "$SRC" cat-file -e "$PREV" 2>/dev/null; then
CHANGED="$(git -C "$SRC" diff --name-only "$PREV" "$CUR_SHA")"
echo "Изменения с $PREV:"
echo "$CHANGED" | sed 's/^/ /'
# Правка общего compose затрагивает все образы
echo "$CHANGED" | grep -qE '^docker-compose\.prod\.yml$' && FULL=1
else
echo "Маркер прошлого деплоя недоступен — полная пересборка (разовая)"
FULL=1
fi
REBUILD=""
FRONTEND_CHANGED=0
if [ "$FULL" = 1 ]; then
REBUILD="$ALL_BACKEND"
FRONTEND_CHANGED=1
else
for svc in $ALL_BACKEND; do
echo "$CHANGED" | grep -qE "^services/$svc/" && REBUILD="$REBUILD $svc"
done
echo "$CHANGED" | grep -qE '^services/frontend/' && FRONTEND_CHANGED=1
fi
REBUILD="$(echo "$REBUILD" | xargs || true)"
echo "==> [1/6] Секреты из Infisical (prod) → .env"
INFISICAL_BIN="$APP/.infisical-cli"
if [ ! -x "$INFISICAL_BIN" ]; then
echo " ставлю infisical CLI (разово)"
curl -sSL -o /tmp/infisical-cli.tar.gz \
"https://github.com/Infisical/infisical/releases/download/infisical-cli%2Fv0.31.1/infisical_0.31.1_linux_amd64.tar.gz"
tar xzf /tmp/infisical-cli.tar.gz -C /tmp infisical
mv /tmp/infisical "$INFISICAL_BIN"
rm -f /tmp/infisical-cli.tar.gz
fi
INFISICAL_DOMAIN=https://infisical.jze9.ru/api
INFISICAL_PROJECT_ID=a2e7505e-573f-43e9-872b-4ed7a6bed7b8
INFISICAL_TOKEN="$("$INFISICAL_BIN" login --method=universal-auth \
--client-id="${INFISICAL_CLIENT_ID:?INFISICAL_CLIENT_ID не задан}" \
--client-secret="${INFISICAL_CLIENT_SECRET:?INFISICAL_CLIENT_SECRET не задан}" \
--domain="$INFISICAL_DOMAIN" --plain --silent)"
TMP_ENV="$(mktemp)"
"$INFISICAL_BIN" export --token="$INFISICAL_TOKEN" --domain="$INFISICAL_DOMAIN" \
--projectId="$INFISICAL_PROJECT_ID" --env=prod --format=dotenv --expand=false \
--silent > "$TMP_ENV"
# Не затирать рабочий .env, если Infisical вернул мало строк (недоступен/пуст) —
# иначе следующий шаг раскатит прод с пустым конфигом.
ENV_LINES="$(grep -cE '^[A-Za-z_][A-Za-z0-9_]*=' "$TMP_ENV" || true)"
if [ "$ENV_LINES" -lt 20 ]; then
echo "ОШИБКА: Infisical export вернул только $ENV_LINES строк (ожидалось ~38) — сервис недоступен или пуст. Деплой прерван, .env на проде не тронут."
rm -f "$TMP_ENV"
exit 1
fi
mv "$TMP_ENV" "$APP/.env"
echo " OK: $ENV_LINES секретов"
echo "==> [2/6] Синхронизация кода в $APP (сохраняя .env и compose)"
rsync -a \
--exclude='__pycache__/' --exclude='*.pyc' --exclude='.pytest_cache/' \
--exclude='node_modules/' --exclude='dist/' \
"$SRC/services" "$SRC/scripts" "$APP/"
cd "$APP"
if [ -n "$REBUILD" ]; then
echo "==> [3/6] Пересборка и перезапуск: $REBUILD"
# shellcheck disable=SC2086
$COMPOSE up -d --build $REBUILD
else
echo "==> [3/6] Бэкенд-сервисы не менялись — пропуск сборки"
fi
# Секреты в .env могли поменяться без изменения кода (правка в Infisical) —
# docker compose сам не пересоздаст контейнер, чей env_file изменился, если
# просто повторно не позвать up -d. Дёшево и идемпотентно: compose пересоздаёт
# только контейнеры с реально изменившимся конфигом, остальные не трогает.
echo "==> [4/6] Применение секретов к неизменившимся сервисам (если менялись)"
# shellcheck disable=SC2086
$COMPOSE up -d $ALL_BACKEND
echo "==> [5/6] Миграции БД (с ретраем)"
for i in $(seq 1 10); do
$COMPOSE exec -T api alembic upgrade head && break
echo " api ещё не готов, повтор $i/10..."; sleep 5
done
if [ "$FRONTEND_CHANGED" = 1 ]; then
echo "==> [6/6] Сборка фронтенда"
docker run --rm -v "$APP/services/frontend":/app -w /app node:20-slim \
sh -c "npm install --no-audit --no-fund --loglevel=error && npm run build"
echo "==> [6/6] Выкладка статики на CT 102 (через $PVE_HOST)"
cd "$APP/services/frontend/dist"
tar czf /tmp/academic-dist.tgz .
export SSHPASS="${PVE_PASSWORD:?PVE_PASSWORD не задан}"
SSHOPT="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
# shellcheck disable=SC2086
sshpass -e scp $SSHOPT /tmp/academic-dist.tgz "root@${PVE_HOST}:/tmp/academic-dist.tgz"
# shellcheck disable=SC2086
sshpass -e ssh $SSHOPT "root@${PVE_HOST}" 'bash -s' <<'REMOTE'
set -e
pct push 102 /tmp/academic-dist.tgz /tmp/academic-dist.tgz
pct exec 102 -- bash -c '
set -e
cd /var/www
cp -a academic "academic.bak.$(date +%s)"
cd academic
rm -rf assets index.html
tar xzf /tmp/academic-dist.tgz
chown -R 1000:1000 .
nginx -t && nginx -s reload
'
rm -f /tmp/academic-dist.tgz
REMOTE
rm -f /tmp/academic-dist.tgz
else
echo "==> [6/6] Фронтенд не менялся — пропуск сборки и выкладки"
fi
echo "$CUR_SHA" > "$MARKER"
echo "==> Деплой завершён успешно (маркер обновлён: $CUR_SHA)"