Files
LMS/deploy/DEPLOY.md
Vladislav Kopnin 1e1e9ae0e4 feat: закрыть остаточный backlog
- 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 обновлены
2026-10-29 12:01:44 +03:00

215 lines
11 KiB
Markdown
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.

# Деплой 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
```bash
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER" # перелогиньтесь, чтобы работать без sudo
docker compose version # нужна версия v2
```
## 2. Получить код
```bash
sudo mkdir -p /opt/lms && sudo chown "$USER" /opt/lms
git clone <URL-репозитория> /opt/lms
cd /opt/lms
```
## 3. Настроить окружение
```bash
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` не коммитьте.
Сроки аутентификации и сессии (можно не задавать — действуют значения ниже):
```dotenv
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. Собрать и запустить
```bash
./deploy/deploy.sh up
```
Скрипт соберёт образы (первый раз ~5–15 минут: скачиваются Gradle и зависимости)
и поднимет контейнеры. Проверка:
```bash
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
```bash
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`](IMPORT.md). Файлы со списками студентов и выгрузками паролей —
персональные данные, держите их вне репозитория.
## 7. Обновление версии
```bash
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`](BACKUP.md). Кратко:
```bash
./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`).
Ежедневное расписание:
```bash
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. Диагностика
```bash
./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.