- keyset-пагинация аудита с курсором (occurred_at, id) и UI «Показать ещё» - пакетный /api/v1/labs/details и переход progress на один запрос - абсолютный потолок сессии 30 дней (identity V12 + web), очистка refresh - админская инвентаризация legacy-активов (/admin/legacy-assets, скрипт) - E2E в Chromium/Firefox/WebKit (18/18), CI preflight - LDD: 51 claim, 346 тестов, docs и handoff обновлены
11 KiB
Деплой 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 (.gitlab-ci.yml) на каждом push/MR выполняет
./gradlew --no-daemon test ktlintCheck detekt evidenceCheck и job smoke
(./scripts/ldd-smoke.sh: полный стек, health, вход админа, один курс).
Полный job release-evidence (./scripts/ldd-release.sh) обязателен для тегов
и запускается вручную для веток. smoke и release запускают
scripts/ci-preflight.sh, который проверяет Java 21, Docker Compose v2,
Python 3, Node и Playwright. check использует privileged docker:dind
для Testcontainers; smoke/release-evidence выполняются на shell-runner
с Java 21, Docker Compose v2, Python 3, Node и браузерами
Chromium/Firefox/WebKit.
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.