#!/usr/bin/env bash # Полное обновление SAC на сервере (native). # Установка: sudo cp /opt/security-alert-center/deploy/sac-deploy.sh /opt/sac-deploy.sh # sudo chmod 755 /opt/sac-deploy.sh # Запуск: sudo /opt/sac-deploy.sh # # Выполняет от пользователя sac: git pull, pip, alembic, npm build. # От root: systemctl restart sac-api. set -euo pipefail APP_ROOT="${SAC_APP_ROOT:-/opt/security-alert-center}" APP_USER="${SAC_APP_USER:-sac}" SERVICE_NAME="${SAC_SERVICE:-sac-api}" CONFIG_FILE="${SAC_CONFIG_FILE:-${APP_ROOT}/config/sac-api.env}" log() { printf '[sac-deploy] %s\n' "$*"; } die() { printf '[sac-deploy] ERROR: %s\n' "$*" >&2; exit 1; } if [ "$(id -u)" -ne 0 ]; then die "Запускайте от root: sudo /opt/sac-deploy.sh" fi id "$APP_USER" &>/dev/null || die "Пользователь ${APP_USER} не существует" if [ ! -d "${APP_ROOT}/.git" ]; then die "Не найден git-репозиторий: ${APP_ROOT}" fi log "Выравнивание владельца ${APP_ROOT} → ${APP_USER}:${APP_USER}" chown -R "${APP_USER}:${APP_USER}" "${APP_ROOT}" log "git fetch + sync" sudo -u "${APP_USER}" git -C "${APP_ROOT}" fetch --prune if sudo -u "${APP_USER}" git -C "${APP_ROOT}" pull --ff-only 2>/dev/null; then log "git: fast-forward OK" else UPSTREAM="$(sudo -u "${APP_USER}" git -C "${APP_ROOT}" rev-parse --abbrev-ref '@{u}' 2>/dev/null || true)" if [ -z "${UPSTREAM}" ]; then UPSTREAM="origin/main" fi log "WARN: fast-forward невозможен (часто после force-push на kalinamall) — reset --hard ${UPSTREAM}" sudo -u "${APP_USER}" git -C "${APP_ROOT}" reset --hard "${UPSTREAM}" fi if [ ! -f "${CONFIG_FILE}" ]; then die "Нет конфига: ${CONFIG_FILE}" fi VENV="${APP_ROOT}/backend/.venv" if [ ! -x "${VENV}/bin/pip" ]; then die "Нет venv: ${VENV} — сначала установите по install-ubuntu-24.04-native.md" fi log "pip install -r requirements.txt" sudo -u "${APP_USER}" "${VENV}/bin/pip" install -q -r "${APP_ROOT}/backend/requirements.txt" log "alembic upgrade head" sudo -u "${APP_USER}" bash -c " set -a # shellcheck source=/dev/null source '${CONFIG_FILE}' set +a cd '${APP_ROOT}/backend' && '${VENV}/bin/alembic' upgrade head " if [ -f "${APP_ROOT}/frontend/package.json" ]; then if ! command -v npm >/dev/null 2>&1; then log "npm не найден — пропуск сборки UI (apt install nodejs npm)" else log "frontend: npm ci / build" sudo -u "${APP_USER}" bash -c " cd '${APP_ROOT}/frontend' if [ -f package-lock.json ]; then npm ci; else npm install; fi npm run build " if [ ! -f "${APP_ROOT}/frontend/dist/index.html" ]; then die "Сборка UI не создала frontend/dist/index.html" fi fi fi if [ -f "${APP_ROOT}/deploy/systemd/${SERVICE_NAME}.service" ]; then SYSTEMD_DST="/etc/systemd/system/${SERVICE_NAME}.service" if [ ! -f "${SYSTEMD_DST}" ] || ! cmp -s "${APP_ROOT}/deploy/systemd/${SERVICE_NAME}.service" "${SYSTEMD_DST}"; then log "Обновление systemd unit → ${SYSTEMD_DST}" cp "${APP_ROOT}/deploy/systemd/${SERVICE_NAME}.service" "${SYSTEMD_DST}" systemctl daemon-reload fi fi log "Проверка DATABASE_URL (как у uvicorn через SAC_CONFIG_FILE)" sudo -u "${APP_USER}" bash -c " export SAC_CONFIG_FILE='${CONFIG_FILE}' cd '${APP_ROOT}/backend' '${VENV}/bin/python' -c \" from sqlalchemy import create_engine, text from app.config import get_settings engine = create_engine(get_settings().database_url, pool_pre_ping=True) with engine.connect() as conn: conn.execute(text('SELECT 1')) print('db: OK') \" " || die "PostgreSQL: проверьте DATABASE_URL в ${CONFIG_FILE} и systemctl status postgresql" log "systemctl restart ${SERVICE_NAME} (краткий 502 в UI возможен ~10 с)" systemctl restart "${SERVICE_NAME}" systemctl is-active --quiet "${SERVICE_NAME}" || die "${SERVICE_NAME} не active" HEALTH_OK=0 for _ in 1 2 3 4 5 6; do sleep 2 if curl -sf "http://127.0.0.1:8000/health" >/dev/null 2>&1; then HEALTH_OK=1 break fi done if [ "${HEALTH_OK}" -eq 1 ]; then log "health: OK (http://127.0.0.1:8000/health)" else die "${SERVICE_NAME} запущен, но /health не ответил — journalctl -u ${SERVICE_NAME} -n 80 --no-pager" fi RENDER_DAILY_TIMER="${APP_ROOT}/deploy/systemd/render-sac-daily-report-timer.sh" if [ -f "${RENDER_DAILY_TIMER}" ]; then chmod +x "${RENDER_DAILY_TIMER}" 2>/dev/null || true fi for aux in sac-retention sac-daily-report; do for suffix in service timer; do src="${APP_ROOT}/deploy/systemd/${aux}.${suffix}" dst="/etc/systemd/system/${aux}.${suffix}" if [ "${aux}" = "sac-daily-report" ] && [ "${suffix}" = "timer" ]; then if [ -x "${RENDER_DAILY_TIMER}" ]; then log "Генерация ${dst} из ${CONFIG_FILE} (SAC_DAILY_REPORT_HOUR/TIMEZONE)" bash "${RENDER_DAILY_TIMER}" "${CONFIG_FILE}" "${dst}" systemctl daemon-reload systemctl restart "${aux}.timer" 2>/dev/null || systemctl start "${aux}.timer" 2>/dev/null || true elif [ -f "${src}" ]; then log "WARN: ${RENDER_DAILY_TIMER} не найден — копируем статический timer" cp "${src}" "${dst}" systemctl daemon-reload fi continue fi if [ -f "${src}" ]; then if [ ! -f "${dst}" ] || ! cmp -s "${src}" "${dst}"; then log "Обновление ${dst}" cp "${src}" "${dst}" systemctl daemon-reload fi fi done if [ -f "/etc/systemd/system/${aux}.timer" ]; then systemctl enable "${aux}.timer" 2>/dev/null || true systemctl start "${aux}.timer" 2>/dev/null || true fi done log "Готово."