Files
security-alert-center/deploy/sac-deploy.sh
T
PapaTramp 91bd6afd1f fix: start sac-api via /usr/bin/bash to avoid systemd 203/EXEC
ExecStart no longer relies on shebang when CRLF slips into deploy scripts.
sac-deploy strips CR from all deploy/*.sh and waits for active before health check.
2026-07-13 17:29:28 +10:00

199 lines
7.2 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
# Полное обновление 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 "Проверка ${CONFIG_FILE} (pydantic; без bash source — inline-комментарии в значениях ломают deploy)"
sudo -u "${APP_USER}" bash -c "
export SAC_CONFIG_FILE='${CONFIG_FILE}'
cd '${APP_ROOT}/backend'
'${VENV}/bin/python' -c 'from app.config import get_settings; get_settings(); print(\"config: OK\")'
" || die "Неверный ${CONFIG_FILE}: одна переменная = одна строка, комментарии только отдельной строкой с # (не «false ← …» после значения)"
log "alembic upgrade head"
sudo -u "${APP_USER}" bash -c "
export SAC_CONFIG_FILE='${CONFIG_FILE}'
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
START_SH="${APP_ROOT}/deploy/systemd/sac-api-start.sh"
if [ -f "${START_SH}" ]; then
sed -i 's/\r$//' "${START_SH}"
chmod 755 "${START_SH}"
fi
for _sh in "${APP_ROOT}"/deploy/sac-deploy.sh "${APP_ROOT}"/deploy/systemd/*.sh; do
[ -f "${_sh}" ] || continue
sed -i 's/\r$//' "${_sh}"
chmod 755 "${_sh}" 2>/dev/null || true
done
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 с)"
log "Сброс зависших remote_action (running без worker после restart)"
sudo -u "${APP_USER}" bash -c "
export SAC_CONFIG_FILE='${CONFIG_FILE}'
cd '${APP_ROOT}/backend'
'${VENV}/bin/python' -c \"
from app.database import SessionLocal
from app.services.host_remote_actions import clear_stale_running_remote_actions
session = SessionLocal()
try:
names = clear_stale_running_remote_actions(session)
for name in names:
print(f'stale remote_action reset: {name}')
finally:
session.close()
\"
"
systemctl restart "${SERVICE_NAME}"
sleep 2
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
if systemctl is-active --quiet "${SERVICE_NAME}"; then
break
fi
sleep 1
done
systemctl is-active --quiet "${SERVICE_NAME}" || die "${SERVICE_NAME} не active — journalctl -u ${SERVICE_NAME} -n 40 --no-pager"
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 "Готово."