Основной путь — Docker, см. deploy-my-olymp.md. Этот вариант остаётся для машины, где Docker не ставится: например, Windows Server на VPS без вложенной виртуализации. Там Linux-контейнеры не запускаются вовсе, и всё, что делал compose, приходится задать руками.
Шаги 0, 4 и 5 основной инструкции — домен, подписка на события и адрес
в кабинете MAX — те же самые, только команды без docker compose.
Пропущенное здесь проявляется не при старте, а на первом запросе.
Рабочий каталог решает. Файл .env ищется относительно каталога, из
которого запущен процесс, а не рядом с кодом. Запускаете из C:\Server —
файл должен лежать в C:\Server\.env. С версии, где появился этот раздел,
приложение само пишет в лог полный путь, по которому искало.
Проверить, что файл вообще есть и назван правильно:
dir /a C:\Server\.env
dir без /a скрытые файлы не показывает. Если видно .env.txt — это и
есть причина: «Блокнот» дописывает расширение, а такой файл не читается.
Переименовать можно так:
ren C:\Server\.env.txt .env
Не создавайте файл через > в PowerShell. ... > .env и Out-File
в Windows PowerShell 5.1 пишут UTF-16, и приложение падает на старте с
UnicodeDecodeError. Надёжнее из cmd:
cd /d C:\Server
(echo BOT_TOKEN=ваш_токен)>.env
Всё остальное файл переживает: переводы строк CRLF, BOM, кавычки вокруг
значения и пробелы вокруг = разбираются правильно. Пути Windows можно
писать как есть: EXTRA_CA_CERTS_DIR=C:\Server\certs.
Что задать помимо токена. Значения по умолчанию рассчитаны на Docker и вне его не работают:
| Переменная | Значение по умолчанию | Почему не подойдёт |
|---|---|---|
DATABASE_URL |
postgresql+asyncpg://app:app@db:5432/app |
хост db существует только в сети compose; нужен адрес вашей Postgres — см. ниже |
EXTRA_CA_CERTS_DIR |
certs |
подойдёт как есть, если каталог certs\ лежит рядом с рабочим |
APP_ENV |
development |
на публичном сервере открывает /docs и /openapi.json |
Без первой переменной API отвечает ошибкой подключения к базе, без второй
запросы к platform-api2.max.ru падают на TLS.
Postgres нужна настоящая, SQLite не подойдёт. В моделях каталога
используется тип JSONB, он есть только в Postgres; миграции написаны под
неё же. Порядок на Windows:
Поставить PostgreSQL для Windows. В compose стоит 16
(postgres:16-alpine), но 17 работает так же — версия важна только
тем, что от неё зависит путь к утилитам. В окне выбора компонентов
нужны два:
| Компонент | Нужен | Зачем |
|---|---|---|
| PostgreSQL Server | да | собственно база |
| Command Line Tools | да | psql, createdb — ими делаются шаги ниже |
| pgAdmin 4 | по желанию | графический клиент, ~500 МБ; для запуска не нужен |
| Stack Builder | нет | загрузчик расширений вроде PostGIS, ничего из него не требуется |
В мастере: каталоги оставить по умолчанию, пароль пользователю
postgres — только буквы и цифры (@, :, /, ? пришлось бы
кодировать в строке подключения). На последнем шаге снять галочку
запуска Stack Builder.
Запомните порт. Обычно 5432, но если он занят — например, уже стоит
другая версия Postgres, — установщик молча предложит 5433. Этот номер
дальше нужен и в командах createdb/psql (ключ -p), и в
DATABASE_URL. Посмотреть, кто занимает порты:
Get-NetTCPConnection -LocalPort 5432,5433 -State Listen | Select-Object LocalPort, OwningProcess
Добавить утилиты Postgres в PATH текущей сессии — тогда их можно
вызывать по имени, и команды ниже одинаковы в PowerShell и в cmd.
Номер версии подставьте свой, по имени созданной папки:
$env:Path += ";C:\Program Files\PostgreSQL\17\bin"
Без этого в PowerShell команда, начинающаяся с кавычки, не запустится:
он примет её за строку и ответит «Непредвиденная лексема». Вызвать
файл по полному пути там можно только через оператор &:
& "C:\Program Files\...\createdb.exe" -U postgres ....
Создать базу — обязательно с явной кодировкой UTF-8:
createdb -U postgres -E UTF8 -T template0 --locale-provider=icu --icu-locale=ru-RU max_olymp
Спросит пароль пользователя postgres — тот, что задали при установке.
На русской Windows кластер создаётся в системной локали, и без этих
ключей база получилась бы в WIN1251 — русские названия олимпиад в ней
поехали бы. --icu-locale=ru-RU заодно даёт правильную сортировку: в
каталоге ORDER BY name выполняет сама база.
Если сборка без ICU и команда откажет, сработает вариант попроще, с сортировкой по байтам (кириллица упорядочится верно, но заглавные окажутся впереди строчных):
createdb -U postgres -E UTF8 -T template0 --locale=C max_olymp
Проверить, что вышло:
psql -U postgres -l
В строке max_olymp должно стоять UTF8. Если там WIN1251 — базу
нужно удалить (dropdb -U postgres max_olymp) и создать заново, пока
каталог в неё не импортирован.
Прописать в .env:
DATABASE_URL=postgresql+asyncpg://postgres:пароль@localhost:5432/max_olymp
Порт в строке — тот, что выбрал установщик. Если 5433, то и здесь 5433.
POSTGRES_PORT для этого не годится: её читает только
docker-compose, чтобы опубликовать порт контейнера на хост. Приложение
её не смотрит вовсе — адрес базы оно берёт целиком из DATABASE_URL.
Если в пароле есть @, :, / или ?, их нужно закодировать
(@ → %40): иначе строка разберётся неправильно. Проще задать пароль
из букв и цифр.
Проверить, что приложение читает именно то, что нужно, можно по логу старта: он печатает строку подключения целиком, с портом и без пароля.
Создать схему и наполнить каталог, из C:\Server:
alembic upgrade head
python -m scripts.import_subjects
python -m scripts.import_schedule
python -m scripts.import_catalog
Без import_schedule у олимпиад каталога нет дат, и календарь пуст.
Связь с базой проверяется при старте: в логе будет либо «База … отвечает», либо причина отказа. Пароль в лог не попадает.
Сертификаты кладите в certs\ рядом с рабочим каталогом — то есть
C:\Server\certs\, а не C:\Server\app\certs\. Годятся только файлы с
расширением .crt, .pem и .cer; связку .p7b с Госуслуг нужно сначала
разобрать на отдельные сертификаты. Лог старта перечисляет, что подхватил,
а если не подхватил ничего — пишет полный путь, где искал.
Статику приложение отдаёт само — если найдёт. Положите каталог
frontend/ рядом с рабочим каталогом, из которого запускаете:
C:\Server\
├── app\ код приложения
├── frontend\ файлы мини-приложения
└── .env
Тогда GET / вернёт index.html, и в MAX приложение откроется. Каталог
ищется по FRONTEND_DIR (по умолчанию frontend, путь от рабочего
каталога). В лог старта пишется, нашёлся он или нет.
Если каталога нет, раздача выключается и корень отвечает 404 — это
нормально для Docker, где статику отдаёт nginx, но при запуске без него
означает, что мини-приложение не откроется вовсе.
Отдаётся с Cache-Control: no-cache: встроенный браузер MAX иначе держит
старую версию и после выкладки.
Быстрая проверка, что токен доехал — не заглядывая в файл:
curl https://my-olymp.ru/health
Поле bot_configured должно быть true. Если false — процесс токена не
видит, и смотреть надо в лог старта: там теперь написано, какой именно
файл искали и нашли ли.
Чтобы отделить «токен неверный» от «файл не читается», задайте переменную прямо в сессии и перезапустите — переменная окружения имеет приоритет над файлом:
set BOT_TOKEN=<токен>
uvicorn app.main:app --host 127.0.0.1 --port 8000
Предупреждение про BOT_TOKEN пропало — значит дело было в файле.
Всё описанное выше поднимает бэкенд, и на этом останавливается. А MAX доставляет события только по https на 443 и не принимает самоподписанные сертификаты, так что без TLS мини-приложение не откроется, а бот не получит ни одного события.
В Docker этим занимались nginx и certbot — оба контейнерами. Без Docker их надо чем-то заменить, и проще всего Caddy: одна программа вместо двух, сертификат Let’s Encrypt выпускается при первом запуске и продлевается сам, без webroot и заданий в планировщике.
Готовая конфигурация лежит в deploy/Caddyfile —
она повторяет то, что делает наш nginx: статика мини-приложения, прокси
на /api/*, /webhook, /health и схему API, перенаправление с www,
заголовки безопасности и запрет кэширования статики.
Что нужно до запуска:
Get-Service W3SVC;127.0.0.1:8000 и наружу не смотрит.Порядок:
caddy.exe с caddyserver.com/download
и положить, например, в C:\Caddy.deploy/Caddyfile.root * — он должен вести
на каталог frontend вашего клона, — и почту в email.Запустить:
cd C:\Caddy
.\caddy.exe run --config Caddyfile
При первом запуске Caddy сходит в Let’s Encrypt за сертификатом; в
выводе появятся строки про certificate obtained successfully. После
этого https://my-olymp.ru/health должен отвечать тем же, чем и
http://127.0.0.1:8000/health.
Перенаправление с http на https Caddy включает сам — отдельный серверный блок на 80, как в nginx, писать не нужно.
Заголовки в блоке handle дополняют заголовки уровня сайта, а не
заменяют их: в отличие от nginx, повторять Strict-Transport-Security
и X-Content-Type-Options во вложенном блоке не требуется.
caddy run держит окно занятым и умирает вместе с сеансом RDP. Для
постоянной работы Caddy нужно запускать службой — через обёртку вроде
NSSM или заданием планировщика «При запуске компьютера».
cd C:\MAX-olymp
git pull
cd backend
pip install -r requirements.txt
alembic upgrade head
и перезапустить бэкенд. alembic upgrade head здесь обязателен: в Docker
миграции применяет команда запуска контейнера, а uvicorn, запущенный
руками, этого не делает. Если шаг пропустить, новые таблицы не появятся —
бэкенд при этом поднимется и про утренние напоминания скажет одной
строкой в логе, что они отключены и почему.
Если git pull принёс новый data\olympiads_schedule.json, следом
выполните python -m scripts.import_schedule — импорт обновляет этапы на
месте, прогресс пользователей не теряется.
Проверить напоминания после обновления, не дожидаясь утра:
python -m scripts.send_reminders --users
python -m scripts.send_reminders --dry-run
При старте бэкенд сам спрашивает у MAX, оформлена ли подписка на события,
и пишет об этом строку в лог. «MAX не присылает события на …» значит, что
бот глух: не узнает о запуске, не ответит на сообщения, сводки уходить
будет некому. Лечится шагом 4 — те же команды, только без docker compose:
python -m scripts.setup_webhook # оформить подписку на my-olymp.ru
python -m scripts.setup_webhook --list # проверить; туннельные адреса снять --delete --url
Кто запустил бота, пока подписки не было, для сервера остаётся «без
событий»: bot_started приходит один раз. Такому человеку достаточно
написать боту любое сообщение — после этого --users покажет «запущен».
Снимок состояния — powershell -ExecutionPolicy Bypass -File deploy\diagnose.ps1,
подробнее в основной инструкции.
Симптомы, которые бывают только без Docker:
| Симптом | Где смотреть |
|---|---|
BOT_TOKEN не задан при живом .env |
процесс запущен не из той папки или файл назван .env.txt; лог старта печатает путь, по которому искали |
404 на / при работающем /api |
рядом с рабочим каталогом нет frontend/; лог старта пишет, где искали |
В MAX вместо приложения 404 |
то же самое: корень домена не отдаёт index.html |
500 на /api/*, в трейсбеке getaddrinfo failed |
DATABASE_URL ведёт на хост db из Docker; лог старта пишет это отдельной строкой |
ConnectionRefusedError вместо getaddrinfo |
хост верный, а порт нет: установщик мог занять 5433; сверьте с логом старта |
| «Утренние напоминания отключены: в базе нет таблицы reminder_log» | после git pull не выполнен alembic upgrade head; выполнить и перезапустить бэкенд |
Календарь пуст, в ответе /api/me/calendar нет поля olympiads |
после git pull работает старый бэкенд: остановить uvicorn и запустить заново |
Календарь пуст, olympiads есть, но пустые при сохранённых олимпиадах |
в базу не загружено расписание: python -m scripts.import_schedule. Если уже загружено — у выбранных олимпиад на olimpiada.ru нет дат (так у 85 из 146) |