Files
LMS/deploy/DEPLOY.md
Vladislav Kopnin 9412d87fed fix: закрыть P0/P1 расхождения с целевой моделью
- академический отпуск: readOnly, запрет submit/start/finish защиты
- черновики лабораторных скрыты от студентов в списке/деталях/по UUID
- удалено действие «Убрать из группы» из UI и API
- импорт: restoreInactiveStatuses, skipped-отчёт, разблокировка архивного аккаунта
- глобальные материалы STUDENTS/TEACHERS с JWT-фильтрацией и сменой аудитории
- LDD-AUTH-005 переведён на unit-evidence, clean check проходит по P0
- backup: безопасный парсинг .env, verify-latest, кавычки cron
- robots/noindex, комментарии без автора, профиль без черновиков
- docs/18-goal-audit.md; baseline 57 claims / 366 тестов / release PASS
2026-10-29 18:58:21 +03:00

11 KiB
Raw Blame History

Деплой LMS на сервер lms.our-place.ru

Стек целиком запускается в Docker. Java на сервере не устанавливается — приложение собирается и работает внутри контейнеров (multi-stage Dockerfile: сборка на JDK 21, запуск на JRE 21).

Компоненты:

Сервис Контейнер Наружу
web (Thymeleaf UI) lms-web 127.0.0.1:8090 (проксирует nginx)
gateway lms-gateway нет
identity / academic / progress lms-* нет
PostgreSQL 18 lms-postgres нет

Данные лежат в именованных volume: lms_pgdata (БД), lms_submissions (решения), lms_assets (картинки материалов).


0. Что нужно

  • Сервер с Ubuntu 22.04/24.04 (или другим Linux с Docker), sudo/root.
  • Домен lms.our-place.ru, A-запись которого указывает на IP сервера.
  • Открытые порты 80 и 443 (443 — если настраиваете HTTPS).

1. Установка Docker

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"   # перелогиньтесь, чтобы работать без sudo
docker compose version            # нужна версия v2

2. Получить код

sudo mkdir -p /opt/lms && sudo chown "$USER" /opt/lms
git clone <URL-репозитория> /opt/lms
cd /opt/lms

3. Настроить окружение

cp .env.prod.example .env.prod
nano .env.prod

Обязательно задайте LMS_DB_PASSWORD и LMS_INTERNAL_TOKEN. Остальные значения по умолчанию подходят.

LMS_INTERNAL_TOKEN — общий секрет для внутренних вызовов identity из academic/progress (профиль студента, состав группы). Сгенерируйте длинную случайную строку (например, openssl rand -hex 32) и укажите её в .env.prod. Compose передаёт одно и то же значение identity, academic и progress. Значение по умолчанию dev-internal-token для production не используйте; .env.prod не коммитьте.

Сроки аутентификации и сессии (можно не задавать — действуют значения ниже):

LMS_ACCESS_TOKEN_TTL=15m              # access JWT identity, короткий
LMS_REFRESH_TOKEN_TTL=7d              # refresh-токен, ротация и отзыв
LMS_SESSION_TIMEOUT=7d                # web-сессия LMS_SESSION, sliding
LMS_SESSION_ABSOLUTE_TIMEOUT=30d      # жёсткий потолок сессии
LMS_REFRESH_TOKEN_RETENTION=30d       # сколько хранить истёкшие refresh
LMS_REFRESH_TOKEN_CLEANUP_CRON='0 30 3 * * *'

LMS_ACCESS_TOKEN_TTL, LMS_REFRESH_TOKEN_TTL, LMS_SESSION_ABSOLUTE_TIMEOUT, LMS_REFRESH_TOKEN_RETENTION и LMS_REFRESH_TOKEN_CLEANUP_CRON читает identity; LMS_SESSION_TIMEOUT и LMS_SESSION_ABSOLUTE_TIMEOUT — web. docker-compose.prod.yml прокидывает эти переменные в environment соответствующих сервисов; при переопределении используются значения из .env.prod. Access обновляется автоматически за 2 минуты до истечения; прямой API-клиент должен сам вызывать POST /api/v1/auth/refresh. Уменьшайте значения только осознанно: слишком короткая web-сессия заставит пользователей чаще входить заново.

4. Собрать и запустить

./deploy/deploy.sh up

Скрипт соберёт образы (первый раз ~5–15 минут: скачиваются Gradle и зависимости) и поднимет контейнеры. Проверка:

docker compose -f docker-compose.prod.yml --env-file .env.prod ps
curl -fsS http://127.0.0.1:8090/actuator/health   # {"status":"UP",...}

5. Nginx + HTTPS

sudo apt-get update && sudo apt-get install -y nginx certbot python3-certbot-nginx
sudo mkdir -p /var/www/certbot

sudo cp deploy/nginx/lms.our-place.ru.conf /etc/nginx/sites-available/lms.our-place.ru.conf
sudo ln -sf /etc/nginx/sites-available/lms.our-place.ru.conf /etc/nginx/sites-enabled/lms.our-place.ru.conf
sudo rm -f /etc/nginx/sites-enabled/default

sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d lms.our-place.ru --redirect -m admin@our-place.ru --agree-tos

certbot --nginx сам добавит TLS-блок и редирект с HTTP на HTTPS. Автопродление уже настроено пакетом (systemctl status certbot.timer).

6. Первый запуск приложения

Откройте https://lms.our-place.ru → откроется /setup → создайте администратора.

6a. Наполнение данными

Дальше всё через интерфейс: импорт групп (.xls), импорт лабораторной ZIP'ом, раздача вариантов, оценки и защита, материалы. Подробный порядок с проверками — в IMPORT.md. Файлы со списками студентов и выгрузками паролей — персональные данные, держите их вне репозитория.

7. Обновление версии

cd /opt/lms
git pull
./deploy/deploy.sh up        # backup → сборка → миграции → перезапуск

deploy.sh up обязательно делает backup перед обновлением. Если backup завершился ошибкой, деплой прерывается. Миграции БД (Flyway) применяются автоматически при старте сервисов: identity V4–V13 (V10 создаёт identity.audit_events, V11 — identity.refresh_tokens, V12 — absolute_expires_at, V13 — keyset-индексы аудита), academic V6–V12, progress V13.

После обновления проверьте, что админ открывает /admin/audit и видит события (например, после входа в профиль студента — PROFILE_VIEW); преподаватель и студент на эту страницу не попадают. Журнал листается keyset-курсором (cursor/limit, «Показать ещё»; offset page остаётся для совместимости). Для инвентаризации старых файлов админ открывает /admin/legacy-assets, вызывает GET /api/v1/content/legacy-assets или запускает scripts/legacy-assets.sh: файлы без привязки к статье больше не публичны (404 для обычных пользователей), их нужно привязать или удалить.

CI (.woodpecker/, Gitea + Woodpecker 3.x) на каждом push/MR выполняет workflow check (./gradlew --no-daemon test ktlintCheck detekt evidenceCheck) и smoke (./scripts/ldd-smoke.sh: полный стек, health, вход админа, один курс). Полный workflow release (./scripts/ldd-release.sh) обязателен для тегов и запускается вручную для веток. Smoke/release начинаются с scripts/ci-preflight.sh, который проверяет Java 21, Docker Compose v2, Python 3, Node и Playwright. Агент — Docker backend на отдельной CI VM, шаги — в nested dind без хост-Docker сокета; установка и модель изоляции — WOODPECKER.md.

8. Резервное копирование

Полная инструкция — BACKUP.md. Кратко:

./deploy/backup.sh backup          # БД + решения + материалы
./deploy/backup.sh list
./deploy/backup.sh verify <dir>
./deploy/backup.sh restore <dir>   # destructive, требует ввода RESTORE

Backup включает pg_dump БД, volume решений и volume материалов, контрольные суммы и manifest. По умолчанию хранится 14 дней (LMS_BACKUP_KEEP_DAYS), каталог — /opt/lms/backups (LMS_BACKUP_DIR).

Ежедневное расписание:

sudo cp deploy/systemd/lms-backup.service deploy/systemd/lms-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now lms-backup.timer

Backup на том же сервере — не единственная защита: настройте выгрузку на отдельный хост и периодически проверяйте восстановление на staging.

9. Диагностика

./deploy/deploy.sh ps
./deploy/deploy.sh logs web
./deploy/deploy.sh logs gateway
./deploy/deploy.sh logs identity

Типичные проблемы:

  • web отдаёт 502 через nginx — контейнер web не поднялся: смотрите logs web. Часто причина — занят порт 8090 или не задан LMS_DB_PASSWORD (compose не стартует).
  • Страница входа открывается, но запросы падают — проверьте logs gateway и logs identity; gateway должен получить JWKS от identity.
  • 401 на страницах после входа (Docker) — у backend-сервисов не задан адрес JWKS: должно быть LMS_JWT_JWK_SET_URI=http://identity:8081/api/v1/auth/jwks.json (уже прописано в docker-compose.prod.yml для academic/progress/gateway). Внутри контейнера localhost:8081 недоступен, поэтому токен не проверяется.
  • 403/502 в профиле студента или составе группы — проверьте, что LMS_INTERNAL_TOKEN задан в .env.prod и одинаков у identity, academic и progress. При неверном внутреннем токене identity отвечает 403.
  • Не проходят загрузки больших ZIP — увеличьте client_max_body_size в nginx (сейчас 120 МБ) и spring.servlet.multipart.max-request-size.

Замечания по безопасности

  • PostgreSQL наружу не публикуется; связь между контейнерами — по внутренней сети lms.
  • Публикуется только 127.0.0.1:8090, доступ снаружи — исключительно через nginx/TLS.
  • .env.prod содержит пароль БД и не коммитится (в .gitignore).
  • Пароль администратора задаётся вами на шаге 6; хранение — BCrypt.