Исправлен деплой скрипт
This commit is contained in:
@@ -10,5 +10,7 @@ SCHEDULE_CACHE_TTL=900
|
||||
SCHEDULE_RETRY_AFTER=60
|
||||
|
||||
# --- порты (только для docker) -------------------------------------------------
|
||||
# Оба порта публикуются на 127.0.0.1 (жёстко задано в docker-compose.yml):
|
||||
# снаружи сервисы отдаёт nginx, напрямую в интернет они не смотрят.
|
||||
API_PORT=8000
|
||||
FRONTEND_PORT=8080
|
||||
|
||||
10
Makefile
10
Makefile
@@ -1,7 +1,7 @@
|
||||
PY := $(CURDIR)/.venv/bin/python
|
||||
PIP := $(CURDIR)/.venv/bin/pip
|
||||
|
||||
.PHONY: help venv run dev run-api run-front test build up down restart logs ps deploy
|
||||
.PHONY: help venv run dev run-api run-front test build up down restart logs ps deploy nginx-print nginx-install
|
||||
|
||||
help: ## Показать этот список команд
|
||||
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}'
|
||||
@@ -57,3 +57,11 @@ ps: ## Статус сервисов
|
||||
|
||||
deploy: ## Собрать и запустить (docker)
|
||||
./deploy.sh
|
||||
|
||||
# --- nginx (на сервере) ------------------------------------------------------
|
||||
|
||||
nginx-print: ## Показать конфиг nginx для этого стенда, ничего не меняя
|
||||
deploy/nginx-site.sh --print
|
||||
|
||||
nginx-install: ## Сгенерировать, включить и перечитать конфиг nginx (запускать с sudo)
|
||||
sudo deploy/nginx-site.sh
|
||||
|
||||
64
README.md
64
README.md
@@ -24,7 +24,7 @@ make run # создаст .venv, поставит зависим
|
||||
Яндекс.Диск (xlsx)
|
||||
│ скачивание по публичной ссылке
|
||||
▼
|
||||
api (FastAPI), порт 8000, наружу — только 127.0.0.1 (в docker так публикуется порт)
|
||||
api (FastAPI), порт 8000, в docker публикуется на 127.0.0.1
|
||||
• скачивает и разбирает xlsx без сторонних библиотек
|
||||
• кэш в памяти: TTL + проверка даты изменения файла + stale-while-error
|
||||
• один воркер, поэтому кэш общий
|
||||
@@ -128,7 +128,8 @@ src/
|
||||
tools/
|
||||
make_icons.py генерация иконок приложения из логотипа (нужен rsvg-convert)
|
||||
deploy/
|
||||
nginx-dev.conf пример сайта nginx для отдельного стенда
|
||||
nginx-site.sh генерация и включение конфига nginx (SSL — сертификат родителя)
|
||||
nginx-dev.conf конфиг-заготовка для стенда без wildcard у родителя
|
||||
README-dev.md как поднять второй стенд рядом с основным
|
||||
tests/
|
||||
conftest.py общие фикстуры
|
||||
@@ -176,8 +177,8 @@ tests/
|
||||
| `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 |
|
||||
| `FRONTEND_PORT` | `8080` | docker | внешний порт frontend (на проде: `127.0.0.1:8090`) |
|
||||
| `API_PORT` | `8000` | docker | внешний порт api (публикуется на 127.0.0.1) |
|
||||
| `FRONTEND_PORT` | `8080` | docker | внешний порт frontend (публикуется на 127.0.0.1) |
|
||||
|
||||
`.env` ищется вверх от каталога модуля: и backend, и frontend находят общий
|
||||
`.env` в корне репозитория; `load_dotenv` не перезаписывает уже выставленные
|
||||
@@ -219,12 +220,35 @@ tests/
|
||||
пакет venv: `sudo apt install python3-venv` (для нестандартной версии —
|
||||
например, `python3.12-venv`).
|
||||
- `httpx2` и `httpcore2` — новые пакеты (август 2026, httpx перешёл под
|
||||
опеку Pydantic и был переименован). Если pip берёт пакеты из корпоративного
|
||||
зеркала или старого индекса, этих пакетов там может ещё не быть; проверьте
|
||||
установку напрямую: `pip install -r requirements-dev.txt -i https://pypi.org/simple`.
|
||||
В коде и тестах модуль импортируется как `httpx2`; `import httpx` больше
|
||||
не работает. В рабочем коде httpx2 не используется — нужен только тестам.
|
||||
- Makefile написан под bash: Linux, macOS или WSL. На Windows без WSL
|
||||
опеку 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».
|
||||
|
||||
## Запуск
|
||||
@@ -243,6 +267,8 @@ make run # = make venv + запуск api и сайта
|
||||
- API — <http://127.0.0.1:8000> (например, `/status` и `/groups`);
|
||||
- сайт — <http://127.0.0.1:8080> (его и открывать в браузере).
|
||||
|
||||
Чтобы отвязать процесс от терминала, стоит запускать через `nohup make run &`
|
||||
|
||||
Остановить — `Ctrl+C`. Данные скачиваются сразу на старте приложения
|
||||
(скачивание занимает несколько секунд) и дальше обновляются по интервалу.
|
||||
|
||||
@@ -274,6 +300,24 @@ cp .env.example .env # вписать SCHEDULE_LINK (и порты при
|
||||
У обоих сервисов есть 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
|
||||
|
||||
@@ -19,13 +19,13 @@ git clone <адрес репозитория> /opt/schedule-dev
|
||||
SCHEDULE_LINK=https://disk.yandex.ru/i/...
|
||||
SCHEDULE_INTERVAL_MINUTES=15
|
||||
SCHEDULE_RETRY_AFTER=60
|
||||
API_PORT=127.0.0.1:8001
|
||||
FRONTEND_PORT=127.0.0.1:8091
|
||||
API_PORT=8001
|
||||
FRONTEND_PORT=8091
|
||||
```
|
||||
|
||||
Порты 8001/8091 выбраны, чтобы не пересечься с основным стендом (8000/8090).
|
||||
Привязка к `127.0.0.1` — чтобы API и фронт не смотрели в интернет напрямую,
|
||||
наружу их отдаёт nginx.
|
||||
В docker-compose.yml оба порта публикуются на `127.0.0.1` — API и фронт не
|
||||
смотрят в интернет напрямую, наружу их отдаёт nginx.
|
||||
|
||||
## 3. Запуск
|
||||
|
||||
@@ -37,8 +37,16 @@ sudo docker compose ps
|
||||
|
||||
## 4. nginx и сертификат
|
||||
|
||||
В `deploy/nginx-dev.conf` порт `proxy_pass` должен совпадать с
|
||||
`FRONTEND_PORT` из `.env` (по умолчанию 8091).
|
||||
Конфиг генерирует `deploy/nginx-site.sh`: порт фронта возьмёт из `.env`,
|
||||
сертификат — родительского домена (wildcard), затем включит сайт, проверит
|
||||
`nginx -t` и перечитает nginx:
|
||||
|
||||
```bash
|
||||
sudo deploy/nginx-site.sh --domain dev.schedule.our-place.ru --cert our-place.ru
|
||||
```
|
||||
|
||||
Если у родительского домена нет wildcard-сертификата — выпустите свой
|
||||
(конфиг-заготовка для ручного пути: `deploy/nginx-dev.conf`):
|
||||
|
||||
```bash
|
||||
sudo cp deploy/nginx-dev.conf /etc/nginx/sites-available/dev.schedule.our-place.ru
|
||||
|
||||
120
deploy/nginx-site.sh
Executable file
120
deploy/nginx-site.sh
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
# Генерирует и включает конфиг nginx для сайта расписания.
|
||||
#
|
||||
# SSL обеспечивает сертификат родительского домена (обычно wildcard *.example.ru):
|
||||
# отдельный certbot для поддомена не нужен — достаточно имени сертификата.
|
||||
# Список имён: sudo ls /etc/letsencrypt/live/
|
||||
#
|
||||
# Имя сертификата берётся из --cert, иначе — родительский домен от --domain
|
||||
# (schedule.example.ru -> example.ru). Порт фронта берётся из --port, иначе из
|
||||
# FRONTEND_PORT файла .env в корне проекта, иначе 8090.
|
||||
#
|
||||
# Примеры:
|
||||
# deploy/nginx-site.sh --print # показать конфиг, ничего не менять
|
||||
# sudo deploy/nginx-site.sh # поставить и перечитать nginx
|
||||
# sudo deploy/nginx-site.sh --domain sched.example.ru --cert example.ru
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
grep '^#' "$0" | sed 's/^# \{0,1\}//' | tail -n +2
|
||||
}
|
||||
|
||||
DOMAIN="schedule.our-place.ru"
|
||||
CERT_NAME=""
|
||||
FRONTEND_PORT=""
|
||||
PRINT=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--domain) DOMAIN="$2"; shift 2 ;;
|
||||
--cert) CERT_NAME="$2"; shift 2 ;;
|
||||
--port) FRONTEND_PORT="$2"; shift 2 ;;
|
||||
--print) PRINT=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "Неизвестный аргумент: $1 (см. --help)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ $DOMAIN =~ ^[a-zA-Z0-9.-]+$ ]] || { echo "Кривое имя домена: $DOMAIN" >&2; exit 2; }
|
||||
if [[ -z $CERT_NAME ]]; then
|
||||
CERT_NAME="${DOMAIN#*.}" # schedule.our-place.ru -> our-place.ru (wildcard родителя)
|
||||
fi
|
||||
[[ $CERT_NAME =~ ^[a-zA-Z0-9.-]+$ ]] || { echo "Кривое имя сертификата: $CERT_NAME" >&2; exit 2; }
|
||||
|
||||
if [[ -z $FRONTEND_PORT ]]; then
|
||||
# FRONTEND_PORT из .env; значение вида «127.0.0.1:8090» трактуем как «порт после двоеточия»
|
||||
FRONTEND_PORT=$(
|
||||
grep -E '^FRONTEND_PORT=' .env 2>/dev/null | tail -1 | cut -d= -f2- \
|
||||
| tr -d ' "' | awk -F: '{print $NF}'
|
||||
) || FRONTEND_PORT=""
|
||||
fi
|
||||
FRONTEND_PORT=${FRONTEND_PORT:-8090}
|
||||
[[ $FRONTEND_PORT =~ ^[0-9]+$ ]] || { echo "Не разобрал FRONTEND_PORT из .env, задайте --port" >&2; exit 2; }
|
||||
|
||||
read -r -d '' CONFIG <<'TEMPLATE' || true
|
||||
# Сгенерировано deploy/nginx-site.sh — правки вносить в скрипт, не сюда.
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name __DOMAIN__;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name __DOMAIN__;
|
||||
|
||||
# сертификат родительского домена (обычно wildcard), обновляет certbot родителя
|
||||
ssl_certificate /etc/letsencrypt/live/__CERT__/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/__CERT__/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
client_max_body_size 8m;
|
||||
|
||||
location / {
|
||||
# порт FRONTEND_PORT из .env стенда
|
||||
proxy_pass http://127.0.0.1:__PORT__;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
# холодный кэш: бэкенд качает таблицу, до ~2 минут
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
}
|
||||
TEMPLATE
|
||||
|
||||
CONFIG=${CONFIG//__DOMAIN__/$DOMAIN}
|
||||
CONFIG=${CONFIG//__CERT__/$CERT_NAME}
|
||||
CONFIG=${CONFIG//__PORT__/$FRONTEND_PORT}
|
||||
|
||||
if [[ $PRINT == 1 ]]; then
|
||||
printf '%s\n' "$CONFIG"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Установка требует root: sudo deploy/nginx-site.sh (посмотреть конфиг — deploy/nginx-site.sh --print)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEST="/etc/nginx/sites-available/${DOMAIN}"
|
||||
printf '%s\n' "$CONFIG" > "$DEST"
|
||||
ln -sf "$DEST" "/etc/nginx/sites-enabled/${DOMAIN}"
|
||||
|
||||
if nginx -t 2>&1; then
|
||||
systemctl reload nginx
|
||||
echo "Готово: https://${DOMAIN} -> 127.0.0.1:${FRONTEND_PORT} (сертификат ${CERT_NAME})"
|
||||
else
|
||||
echo "nginx -t не прошёл: конфиг записан в ${DEST} и включён, но nginx НЕ перечитан. Разберитесь и выполните: sudo nginx -t && sudo systemctl reload nginx" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -27,7 +27,8 @@ services:
|
||||
# внутри compose api доступен по имени сервиса; менять не нужно
|
||||
SCHEDULE_API: ${SCHEDULE_API:-http://api:8000}
|
||||
ports:
|
||||
- "${FRONTEND_PORT:-8080}:8080"
|
||||
# фронт обслуживает nginx; наружу напрямую не смотрит
|
||||
- "127.0.0.1:${FRONTEND_PORT:-8080}:8080"
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
-r requirements.txt
|
||||
pytest==9.1.1
|
||||
httpx2==2.12.0
|
||||
|
||||
Reference in New Issue
Block a user