Админка нужна на «голом» сервере, поэтому возвращаю её в основную ветку. Она выключена, пока не заданы ADMIN_LOGIN/ADMIN_PASSWORD: маршруты /api/admin/* не регистрируются, страницы нет — обычная установка ничего не замечает. Панель (открывается по секретному пути ADMIN_PATH, кнопки входа на сайте нет, вход по логину/паролю): - состояние: группы, когда обновлялось, дата файла на Диске, изменился ли файл при последнем чтении (sha256), ошибки, путь файла настроек; - новая ссылка на таблицу: проверяется скачиванием, при ошибке откат; - интервал обновления (TTL) в минутах; - «Обновить сейчас»: принудительно перечитывает таблицу (качает файл всегда, интервал — только если на Диске изменилась дата); - история входов: последняя попытка (откуда, когда, успешна ли) и последний успешный вход — и в панели, и в логе службы. Защита входа: ADMIN_MAX_ATTEMPTS неудач (по умолчанию 3), затем блокировка на ADMIN_LOCKOUT_MINUTES минут (по умолчанию 15) — всё из .env. Реализация: - backend/admin.py: cookie-сессия на HMAC (ключ — пароль, 12 ч, HttpOnly, SameSite, Secure за https), сравнение за постоянное время, реальный IP из X-Forwarded-For, логирование каждой попытки; - schedule/settings.py: ссылка, интервал и история входов в JSON; путь по умолчанию data/settings.json (в docker — том schedule_data), при недоступности файла настройки живут в памяти и панель предупреждает; - cache.py: apply_link (с проверкой и откатом), force_reload, state(); - frontend: страница админки по ADMIN_PATH (вне static, noindex) и прокси /api/admin/* с телом и Set-Cookie; - install.sh пишет переменные админки в .env, Dockerfile копирует admin.py. Тесты: 156. Проверено локально (страница, 3 попытки -> блокировка, история) и в собранном docker-образе (том настроек, запись истории).
57 lines
2.1 KiB
YAML
57 lines
2.1 KiB
YAML
services:
|
|
api:
|
|
build:
|
|
context: .
|
|
target: api
|
|
environment:
|
|
SCHEDULE_LINK: ${SCHEDULE_LINK}
|
|
SCHEDULE_INTERVAL_MINUTES: ${SCHEDULE_INTERVAL_MINUTES:-15}
|
|
SCHEDULE_CACHE_TTL: ${SCHEDULE_CACHE_TTL:-900}
|
|
SCHEDULE_RETRY_AFTER: ${SCHEDULE_RETRY_AFTER:-60}
|
|
# админка: пусто = выключена (страница и /api/admin/* не появятся)
|
|
ADMIN_PATH: ${ADMIN_PATH:-}
|
|
ADMIN_LOGIN: ${ADMIN_LOGIN:-}
|
|
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-}
|
|
ADMIN_MAX_ATTEMPTS: ${ADMIN_MAX_ATTEMPTS:-3}
|
|
ADMIN_LOCKOUT_MINUTES: ${ADMIN_LOCKOUT_MINUTES:-15}
|
|
# настройки админки (ссылка, интервал, история входов) живут в томе
|
|
SETTINGS_FILE: ${SETTINGS_FILE:-/data/settings.json}
|
|
volumes:
|
|
- schedule_data:/data
|
|
ports:
|
|
# api нужен только фронтенду внутри docker-сети; наружу — 127.0.0.1 для отладки
|
|
- "127.0.0.1:${API_PORT:-8000}:8000"
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=5)"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
frontend:
|
|
build:
|
|
context: .
|
|
target: frontend
|
|
environment:
|
|
# внутри compose api доступен по имени сервиса; менять не нужно
|
|
SCHEDULE_API: ${SCHEDULE_API:-http://api:8000}
|
|
# страница админки отдаётся фронтендом по этому пути
|
|
ADMIN_PATH: ${ADMIN_PATH:-}
|
|
ports:
|
|
# фронт обслуживает nginx; наружу напрямую не смотрит
|
|
- "127.0.0.1:${FRONTEND_PORT:-8080}:8080"
|
|
restart: unless-stopped
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=5)"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
volumes:
|
|
schedule_data:
|