Сверка
Проверка доступности литературы пяти образовательных программ НИУ ВШЭ. Целевой адрес: https://miem.space/courses. Авторизация не требуется.
Запуск
Требуется Node.js24 и pnpm11.25.0.
pnpm install --frozen-lockfile
TEST_DATABASE_URL=<изолированная-локальная-БД-sverka_test> pnpm test
pnpm typecheck
pnpm dev
Открыть http://127.0.0.1:5173/courses. Для сервера требуется DATABASE_URL с подключением к отдельной базе PostgreSQL. Там хранятся результаты, очередь и расписание; новое пустое хранилище инициализируется при первом подключении. Пароль передаётся только через окружение или защищённый файл окружения вне Git. COURSES_ORIGIN задаёт допустимый Origin для POST за reverse proxy, в production — https://miem.space.
Ручная проверка выполняется порциями, пока вкладка открыта. После её закрытия или перезапуска сервера кнопка «Продолжить» использует сохранённую очередь. Глобальная аренда не допускает одновременную обработку одной порции. Каждая порция сохраняет результаты и очередь в одной транзакции PostgreSQL; при ошибке записи задания остаются в очереди.
Страницы курсов загружаются заново, но литература неизменившихся курсов берётся из кэша вместе с исходной датой проверки. Полная проверка без кэша нужна для проверки изменений на внешних сайтах. HTTP200 не доказывает доступ к полному тексту; JavaScript-заглушки, CAPTCHA и признаки подписки отмечаются отдельно. Литература только во вложении отмечается как неполный охват.
Частота запросов
Материалы проверяются последовательно. Между запросами к одному хосту выдерживается 3–4 секунды, включая каталоги, курсы и перенаправления. Ограничение общее для API и worker и сохраняется в PostgreSQL. После HTTP429/503 действует пауза из Retry-After (не менее минуты), после HTTP403 — пять минут. Повторных попыток в цикле нет. Ссылки, пропущенные во время паузы, отмечаются как ограниченные с датой окончания паузы; это не подтверждает, что материал удалён. Их можно обновить последующей полной проверкой. Общий таймаут загрузки с перенаправлениями — 30 секунд, включая паузы. Паузы снижают нагрузку, но не гарантируют отсутствие блокировки со стороны сайта.
Расписание
В интерфейсе откройте «Проверка по расписанию»: включите автоматический запуск, выберите ежедневное или еженедельное повторение, время по Москве, при необходимости день недели и режим кэша. Сохранение и отключение доступны там же. По умолчанию расписание выключено; оно общее для всех посетителей и хранится в той же базе PostgreSQL.
pnpm dev, pnpm start и Docker запускают веб-сервер и отдельный фоновый процесс одним управляемым runtime. Проверка по расписанию выполняется без открытой вкладки, при перезапуске продолжается по сохранённой очереди. Расписание проверяется раз в пять секунд. Ручные проверки сохраняют прежний режим с открытой вкладкой.
Если любая предыдущая проверка ещё имеет статус running, совпавший слот пропускается с отметкой в интерфейсе: параллельных запусков нет. После простоя сервера выполняется один просроченный запуск; пропущенные дни не образуют очередь. Изменение или отключение расписания не прерывает уже начатую автоматическую проверку. Если один из двух процессов runtime завершится, второй останавливается; restart policy Docker поднимает их вместе.
Production
docker compose build
docker compose up -d
Эти команды изменяют production: выполнять только через разрешённый deployment-процесс после независимого review. Сервис слушает только 127.0.0.1:3011, работает от UID1000 и использует существующий сервер PostgreSQL через внешнюю Docker-сеть miem_workers_default. Отдельный контейнер БД или SQLite volume не создаётся. Healthcheck: GET /courses/api/health. Порт3011 — предлагаемый; перед запуском проверить, что он свободен. Код не зависит от Cloudflare Workers, D1 или Sites hosting.
Добавить deploy/nginx-courses.conf в существующий TLS-server miem.space, проверить nginx -t, затем выполнить разрешённый reload. Конфиг передаёт исходный URI без удаления /courses; /courses-other не захватывается. Фактический путь Nginx-конфига должен быть подтверждён read-only preflight, а не угадан.
Порядок первого deployment
- Подтвердить актуальные branch/head, независимое review, тесты, сборку, Docker daemon/диск, свободный порт и отсутствие существующей локации
/courses. - Сохранить текущую конфигурацию Nginx. Использовать отдельную базу
miem_courses_linksи отдельную LOGIN-роль без SUPERUSER/CREATEDB/CREATEROLE. Не использовать учётную запись и таблицы существующих приложений. Перед изменением уже заполненной базы сделать pg_dump и проверить возможность восстановления; на первом размещении новая база пустая. - Собрать exact commit, запустить только новый сервис. Проверить localhost healthcheck и
/courses, статические файлы и шрифты. - Включить Nginx location, выполнить syntax check/reload через разрешённый канал. Проверить HTTPS страницу, healthcheck и ручной шаг проверки, затем перезапуск сервиса с сохранением данных.
- При неудаче вернуть прежний Nginx-конфиг, проверить и reload; остановить только новый сервис. Базу и роль не удалять. При обновлении вернуть предыдущий совместимый image tag, сохранив PostgreSQL. Не применять
compose down -vили общий prune.
Обновление Nginx обычно не требует остановки существующих сервисов; фактический риск и разрешённый способ reload определяются инфраструктурным runbook. DATABASE_URL передаётся Docker Compose из окружения либо через защищённый deployment env-file; его содержимое не публикуется и не коммитится. В этом checkout production deployment не выполнен.
Проверки
TEST_DATABASE_URL должен вести только на локальный sverka_test; DB-тесты создают и удаляют собственные случайные схемы и отказываются работать с другой базой. pnpm test проверяет извлечение ссылок, классификацию, URL со скобками, запрет непубличных IP, атомарный rollback/replay очереди, единственный общий запуск, lease, создание/изменение/отключение расписания, московское время и переходы календаря, однократный claim, skip пересекающихся запусков и выполнение worker без браузера. Node HTTP-загрузчик закрепляет проверенный DNS-адрес в сокете и проверяет каждый redirect; TLS проверяет исходное имя хоста. Прокси из environment не используются загрузчиком.