Добавлен установщик
This commit is contained in:
545
README.md
545
README.md
@@ -1,22 +1,19 @@
|
||||
# Расписание
|
||||
|
||||
Публичное расписание учебных групп факультета: источник данных — Яндекс.Таблица
|
||||
университета (файл xlsx), который скачивается, разбирается, превращается в JSON
|
||||
API и отображается на фронте.
|
||||
Публичное расписание учебных групп. Источник данных — Яндекс.Таблица (xlsx на
|
||||
Яндекс.Диске): файл скачивается по публичной ссылке, разбирается без сторонних
|
||||
библиотек и отдаётся как JSON API и как сайт, который можно «установить» на
|
||||
главный экран телефона (PWA).
|
||||
|
||||
## Быстрый старт
|
||||
Умеет:
|
||||
|
||||
Нужны Python 3.11+, bash (Linux, macOS или WSL на Windows) и публичная ссылка
|
||||
на таблицу.
|
||||
|
||||
```bash
|
||||
python3 --version # должен быть 3.11 или новее
|
||||
cp .env.example .env # в SCHEDULE_LINK вписать публичную ссылку Я.Диска
|
||||
make run # создаст .venv, поставит зависимости и запустит оба сервиса
|
||||
```
|
||||
|
||||
Открыть <http://127.0.0.1:8080>. Остановить — `Ctrl+C`.
|
||||
Подробнее — разделы «Зависимости» и «Запуск» ниже.
|
||||
- расписание по группам, преподавателям и аудиториям с разделением
|
||||
числитель/знаменатель и бейджем текущей недели;
|
||||
- занятость всех аудиторий по дням и поиск свободных на конкретную пару;
|
||||
- совпадающие недели показываются одним списком, различающиеся — двумя
|
||||
колонками, пустая — «Окно»;
|
||||
- примечания из таблицы (красный текст) — отдельной строкой в карточке;
|
||||
- PWA: манифест, service worker, иконки — «на главный экран».
|
||||
|
||||
## Архитектура
|
||||
|
||||
@@ -24,20 +21,324 @@ make run # создаст .venv, поставит зависим
|
||||
Яндекс.Диск (xlsx)
|
||||
│ скачивание по публичной ссылке
|
||||
▼
|
||||
api (FastAPI), порт 8000, в docker публикуется на 127.0.0.1
|
||||
• скачивает и разбирает xlsx без сторонних библиотек
|
||||
api — FastAPI, порт 8000
|
||||
• качает и разбирает xlsx стандартной библиотекой (zipfile + xml.etree)
|
||||
• кэш в памяти: TTL + проверка даты изменения файла + stale-while-error
|
||||
• один воркер, поэтому кэш общий
|
||||
▲ SCHEDULE_API=http://api:8000 (внутри docker-сети)
|
||||
│ SCHEDULE_API=http://127.0.0.1:8000 (локальная разработка)
|
||||
frontend (FastAPI: статика + прокси), прод :8090, dev :8080
|
||||
• отдаёт index.html / app.js / style.css / картинки
|
||||
▲ SCHEDULE_API=http://api:8000 (внутри docker-сети)
|
||||
│
|
||||
frontend — FastAPI, порт из .env (по умолчанию 8080)
|
||||
• отдаёт index.html / app.js / style.css / иконки
|
||||
• /api/* — тонкий прокси к api (браузер не знает адрес бэкенда)
|
||||
▲
|
||||
nginx (https://schedule.our-place.ru) → 127.0.0.1:8090
|
||||
nginx — порты 80/443, https://<домен сайта> → 127.0.0.1:<порт фронта>
|
||||
```
|
||||
|
||||
## Как устроено расписание в таблице
|
||||
Роли:
|
||||
|
||||
- **docker** изолирует оба сервиса, перезапускает их при падении
|
||||
(`restart: unless-stopped`) и следит за здоровьем (healthcheck на
|
||||
`/healthz`); frontend стартует, только когда api стал healthy.
|
||||
- Оба порта публикуются **только на 127.0.0.1** (жёстко задано в
|
||||
`docker-compose.yml`): из интернета контейнеры напрямую недоступны.
|
||||
- **nginx** — единственная дверь из интернета: принимает https и проксирует на
|
||||
фронт. Его конфиг генерирует `deploy/nginx-site.sh` (это делает установщик).
|
||||
|
||||
## Установка на сервер
|
||||
|
||||
Скачайте zip с проектом, распакуйте и запустите одну команду:
|
||||
|
||||
```bash
|
||||
unzip schedule.zip && cd schedule
|
||||
bash install.sh
|
||||
```
|
||||
|
||||
Установка требует root — скрипт сам перезапустится через sudo. До запуска DNS
|
||||
домена уже должен указывать на сервер, а порты 80/443 — быть открытыми:
|
||||
сертификат Let's Encrypt выпускается прямо в процессе установки.
|
||||
|
||||
Скрипт спросит:
|
||||
|
||||
| Вопрос | Что ответить |
|
||||
|---|---|
|
||||
| Как ставить: Docker или систему | Docker — рекомендуется (разница ниже) |
|
||||
| Публичная ссылка на таблицу | ссылка Я.Диска, единственная обязательная настройка |
|
||||
| Домен сайта | например `sched.example.ru`; пусто = без nginx, сайт на 127.0.0.1 |
|
||||
| Порт api и порт сайта | по умолчанию 8000 и 8080 |
|
||||
| Пользователь служб (только для «в систему») | от кого работают сервисы |
|
||||
| Почта (только если задан домен) | для уведомлений Let's Encrypt |
|
||||
|
||||
Ответы можно передать и флагами: `--mode`, `--link`, `--domain`, `--api-port`,
|
||||
`--front-port`, `--user`, `--email`. `install.sh --dry-run` показывает план
|
||||
установки, ничего не меняя.
|
||||
|
||||
### Docker или система — что выбрать
|
||||
|
||||
**Docker** — рекомендуется: сервисы изолированы, автоматически перезапускаются,
|
||||
обновляются одной командой, не зависят от Python в системе. Установщик поставит
|
||||
Docker (если нет), запишет `.env`, соберёт образы, запустит контейнеры и
|
||||
дождётся их готовности.
|
||||
|
||||
**Система (без Docker)** — если Docker не нужен: установщик проверит Python
|
||||
3.11+, создаст `.venv`, поставит зависимости и зарегистрирует две службы
|
||||
systemd — `schedule-api` и `schedule-frontend` (автозапуск после ребута,
|
||||
перезапуск при падении).
|
||||
|
||||
В обоих случаях, если домен задан, установщик дополнительно поставит nginx и
|
||||
certbot, выпустит сертификат Let's Encrypt (webroot — продление автоматическое,
|
||||
сайт не останавливается) и настроит конфиг: http → https, прокси на порт
|
||||
фронта. Снаружи виден только nginx.
|
||||
|
||||
### Ручная установка (без скрипта)
|
||||
|
||||
Установщик делает ровно следующее — при желании те же шаги выполняются руками.
|
||||
|
||||
**Docker:**
|
||||
|
||||
```bash
|
||||
cp .env.example .env && nano .env # SCHEDULE_LINK (+ порты при желании)
|
||||
sudo ./deploy.sh # собрать и запустить
|
||||
curl -s http://127.0.0.1:8080/api/status # {"has_data":true} — работает
|
||||
```
|
||||
|
||||
**Система:**
|
||||
|
||||
```bash
|
||||
sudo apt install -y python3 python3-venv python3-pip # Python 3.11+
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
cp .env.example .env && nano .env # SCHEDULE_LINK
|
||||
```
|
||||
|
||||
Две службы systemd (`/etc/systemd/system/`), затем
|
||||
`sudo systemctl daemon-reload && sudo systemctl enable --now schedule-api schedule-frontend`:
|
||||
|
||||
```ini
|
||||
# schedule-api.service
|
||||
[Unit]
|
||||
Description=Schedule API (FastAPI)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
User=www-data
|
||||
WorkingDirectory=/путь/к/проекту/src/backend
|
||||
ExecStart=/путь/к/проекту/.venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8000
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
# schedule-frontend.service
|
||||
[Unit]
|
||||
Description=Schedule frontend (FastAPI)
|
||||
After=network-online.target schedule-api.service
|
||||
Wants=network-online.target schedule-api.service
|
||||
[Service]
|
||||
User=www-data
|
||||
WorkingDirectory=/путь/к/проекту/src/frontend
|
||||
Environment=SCHEDULE_API=http://127.0.0.1:8000
|
||||
ExecStart=/путь/к/проекту/.venv/bin/python -m uvicorn main:app --host 127.0.0.1 --port 8080
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
**nginx + сертификат** (общий шаг для обоих способов; `.env` уже должен быть):
|
||||
|
||||
```bash
|
||||
sudo apt install -y nginx certbot
|
||||
# временный http-конфиг домена с location /.well-known/acme-challenge/ → /var/www/html
|
||||
sudo certbot certonly --webroot -w /var/www/html -d sched.example.ru
|
||||
sudo deploy/nginx-site.sh --domain sched.example.ru --cert sched.example.ru --port 8080
|
||||
curl -sI https://sched.example.ru/ | head -3 # HTTP/2 200 — готово
|
||||
```
|
||||
|
||||
`deploy/nginx-site.sh` генерирует финальный конфиг (http → https, прокси на
|
||||
`FRONTEND_PORT` из `.env`, таймауты под холодный кэш), включает его, проверяет
|
||||
`nginx -t` и перечитывает nginx. Посмотреть конфиг, ничего не меняя:
|
||||
`deploy/nginx-site.sh --print --domain sched.example.ru`.
|
||||
|
||||
### Обслуживание
|
||||
|
||||
- **Обновление**: Docker — `cd <каталог проекта> && sudo ./deploy.sh`;
|
||||
система — заменить файлы проекта и
|
||||
`sudo systemctl restart schedule-api schedule-frontend`.
|
||||
- **Диагностика**: Docker — `sudo docker compose ps` и
|
||||
`sudo docker logs api --tail 50`; система — `systemctl status schedule-api`
|
||||
и `journalctl -u schedule-api -u schedule-frontend`. Состояние кэша —
|
||||
`/api/status` (смотреть поле `error`).
|
||||
- **Ротация логов контейнеров** — настроить сразу: без лимита логи растут
|
||||
вечно и однажды забивают диск. `/etc/docker/daemon.json`:
|
||||
|
||||
```json
|
||||
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
|
||||
```
|
||||
|
||||
затем `sudo systemctl restart docker`.
|
||||
- **Диск**: следить `df -h /`; раз в месяц
|
||||
`sudo docker system prune -af && sudo journalctl --vacuum-size=200M`.
|
||||
|
||||
## Локальная разработка
|
||||
|
||||
Нужны Python 3.11+ и bash (Linux, macOS или WSL; на Windows без WSL — Docker).
|
||||
|
||||
```bash
|
||||
python3 --version # должен быть 3.11 или новее
|
||||
cp .env.example .env # вписать SCHEDULE_LINK
|
||||
make run # создаст .venv, поставит зависимости, поднимет оба сервиса
|
||||
```
|
||||
|
||||
Открыть <http://127.0.0.1:8080> (API — <http://127.0.0.1:8000>). Остановить —
|
||||
`Ctrl+C`; отвязать от терминала — `nohup make run &`.
|
||||
|
||||
Данные скачиваются сразу на старте (несколько секунд) и дальше обновляются по
|
||||
интервалу.
|
||||
|
||||
Полезные команды (`make help` — весь список):
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `make venv` | создать `.venv` и установить зависимости |
|
||||
| `make run` (или `make dev`) | запустить API и сайт |
|
||||
| `make run-api` / `make run-front` | запустить только один из сервисов |
|
||||
| `make test` | прогнать тесты |
|
||||
| `make nginx-print` / `make nginx-install` | показать / установить конфиг nginx |
|
||||
|
||||
Без make — то же самое руками:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements-dev.txt
|
||||
.venv/bin/python -m uvicorn main:app --reload --port 8000 # в каталоге src/backend
|
||||
.venv/bin/python -m uvicorn main:app --reload --port 8080 # в каталоге src/frontend
|
||||
```
|
||||
|
||||
### Тесты
|
||||
|
||||
```bash
|
||||
make test # 121 тест, сеть не нужна
|
||||
```
|
||||
|
||||
## Зависимости
|
||||
|
||||
**Системные требования**
|
||||
|
||||
| Что | Зачем |
|
||||
|---|---|
|
||||
| Python 3.11+ | локальная разработка и режим «в систему» |
|
||||
| Docker + docker compose v2 | режим Docker (опционально) |
|
||||
|
||||
**Python-пакеты** — закреплены в `requirements.txt` (рабочие) и
|
||||
`requirements-dev.txt` (тесты):
|
||||
|
||||
| Пакет | Версия | Назначение |
|
||||
|---|---|---|
|
||||
| fastapi | 0.141.1 | веб-фреймворк обоих сервисов |
|
||||
| uvicorn | 0.52.4 | ASGI-сервер, которым запускаются сервисы |
|
||||
| python-dotenv | 1.2.3 | чтение `.env` |
|
||||
| requests | 2.34.2 | скачивание файла с Я.Диска и прокси к API |
|
||||
| pytest | 9.1.1 | тесты — только dev |
|
||||
| httpx2 | 2.12.0 | HTTP-клиент (наследник httpx) для `TestClient` — только dev |
|
||||
|
||||
Больше ничего не нужно: xlsx разбирается стандартной библиотекой, базы данных
|
||||
нет, кэш — в памяти процесса. httpx2 в рабочем коде не используется и нужен
|
||||
только тестам; модуль импортируется как `httpx2` — httpx перешёл под опеку
|
||||
Pydantic и был переименован в августе 2026, `import httpx` больше не работает.
|
||||
|
||||
### Если установка не идёт
|
||||
|
||||
- Проверьте `python3 --version`: нужен 3.11 или новее. Если под `python3`
|
||||
скрывается старая версия, создайте окружение нужной явно:
|
||||
`python3.12 -m venv .venv`.
|
||||
- Debian/Ubuntu: если `make venv` падает на создании окружения, доставьте
|
||||
пакет venv: `sudo apt install python3-venv` (для нестандартной версии —
|
||||
например, `python3.12-venv`).
|
||||
|
||||
### Если pip не находит пакеты: зеркала
|
||||
|
||||
Если pip настроен на корпоративное/национальное зеркало, оно может отставать
|
||||
от PyPI — свежих пакетов там ещё нет. Диагностика и обход:
|
||||
|
||||
```bash
|
||||
pip index versions httpx2 # есть ли пакет на текущем индексе
|
||||
pip install -r requirements-dev.txt -i <зеркало> # разово через другое зеркало
|
||||
pip config set global.index-url <зеркало> # закрепить зеркало навсегда
|
||||
```
|
||||
|
||||
Зеркала, проверенные 07.10.2026 (пакеты `httpx2`/`httpcore2` на месте;
|
||||
URL подставлять как есть в `-i` / `index-url`):
|
||||
|
||||
| Зеркало | URL |
|
||||
|---|---|
|
||||
| PyPI, официальный | `https://pypi.org/simple` |
|
||||
| TUNA (Tsinghua) | `https://pypi.tuna.tsinghua.edu.cn/simple` |
|
||||
| Huawei Cloud | `https://mirrors.huaweicloud.com/repository/pypi/simple` |
|
||||
| BFSU | `https://mirrors.bfsu.edu.cn/pypi/web/simple` |
|
||||
| SJTU | `https://mirror.sjtu.edu.cn/pypi/web/simple` |
|
||||
| USTC | `https://mirrors.ustc.edu.cn/pypi/simple` |
|
||||
| SUSTech | `https://mirrors.sustech.edu.cn/pypi/simple` |
|
||||
|
||||
Примечания: Alibaba Cloud (`https://mirrors.aliyun.com/pypi/simple`) обычно
|
||||
тоже свежий, но из части сетей недоступен; Яндекс-зеркало (`mirror.yandex.ru`)
|
||||
PyPI не раздаёт вовсе. Зеркала отстают по-разному, поэтому для свежих пакетов
|
||||
надёжнее разово ставить с официального индекса.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
| Переменная | По умолчанию | Где | Описание |
|
||||
|---|---|---|---|
|
||||
| `SCHEDULE_LINK` | — | api | публичная ссылка Я.Диска, **обязательна** |
|
||||
| `SCHEDULE_INTERVAL_MINUTES` | `15` | api | как часто перечитывать таблицу, минут |
|
||||
| `SCHEDULE_CACHE_TTL` | `900` | api | устаревшее: интервал в секундах (если не задан предыдущий) |
|
||||
| `SCHEDULE_RETRY_AFTER` | `60` | api | пауза между попытками при сбое источника |
|
||||
| `SCHEDULE_API` | `http://127.0.0.1:8000` | frontend | адрес бэкенда (в docker: `http://api:8000`) |
|
||||
| `SCHEDULE_PROXY_TIMEOUT` | `150` | frontend | таймаут прокси к бэкенду, сек |
|
||||
| `API_PORT` | `8000` | docker | внешний порт api (публикуется на 127.0.0.1) |
|
||||
| `FRONTEND_PORT` | `8080` | docker | внешний порт frontend (публикуется на 127.0.0.1) |
|
||||
|
||||
`.env` ищется вверх от каталога модуля: и backend, и frontend находят общий
|
||||
`.env` в корне репозитория; `load_dotenv` не перезаписывает уже выставленные
|
||||
переменные окружения.
|
||||
|
||||
## Структура кода
|
||||
|
||||
```
|
||||
install.sh интерактивная установка на сервер: Docker или systemd + nginx
|
||||
src/
|
||||
backend/
|
||||
main.py FastAPI: маршруты, create_app(cache), /healthz
|
||||
schedule/
|
||||
xlsx.py чтение xlsx: общие строки, объединённые ячейки,
|
||||
скрытые столбцы, красный текст, серийные даты
|
||||
source.py скачивание файла по публичной ссылке Я.Диска
|
||||
lessons.py разбор ячеек занятий (общий конвейер ролей)
|
||||
parsing.py лист → расписание, недели, индексы преподавателей/аудиторий
|
||||
cache.py ScheduleCache: блокировка, интервал, stale-while-error
|
||||
errors.py ScheduleError
|
||||
frontend/
|
||||
main.py FastAPI: статика + прокси /api/* (таймаут 150 c)
|
||||
static/
|
||||
index.html, app.js, style.css
|
||||
manifest.json PWA: имя, иконки, standalone-режим
|
||||
sw.js минимальный service worker (без кэша) для установки приложения
|
||||
img/ логотипы и иконки приложения
|
||||
tools/
|
||||
make_icons.py генерация иконок приложения из логотипа (нужен rsvg-convert)
|
||||
deploy/
|
||||
nginx-site.sh генерация и включение конфига nginx (используется install.sh)
|
||||
tests/
|
||||
conftest.py общие фикстуры
|
||||
xlsx_builder.py генерация xlsx в памяти (без openpyxl)
|
||||
fixtures.py общая тестовая таблица
|
||||
test_lessons.py все форматы ячеек
|
||||
test_xlsx.py чтение книги
|
||||
test_parsing.py разбор листов, недели, индексы
|
||||
test_api.py API и поведение кэша (интервал/stale, 404/400/502)
|
||||
test_deploy.py проверка, что образы копируют все нужные файлы
|
||||
```
|
||||
|
||||
## Как устроена таблица-источник
|
||||
|
||||
Лист «Бакалавры и магистры» — это сетка:
|
||||
|
||||
@@ -103,45 +404,6 @@ nginx (https://schedule.our-place.ru) → 127.0.0.1:8090
|
||||
- Приложение можно «установить» на главный экран (манифест + service
|
||||
worker + иконки приложения); на iOS иконку берёт `apple-touch-icon`.
|
||||
|
||||
## Структура кода
|
||||
|
||||
```
|
||||
src/
|
||||
backend/
|
||||
main.py FastAPI: маршруты, create_app(cache), /healthz
|
||||
schedule/
|
||||
xlsx.py чтение xlsx: общие строки, объединённые ячейки,
|
||||
скрытые столбцы, красный текст, серийные даты
|
||||
source.py скачивание файла по публичной ссылке Я.Диска
|
||||
lessons.py разбор ячеек занятий (общий конвейер ролей)
|
||||
parsing.py лист → расписание, недели, индексы преподавателей/аудиторий
|
||||
cache.py ScheduleCache: блокировка, интервал, stale-while-error
|
||||
errors.py ScheduleError
|
||||
frontend/
|
||||
main.py FastAPI: статика + прокси /api/* (таймаут 150 c)
|
||||
static/
|
||||
index.html, app.js, style.css
|
||||
manifest.json PWA: имя, иконки, standalone-режим
|
||||
sw.js минимальный service worker (без кэша) для установки приложения
|
||||
img/ логотипы (owl.svg, owl-white.svg), иконки приложения
|
||||
(app-icon.svg, icon-192/512.png, apple-touch-icon.png, favicon-32.png)
|
||||
tools/
|
||||
make_icons.py генерация иконок приложения из логотипа (нужен rsvg-convert)
|
||||
deploy/
|
||||
nginx-site.sh генерация и включение конфига nginx (SSL — сертификат родителя)
|
||||
nginx-dev.conf конфиг-заготовка для стенда без wildcard у родителя
|
||||
README-dev.md как поднять второй стенд рядом с основным
|
||||
tests/
|
||||
conftest.py общие фикстуры
|
||||
xlsx_builder.py генерация xlsx в памяти (без openpyxl)
|
||||
fixtures.py общая тестовая таблица
|
||||
test_lessons.py все форматы ячеек
|
||||
test_xlsx.py чтение книги
|
||||
test_parsing.py разбор листов, недели, индексы
|
||||
test_api.py API и поведение кэша (интервал/stale, 404/400/502)
|
||||
test_deploy.py проверка, что образы копируют все нужные файлы
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
Полный список методов отдаёт сам бэкенд: `GET /`.
|
||||
@@ -159,167 +421,10 @@ tests/
|
||||
| GET | `/rooms-day?day={день}` | занятость всех аудиторий за день |
|
||||
| GET | `/rooms-free?day={день}&period={N}` | свободные аудитории |
|
||||
| GET | `/week` | недели семестра и текущая чётность |
|
||||
| GET | `/status` | состояние кэша (`cache_ttl_seconds`, `last_updated`, `table_modified`, `error`) |
|
||||
| GET | `/status` | состояние кэша (`last_updated`, `table_modified`, `error`, …) |
|
||||
| GET | `/healthz` | живость процесса (не обращается к таблице) |
|
||||
|
||||
Фронт проксирует только то, что использует, под префиксом `/api/...`:
|
||||
`groups`, `schedule/{group}`, `teachers`, `teacher/{name}`, `rooms`,
|
||||
`room/{label}`, `rooms-day`, `rooms-free`, `week`, `status`.
|
||||
Остальное (`today`, `schedule/{group}/{day}`) доступно только на бэкенде.
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
| Переменная | По умолчанию | Где | Описание |
|
||||
|---|---|---|---|
|
||||
| `SCHEDULE_LINK` | — | api | публичная ссылка Я.Диска, **обязательна** |
|
||||
| `SCHEDULE_INTERVAL_MINUTES` | `15` | api | как часто перечитывать таблицу, минут |
|
||||
| `SCHEDULE_CACHE_TTL` | `900` | api | устаревшее: интервал в секундах (если не задан предыдущий) |
|
||||
| `SCHEDULE_RETRY_AFTER` | `60` | api | пауза между попытками при сбое источника |
|
||||
| `SCHEDULE_API` | `http://127.0.0.1:8000` | frontend | адрес бэкенда (в docker: `http://api:8000`) |
|
||||
| `SCHEDULE_PROXY_TIMEOUT` | `150` | frontend | таймаут прокси к бэкенду, сек |
|
||||
| `API_PORT` | `8000` | docker | внешний порт api (публикуется на 127.0.0.1) |
|
||||
| `FRONTEND_PORT` | `8080` | docker | внешний порт frontend (публикуется на 127.0.0.1) |
|
||||
|
||||
`.env` ищется вверх от каталога модуля: и backend, и frontend находят общий
|
||||
`.env` в корне репозитория; `load_dotenv` не перезаписывает уже выставленные
|
||||
переменные окружения.
|
||||
|
||||
## Зависимости
|
||||
|
||||
Проект состоит из двух сервисов на Python (FastAPI): `api` и `frontend`.
|
||||
Всё ставится одной командой `make venv` (или вручную, см. ниже).
|
||||
|
||||
**Системные требования**
|
||||
|
||||
| Что | Зачем |
|
||||
|---|---|
|
||||
| Python 3.11+ | оба сервиса |
|
||||
| Docker + docker compose v2 | только для запуска в контейнерах (опционально) |
|
||||
|
||||
**Python-зависимости** — закреплены в `requirements.txt` (рабочие) и
|
||||
`requirements-dev.txt` (тесты):
|
||||
|
||||
| Пакет | Версия | Назначение |
|
||||
|---|---|---|
|
||||
| fastapi | 0.141.1 | веб-фреймворк обоих сервисов |
|
||||
| uvicorn | 0.52.4 | ASGI-сервер, которым запускаются сервисы |
|
||||
| python-dotenv | 1.2.3 | чтение `.env` |
|
||||
| requests | 2.34.2 | скачивание файла с Яндекс.Диска и прокси к API |
|
||||
| pytest | 9.1.1 | тесты (`make test`) — только dev |
|
||||
| httpx2 | 2.12.0 | HTTP-клиент (наследник httpx) для `TestClient` в тестах — только dev |
|
||||
|
||||
Ничего, кроме этого, не нужно: xlsx разбирается стандартной библиотекой
|
||||
(`zipfile` + `xml.etree`), базы данных нет, кэш — в памяти процесса.
|
||||
|
||||
**Если установка не идёт**
|
||||
|
||||
- Проверьте `python3 --version`: нужен 3.11 или новее. Если в системе под
|
||||
`python3` скрывается старая версия (например, 3.6 на старых CentOS), создайте
|
||||
окружение нужной версией явно: `python3.12 -m venv .venv`.
|
||||
- Debian/Ubuntu: если `make venv` падает на создании окружения, доставьте
|
||||
пакет venv: `sudo apt install python3-venv` (для нестандартной версии —
|
||||
например, `python3.12-venv`).
|
||||
- `httpx2` и `httpcore2` — новые пакеты (август 2026, httpx перешёл под
|
||||
опеку Pydantic и был переименован). В коде и тестах модуль импортируется
|
||||
как `httpx2`; `import httpx` больше не работает. В рабочем коде httpx2
|
||||
не используется — нужен только тестам.
|
||||
|
||||
**Если pip не находит пакеты: зеркала**
|
||||
|
||||
Если pip настроен на корпоративное/национальное зеркало, оно может отставать
|
||||
от PyPI — свежие пакеты на нём ещё не появились. Диагностика и обход:
|
||||
|
||||
```bash
|
||||
pip index versions httpx2 # есть ли пакет на текущем индексе
|
||||
pip install -r requirements-dev.txt -i <зеркало> # разово через другое зеркало
|
||||
pip config set global.index-url <зеркало> # закрепить зеркало навсегда
|
||||
```
|
||||
|
||||
Зеркала, проверенные 07.10.2026 (пакеты `httpx2`/`httpcore2` на месте;
|
||||
URL подставлять как есть в `-i` / `index-url`):
|
||||
|
||||
| Зеркало | URL |
|
||||
|---|---|
|
||||
| PyPI, официальный | `https://pypi.org/simple` |
|
||||
| TUNA (Tsinghua) | `https://pypi.tuna.tsinghua.edu.cn/simple` |
|
||||
| Huawei Cloud | `https://mirrors.huaweicloud.com/repository/pypi/simple` |
|
||||
| BFSU | `https://mirrors.bfsu.edu.cn/pypi/web/simple` |
|
||||
| SJTU | `https://mirror.sjtu.edu.cn/pypi/web/simple` |
|
||||
| USTC | `https://mirrors.ustc.edu.cn/pypi/simple` |
|
||||
| SUSTech | `https://mirrors.sustech.edu.cn/pypi/simple` |
|
||||
|
||||
Makefile написан под bash: Linux, macOS или WSL. На Windows без WSL
|
||||
используйте Docker-вариант из раздела «В Docker».
|
||||
|
||||
## Запуск
|
||||
|
||||
### Без Docker
|
||||
|
||||
```bash
|
||||
git clone <адрес репозитория> && cd Schedule
|
||||
cp .env.example .env # открыть .env и вписать SCHEDULE_LINK
|
||||
make run # = make venv + запуск api и сайта
|
||||
```
|
||||
|
||||
`make run` создаёт виртуальное окружение `.venv`, ставит зависимости и
|
||||
поднимает два сервиса с авто-перезагрузкой:
|
||||
|
||||
- API — <http://127.0.0.1:8000> (например, `/status` и `/groups`);
|
||||
- сайт — <http://127.0.0.1:8080> (его и открывать в браузере).
|
||||
|
||||
Чтобы отвязать процесс от терминала, стоит запускать через `nohup make run &`
|
||||
|
||||
Остановить — `Ctrl+C`. Данные скачиваются сразу на старте приложения
|
||||
(скачивание занимает несколько секунд) и дальше обновляются по интервалу.
|
||||
|
||||
Полезные команды (`make help` — весь список):
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `make venv` | создать `.venv` и установить зависимости |
|
||||
| `make run` (или `make dev`) | запустить API и сайт |
|
||||
| `make run-api` / `make run-front` | запустить только один из сервисов |
|
||||
| `make test` | прогнать тесты |
|
||||
|
||||
Если хочется без make — то же самое руками:
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements-dev.txt
|
||||
.venv/bin/python -m uvicorn main:app --reload --port 8000 # в каталоге src/backend
|
||||
.venv/bin/python -m uvicorn main:app --reload --port 8080 # в каталоге src/frontend
|
||||
```
|
||||
|
||||
### В Docker
|
||||
|
||||
```bash
|
||||
cp .env.example .env # вписать SCHEDULE_LINK (и порты при необходимости)
|
||||
./deploy.sh # = docker compose build && up -d
|
||||
```
|
||||
|
||||
У обоих сервисов есть healthcheck на `/healthz`; frontend стартует после того,
|
||||
как api стал healthy. Обновление: `git pull && docker compose up -d --build`.
|
||||
|
||||
### Настройка nginx (прод)
|
||||
|
||||
Скрипт генерирует и включает конфиг сайта: http → https, прокси на
|
||||
`FRONTEND_PORT` из `.env`, после проверки `nginx -t` перечитывает nginx.
|
||||
SSL обеспечивается сертификатом родительского домена (обычно wildcard
|
||||
`*.our-place.ru`) — отдельный certbot для поддомена не нужен.
|
||||
|
||||
```bash
|
||||
sudo ls /etc/letsencrypt/live/ # имя сертификата родительского домена
|
||||
sudo make nginx-install # или: sudo deploy/nginx-site.sh
|
||||
```
|
||||
|
||||
Параметры: `--domain sched.example.ru`, `--cert example.ru` (по умолчанию —
|
||||
родительский домен от `--domain`), `--port 8090` (по умолчанию —
|
||||
`FRONTEND_PORT` из `.env`). Посмотреть конфиг, ничего не меняя:
|
||||
`make nginx-print`. Если wildcard у родителя нет — выпустите сертификат
|
||||
скриптом certbot, как для dev-стенда (см. `deploy/README-dev.md`).
|
||||
|
||||
### Тесты
|
||||
|
||||
```bash
|
||||
make test # 121 тест, сеть не нужна
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user