# Конфигурация Настройки читаются из `.env` библиотекой `python-decouple` (`AutoConfig` с поиском в `/workdir`). Шаблон — `env-dist`; всё, чего нет в `.env`, берётся из значений по умолчанию в `network/settings.py`. ```{important} Секреты никогда не хардкодятся и не попадают в образ. В коде — только `config("VAR_NAME", default=...)`. `.env` монтируется в контейнер во время выполнения. ``` Ниже — все переменные, которые читает `network/settings.py`, по разделам; в `env-dist` они же с умолчаниями. Полноту обоих списков проверяет `network/base/test_settings_documented.py`: переменная, которой нет в одном из них, роняет CI. ## Базовые | Переменная | По умолчанию | Назначение | |---|---|---| | `ENVIRONMENT` | `dev` | При `production` включается задача `parse_data_from_satnogs` | | `DEBUG` | `False` | Режим отладки и Django Debug Toolbar | | `SECRET_KEY` | нет | Секретный ключ Django. Обязателен: без него портал не стартует | | `ALLOWED_HOSTS` | `localhost` | Список через запятую; `127.0.0.1` и `web` добавляются всегда | | `CSRF_TRUSTED_ORIGINS` | пусто | Список через запятую | | `DATABASE_URL` | `sqlite:///db.sqlite3` | В Docker переопределяется на PostgreSQL | | `CONN_MAX_AGE` | `60` | Сколько секунд держать соединение с БД между запросами; `0` — новое на каждый запрос | | `SESSION_ENGINE` | `cached_db` | Хранилище сессий Django: чтение из кэша, запись насквозь в БД | | `LOCALE_PATHS` | `network/locale` | Каталог переводов | | `LOGIN_URL`, `LOGIN_REDIRECT_URL` | `account_login` (при `OIDC_AUTH` — `oidc_authentication_init`), `users:redirect_user` | Имена маршрутов входа и перехода после него | | `MEDIA_VOLUME_PATH` | `/opt/storage/network_volumes/media_volume` | Каталог хоста для медиа `web` и `celery`; читает Compose. Должен существовать до `up` — локально `./media` | | `REGISTRY_VOLUME_PATH` | `/opt/storage/network_volumes/registry` | Каталог хоста для кэша сервиса `registry`; читает Compose | | `SONIKS_IMAGE` | пусто | Тег образа, который деплой записал в `.env`; показывается на странице «Система» | | `DOCS_URL` | GitLab Pages проекта | Куда ведёт `/docs/network/` | | `DOCS_HUB_URL` | GitLab Pages `soniks-docs` | Куда ведёт остальной `/docs/` — хаб документации | | `IGNORE_MIGRATION` | `False` | Не используется: код, который его читал, снят при инициализации форка (`e957dd00`) | ## Безопасность | Переменная | По умолчанию | Назначение | |---|---|---| | `SECURE_HSTS_SECONDS` | `31536000` | Срок HSTS, с | | `SECURE_PROXY_SSL_HEADER` | пусто | Заголовок прокси, подтверждающий HTTPS, в виде `HTTP_X_FORWARDED_PROTO,https` | | `SECURE_SSL_REDIRECT` | включён, если задан `SECURE_PROXY_SSL_HEADER` и выключен `DEBUG` | Редирект на HTTPS. Без заголовка прокси за nginx он зациклится | | `NUM_PROXIES` | пусто | Сколько прокси перед gunicorn дописывают `X-Forwarded-For`. Пусто — лимиты API различают анонимов по всему заголовку, который клиент может подменить. `1` — только если перед приложением ровно один nginx, пишущий заголовок; при лишнем балансировщике все анонимы окажутся в одном счётчике | | `SESSION_COOKIE_SECURE`, `CSRF_COOKIE_SECURE` | `not DEBUG` | Cookie только по HTTPS. Выключать только на стенде, который действительно работает по HTTP, иначе туда никто не войдёт | ### Content Security Policy Каждая переменная — список источников через запятую для одной директивы. Значение **заменяет** умолчание целиком, поэтому переопределение повторяет всё, что ещё нужно; пустое значение означает `'none'`. Значения с кавычками в `.env` берутся в двойные кавычки: `CSP_FRAME_ANCESTORS="'none'"`. `'unsafe-inline'` и `'unsafe-eval'` в умолчаниях нет: атрибут `style="…"` в разметке браузер отбрасывает, и так задумано (см. [фронтенд](dev/frontend.md#политика-безопасности-контента)). К `style-src` код сам добавляет nonce, но только на двух страницах со звуковым плеером (наблюдение и оценка), — через переменную его не задать и не убрать. | Переменная | Директива | По умолчанию | |---|---|---| | `CSP_DEFAULT_SRC` | `default-src` | `'self'`, Mapbox, archive.org, `blob:` | | `CSP_SCRIPT_SRC` | `script-src` | `'self'` | | `CSP_IMG_SRC` | `img-src` | `'self'`, Gravatar, Mapbox, SatNOGS, Yandex Object Storage, `data:`, `blob:` | | `CSP_STYLE_SRC` | `style-src` | `'self'` (+ nonce на страницах с плеером) | | `CSP_WORKER_SRC` | `worker-src` | `blob:` | | `CSP_CHILD_SRC` | `child-src` | `blob:` | | `CSP_FRAME_SRC` | `frame-src` | `blob:,https://dashboard.sonik.space` — без хоста дашборда вкладка «Телеметрия» спутника пустая | | `CSP_FRAME_ANCESTORS` | `frame-ancestors` | `'none'` — портал нельзя встроить во фрейм | ## Внешние сервисы | Переменная | Назначение | |---|---| | `MAPBOX_TOKEN`, `MAPBOX_MAP_ID` | Рендер карт станций и наблюдений | | `DB_API_ENDPOINT`, `DB_API_KEY`, `DB_API_TIMEOUT` | SatNOGS DB — источник спутников и передатчиков | | `NETWORK_API_ENDPOINT` | SatNOGS Network — источник данных для `parse_data_from_satnogs` | | `SATNOGS_DB_PROXY_URL` | Исходящий HTTP(S)-прокси **только** для API `db.satnogs.org` и `network.satnogs.org`, если хосты заблокированы на уровне хостинга | | `EXTERNAL_IMPORT_USER_AGENT` | Как портал представляется nanosats.eu, Wikidata и Wikimedia Commons при импорте паспортных данных. Wikimedia требует от автоматических клиентов осмысленного агента с контактом — адрес в нём должен быть рабочим | | `LL2_API_URL` | Хост Launch Library 2, источника календаря запусков. По умолчанию боевой `https://ll.thespacedevs.com/2.3.0`; для локальной работы — `https://lldev.thespacedevs.com/2.3.0` (без лимита, данные усечены). См. [](launches.md) | | `LL2_HOURLY_BUDGET` | Сколько запросов в час задача `sync_launches_from_ll2` может потратить на LL2. По умолчанию `15` — анонимный лимит боевого хоста; поднимать только вместе с ключом, который поднимает лимит | | `COMMUNITY_TIMEOUT` | Таймаут запроса к форуму сообщества, с (`2.0`) | | `WIKI_STATION_URL` | Не используется: значок-ссылка на вики для владельца на странице станции снят редизайном раздела (`763df9ad`) | ## Орбитальные данные | Переменная | По умолчанию | Назначение | |---|---|---| | `SPACE_TRACK_USERNAME`, `SPACE_TRACK_PASSWORD` | пусто | Учётка Space-Track для загрузки каталога | | `TLE_PRIORITY_FRESHNESS_DAYS` | `7` | Насколько дней приоритетный источник может отставать и всё ещё выигрывать | | `LATEST_TLES_MAX_AGE_DAYS` | `30` | Предельный возраст TLE для публичной выдачи `/api/latesttles/` | | `SPACE_WEATHER_URL` | файл CelesTrak | Индексы солнечной и геомагнитной активности для прогноза схода | | `DECAY_PERIGEE_ALTITUDE_KM` | `120` | Высота перигея, ниже которой спутник считается сошедшим | | `DECAY_FORECAST_HORIZON_DAYS` | `365` | Насколько далеко вперёд считается прогноз схода | | `DECAY_DAILY_FORECAST_DAYS` | `45` | До какого срока прогноз опирается на суточные индексы; дальше дата показывается как оценка | | `DECAY_CONFIRM_LOOKBACK_DAYS` | `30` | Глубина чтения фида подтверждённых сходов Space-Track | | `DECAY_TLE_SILENCE_DAYS` | `30` | Сколько дней ни один источник не публикует TLE, прежде чем молчание считается сходом | ```{note} Дата схода считается по модели атмосферы NRLMSIS, которой управляют индексы F10.7 и Ap из `SPACE_WEATHER_URL`. Суточное разрешение у этих индексов есть примерно на полтора месяца вперёд, дальше известна только сглаженная кривая солнечного цикла — поэтому дата за границей `DECAY_DAILY_FORECAST_DAYS` показывается со знаком «≈». Баллистический коэффициент **не** берётся из `B*` в TLE: это подгоночный параметр SGP4, вбирающий разницу между реальностью и его собственной грубой атмосферой, и в другой модели он занижает сопротивление в разы. Вместо этого коэффициент подгоняется под наблюдаемый спад самого спутника. Если индексы недоступны и в кеше их нет, суточная задача расчёта орбитальных параметров **не выполняется**: прежние даты точнее, чем посчитанные без атмосферы. ``` ```{note} Прогноз даты схода и **перевод спутника в статус «Сошёл с орбиты»** — разные вещи. Статус меняет суточная задача `confirm_satellite_reentries`, и по прогнозу она этого не делает никогда: нужен либо подтверждённый сход из фида Space-Track, либо молчание всех источников TLE дольше `DECAY_TLE_SILENCE_DAYS` вместе с уже прошедшей прогнозной датой. Без учётки Space-Track работает только второе правило. Подробности — в [](satellites.md). ``` ```{note} На dev-стенде и в проде учётные данные Space-Track подставляются при деплое из CI/CD-переменных GitLab (`SPACE_TRACK_*_DEV` и `SPACE_TRACK_*_PROD`), а не берутся из `env-dist` на сервере. Стенд и прод используют **разные** аккаунты, чтобы не упереться в лимиты сессий Space-Track. Подробности — в [](tle.md) и [](dev/deploy.md). ``` ## Планирование наблюдений | Переменная | По умолчанию | Назначение | |---|---|---| | `OBSERVATION_DATE_MIN_START` | `4` | Минимальный отступ начала наблюдения, мин — для станции без живого MQTT; станция на связи заявляет свой порог в секундах (см. [](scheduling.md)) | | `OBSERVATION_DATE_MAX_RANGE` | — | Горизонт планирования | | `OBSERVATION_DURATION_MIN` | `120` | Минимальная длительность, с | | `OBSERVATION_SPLIT_DURATION` | `720` | Порог разбиения длинного окна, с | | `OBSERVATION_SPLIT_BREAK_DURATION` | — | Пауза между частями разбитого окна | | `OBSERVATION_MAX_QUERY_COUNT` | — | Ограничение выборки при планировании | | `NETWORK_SCHEDULER_USERNAME` | `admin` | Аккаунт, от имени которого планирует сетевое расписание | | `OBSERVATIONS_PER_VIOLATOR_SATELLITE`, `OBSERVATIONS_PER_VIOLATOR_SATELLITE_PERIOD` | `2`, `86400` с | Квота на спутники-нарушители частот: сколько наблюдений за период | | `OBSERVATION_WARN_MIN_OBS` | `30` | Порог числа наблюдений, ниже которого форма планирования предупреждает | | `PASS_PREDICTIONS_CACHE_TTL` | `60` с | Сколько переиспользуется ответ с прогнозом пролётов. Коротко намеренно: флаг пересечения отражает текущее расписание | | `FUTURE_OBS_MAX_RANGE_DAYS`, `FUTURE_OBS_THROTTLE_RATE` | `7`, `20/min` | Предельное окно и лимит частоты `/api/future_satellite_obs` | Для наблюдений новых запусков — отдельный набор: `LAUNCH_BREAK_DURATION`, `LAUNCH_SPLIT_DURATION` (`480`), `LAUNCH_MIN_ELEVATION` (`5`), `LAUNCH_GOOD_ELEVATION` (`25`), `LAUNCH_MIN_HORIZON` (`0`), `LAUNCH_MIN_PERIOD` (`48`), `LAUNCH_WAITING_FOR_SATELLITE`. ## Станции | Переменная | По умолчанию | Назначение | |---|---|---| | `STATION_HEARTBEAT_TIME` | `60` | Период сердцебиения станции, с | | `STATION_UPCOMING_END` | `24` | Горизонт «ближайших» наблюдений, ч | | `MAX_ANTENNAS_PER_STATION` | `4` | Лимит антенн | | `MAX_FREQUENCY_RANGES_PER_ANTENNA` | `5` | Лимит диапазонов на антенну | | `MIN_FREQUENCY_FOR_RANGE`, `MAX_FREQUENCY_FOR_RANGE` | `300`, `40000000000` Гц | Допустимые границы диапазона антенны | | `VHF_MIN_FREQUENCY`, `VHF_MAX_FREQUENCY` | `135000000`, `148000000` Гц | Границы VHF для классификации | | `UHF_MIN_FREQUENCY`, `UHF_MAX_FREQUENCY` | `400000000`, `470000000` Гц | Границы UHF | | `L_MIN_FREQUENCY`, `L_MAX_FREQUENCY` | `1050000000`, `1750000000` Гц | Границы L-диапазона | | `S_MIN_FREQUENCY`, `S_MAX_FREQUENCY` | `2200000000`, `2450000000` Гц | Границы S-диапазона | | `JOBS_ALLOW_ANONYMOUS` | `True` | Отвечает ли `GET /api/jobs/` без токена. Выключать, когда весь парк запрашивает расписание с токеном: `JobView` пишет предупреждение на каждый анонимный запрос | | `OBS_AUDIO_DURATION_TOLERANCE` | `120` с | На сколько аудио может расходиться с длительностью наблюдения, прежде чем оно оценивается как «failed» | | `DEMODDATA_TIME_TOLERANCE_MINUTES` | `5` | Насколько метка времени кадра может выходить за окно наблюдения — допуск на дрейф часов станции | | `OBS_NO_RESULTS_CHECK_PERIOD` | — | Период проверки станций без результатов | | `OBS_NO_RESULTS_MIN_COUNT` | `3` | Сколько пустых наблюдений подряд считать проблемой | | `OBS_NO_RESULTS_IGNORE_TIME` | — | Сколько ждать после конца наблюдения, прежде чем считать его провалившимся | | `EMAIL_FOR_STATIONS_ISSUES` | пусто | Адрес для уведомлений о проблемных станциях | | `AUTO_TRANSMITTER_DRIFT` | `False` | Применять ли ночной задаче поправки `downlink_drift` передатчиков, вычисленные по откалиброванным станциям; выключено — задача только пишет в лог, что изменила бы | ## Хранение артефактов Аудио, водопады и данные могут лежать локально или в S3-совместимом хранилище — независимо друг от друга: | Переменная | Назначение | |---|---| | `USE_S3_STORAGE_FOR_AUDIO` | Аудио в S3 | | `USE_S3_STORAGE_FOR_WATERFALL` | Водопады в S3 | | `USE_S3_STORAGE_FOR_DATA` | Демодулированные данные в S3 | | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_STORAGE_BUCKET_NAME`, `AWS_S3_ENDPOINT_URL`, `AWS_QUERYSTRING_AUTH` | Доступ к бакету | | `BACKUP_S3_BUCKET` | Отдельный **приватный** бакет для суточных дампов БД, те же ключи `AWS_*`; пусто — задача не регистрируется (см. [](operations.md)) | | `MEDIA_ROOT`, `STATIC_ROOT`, `MEDIA_URL`, `STATIC_URL` | Локальные пути и URL | | `FILE_UPLOAD_TEMP_DIR` | Каталог для загрузок, не поместившихся в память (`/tmp`) | Архивация аудио (упаковка в zip и выгрузка на archive.org) включается отдельно: `ZIP_AUDIO_FILES`, `AUDIO_FILES_PER_ZIP` (`1000`), `ZIP_FILE_PREFIX`, `ARCHIVE_ZIP_FILES`, `ZIP_FILES_PER_ITEM` (`10`), `ARCHIVE_COLLECTION`, `ARCHIVE_MEDIA_TYPE` (`data`), `ITEM_IDENTIFIER_PREFIX`, `ITEM_TITLE_PREFIX`, `ARCHIVE_SKIP_TIME` (`48` ч), `REMOVE_ARCHIVED_AUDIO_FILES`, `REMOVE_ARCHIVED_ZIP_FILE`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_RETRIES_ON_SLOW_DOWN`, `S3_RETRIES_SLEEP`. Сроки блокировок в кэше, с: `ZIP_AUDIO_LOCK_EXPIRATION` (`600`, дописывание файла в архив), `ZIP_TASK_LOCK_EXPIRATION` (`3600`, задача упаковки), `ARCHIVE_TASK_LOCK_EXPIRATION` (`86400`, задача выгрузки), `ARCHIVE_ZIP_LOCK_EXPIRATION` (`21600`, выгрузка одного архива). `ARCHIVE_AUDIO_LOCK_EXPIRATION` не используется: задача, которая его читала, снята в `22119248`. ```{note} Задачи `zip_audio_files` и `archive_audio_zip_files` регистрируются в расписании только если соответствующий флаг включён **и** `USE_S3_STORAGE_FOR_AUDIO` выключен — при хранении аудио в S3 паковать нечего. ``` ## Данные наблюдений и проверка | Переменная | По умолчанию | Назначение | |---|---|---| | `EXPORTED_FRAMESET_TIME_TO_LIVE` | `86400` с | Сколько хранится выгрузка кадров, прежде чем её удалит периодическая задача | | `PROCESS_AUDIO_LOCK_EXPIRATION` | `900` с | Срок блокировки задачи обработки аудио | | `VET_ITEMS_PER_CHUNK` | `20` | Сколько наблюдений подгружает за раз страница массовой проверки | | `CHUNKS_BUFFER_HEADSTART`, `FWD_BUFFER_CHUNKS`, `BWD_BUFFER_CHUNKS` | `1`, `2`, `2` | Сколько порций страница проверки подгружает сразу при открытии и сколько держит загруженными впереди и позади текущей | | `NOT_SYNCED_MODES` | список аналоговых режимов | Не используется: синхронизация демодулированных данных в SatNOGS DB, которая его читала, снята в `a77b17ff` | ## Статика | Переменная | По умолчанию | Назначение | |---|---|---| | `COMPRESS_ENABLED` | `False` | Сборка скриптов и стилей страниц в общие файлы django-compressor. JS не минифицируется: минификатор ломал tom-select и mapbox-gl (комментарий `COMPRESS_FILTERS` в `settings.py`) | | `COMPRESS_OFFLINE` | `False` | Собирать заранее: `bin/djangoctl.sh` выполняет `compress --force` при старте контейнера, страницы читают его манифест. На сервере включается **только вместе** с `COMPRESS_ENABLED`: без него бандлы собираются по первому запросу, имена лежат в кэше, а `collectstatic --clear` при следующем старте стирает сами файлы | | `COMPRESS_CACHE_BACKEND` | `default` | Кэш, в котором компрессор хранит имена собранных файлов | | `SASS_BINARY` | dart-sass из `node_modules` или `sass` из `PATH` | Путь к dart-sass для SCSS. Задаётся там, где нет ни того ни другого — например в тестовой джобе CI | ## Registry образов станций Читаются Compose, а не `settings.py`: сервис `registry` в `docker-compose.yml` — pull-through cache Docker Hub для станций. Устройство и сниппет nginx — в [](dev/deploy.md). | Переменная | Назначение | |---|---| | `COMPOSE_PROFILES` | `registry` поднимает сервис. Только на сервере, где nginx ведёт `/v2/` на него | | `REGISTRY_PORT` | Порт на loopback, куда nginx проксирует `/v2/`; умолчание `5000`. На проде 5000 занят `sonik_globus` — задать свободный и тот же порт в сниппете nginx | | `REGISTRY_PROXY_USERNAME`, `REGISTRY_PROXY_PASSWORD` | Read-only токен Docker Hub. Пусто — анонимно: работает, но промахи кэша упираются в лимит Docker Hub по IP | ## MQTT-брокер Живой канал со станциями (сервисы `mosquitto` и `mqtt-bridge` под профилем `mqtt`). Устройство и сниппет nginx — в [](dev/deploy.md). | Переменная | Назначение | |---|---| | `COMPOSE_PROFILES` | `mqtt` (вместе с `registry`: `registry,mqtt`) поднимает брокер и мост. Только на сервере, где nginx ведёт `/mqtt` на брокер | | `MQTT_BROKER_HOST`, `MQTT_BROKER_PORT` | Адрес брокера для публикации и моста; в compose — `mosquitto`, `1883`. Пусто — публикации нет, станции забирают конфигурацию REST'ом | | `MQTT_PORTAL_USERNAME`, `MQTT_PORTAL_PASSWORD` | Служебный логин портала на брокере. Проверяется самим порталом в `/api/v2/mqtt/auth/` | ## Авторизация через Keycloak `OIDC_AUTH` (`False`) включает вход через OIDC-провайдера. Дополнительно: `OIDC_RP_CLIENT_ID`, `OIDC_RP_CLIENT_SECRET`, `OIDC_OP_AUTHORIZATION_ENDPOINT`, `OIDC_OP_TOKEN_ENDPOINT`, `OIDC_OP_USER_ENDPOINT`, `OIDC_OP_JWKS_ENDPOINT`, `OIDC_OP_LOGOUT_ENDPOINT`, `OIDC_KEYCLOAK_URL`, `OIDC_REALM`, `OIDC_ADMIN_SERVICE_ID`, `OIDC_ADMIN_SERVICE_SECRET`. ### Лимиты попыток входа Локальный вход и регистрация идут через django-allauth, и его лимиты действуют по умолчанию, без отдельных переменных: пять неверных паролей на одно имя пользователя за пять минут блокируют имя (даже с верным паролем), плюс десять неудачных попыток в минуту с одного IP; регистрация — 20 в минуту с IP, сброс пароля — 20 в минуту с IP и 5 в минуту на адрес. Блокировка по имени показывается ошибкой формы входа, лимиты по IP — страницей 429. Счётчики живут в кэше (`CACHE_BACKEND`): с Redis они общие для всех воркеров, с locmem — на процесс. Переопределяются словарём `ACCOUNT_RATE_LIMITS` в `settings.py` (ключи `login_failed`, `login`, `signup`, `reset_password`; `False` отключает всё). Вход через OIDC этим лимитам не подчиняется. Проверка — `network/users/test_login_rate_limit.py`. ## Celery, кэш, лимиты API | Переменная | По умолчанию | |---|---| | `CELERY_BROKER_URL` | `redis://redis:6379/0` | | `CELERY_RESULT_BACKEND` | `redis://redis:6379/0` | | `REDIS_VISIBILITY_TIMEOUT` | `3600` с — через сколько брокер переотправит неподтверждённую задачу; должен быть больше любого `time_limit`, иначе долгая задача выполнится дважды (проверяет `test_settings_wiring`) | | `CELERY_RESULT_EXPIRES` | `3600` с — он же потолок длительности стадии автопланирования | | `CELERY_TASK_SOFT_TIME_LIMIT`, `CELERY_TASK_TIME_LIMIT` | `600` / `900` с — мягкий лимит бросает `SoftTimeLimitExceeded` внутри задачи, жёсткий убивает процесс; у бэкапа и обходов каталога свои лимиты в `network/celery.py` | | `CELERY_TASK_ACKS_LATE`, `CELERY_TASK_REJECT_ON_WORKER_LOST` | `True` — задача подтверждается после выполнения и возвращается в очередь, если воркер погиб | | `CELERY_WORKER_PREFETCH_MULTIPLIER` | `1` — воркер не резервирует задачи впрок | | `CELERY_WORKER_CONCURRENCY` | `4` — число процессов воркера; не от числа ядер, потому что от него посчитан `mem_limit` сервиса `celery` (см. [](operations.md)) | | `CACHE_BACKEND`, `CACHE_LOCATION`, `CACHE_TTL` | `300` с | | `DOCKER_API_URL` | `http://docker-proxy:2375` — Docker Engine API для страницы «Система» (см. [](operations.md)); пустое значение прячет секции контейнеров и логов | | `LATEST_TLES_THROTTLE_RATE` | `60/min` | | `OBSERVATIONS_LIST_ANON_THROTTLE_RATE`, `OBSERVATIONS_LIST_AUTH_THROTTLE_RATE` | `60/hour`, `240/hour` — чтение списка наблюдений анонимно и с токеном | | `STATIONS_LIST_ANON_THROTTLE_RATE` | `256/hour` — анонимное чтение списка станций | | `ANALYTICS_THROTTLE_RATE` | `120/min` | | `ANALYTICS_HEAVY_THROTTLE_RATE` | `30/min` | | `DEMODDATA_CREATE_THROTTLE_RATE` | `3600/hour` — отправка кадров SIDS в `/api/demoddata/`, на пользователя или на IP анонима | | `STATION_STATUS_THROTTLE_RATE` | `1200/hour` — отчёт станции `POST /api/v2/stations//status/` на владельца; чтение не ограничено | | `ITEMS_PER_PAGE` | `25` | ## InfluxDB Необязательная интеграция для телеметрии: `USE_INFLUX`, `INFLUX_URL`, `INFLUX_TOKEN`, `INFLUX_BUCKED`, `INFLUX_ORG`. ## Почта `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_USE_TLS`, `EMAIL_USE_SSL`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD`, `DEFAULT_FROM_EMAIL`, `SERVER_EMAIL`, `EMAIL_ADMIN`, `EMAIL_TIMEOUT`. По этой же почте уходят необработанные ошибки: обработчик `mail_admins` на логгере `django.request` шлёт трассировку каждого 500 на `EMAIL_ADMIN`. Фильтр `require_debug_false` гасит отправку при `DEBUG=True`, поэтому локальный запуск никому не пишет. Ограничения частоты нет: цикл отказов даст письмо на каждый запрос. ```{important} Так что `EMAIL_HOST` и `EMAIL_ADMIN` на сервере — не декоративные. Пока они не настроены, об ошибках портала не узнаёт никто: обработчик отработает, письмо не уйдёт, а `AdminEmailHandler` проглотит сбой SMTP молча, чтобы не ронять запрос. ```