> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Режим обслуживания

> Как приостановить покупки и выдачу доступа, не потревожив уже принятые деньги, из панели или по bearer-API, и что на самом деле отслеживает автоопределение

Режим обслуживания — это переключатель, который останавливает приём Omniflow новых денег, которые он пока не может отработать. Это одна строка в `maintenance_state`, и он намеренно узок: он блокирует новые покупки и удерживает исходящую выдачу доступа, не трогая больше ничего. Расчёты, возвраты и история заказов продолжают работать всё это время, потому что сбой, ради предотвращения которого он существует, — это списание с клиента за доступ, который установка не может выдать, а не заморозка установки, наполовину обработавшей уже принятые ею платежи.

Двигать его могут оператор — из панели или по bearer-API — и автоматическое определение состояния зависимостей, работающее в процессе API.

## Что он блокирует и что намеренно нет

Пока окно открыто, отклоняются ровно два действия. Третье, просмотр, разрешено явно, чтобы клиент по-прежнему мог добраться до объявления, до своих заказов и до поддержки.

| Заблокировано | Продолжает работать |
| - | - |
| Создание заказа — тариф, дополнение, подарок, товар магазина или пополнение кошелька | Расчёт уже начатого платежа |
| Самовыкуп сохранённой корзины, когда баланса хватает | Запись возвратов |
| Проталкивание состояния права на услугу в Remnawave | История заказов, кошелька и платежей |
| | Обращения в поддержку и ответы |
| | Существующий доступ к VPN, который живёт в Remnawave |

Проверка стоит на создании заказа, во всех пяти путях, которые его создают, поэтому новая покупка никогда не может дойти до платёжного провайдера. Это не вторая проверка внутри платёжного слоя — именно поэтому уже созданный заказ остаётся рассчитываемым.

Заказ, оплаченный до открытия окна, по-прежнему рассчитывается, по-прежнему пополняет кошелёк или создаёт своё право на услугу и по-прежнему засчитывается реферальной программе. Ждёт только исходящий вызов Remnawave.

### Удержанная задача выдачи доступа откладывается, а не проваливается

Когда воркер подхватывает задачу выдачи доступа во время окна, он паркует операцию в состоянии `retrying` с `last_error_code = maintenance_active`, **уменьшает счётчик попыток обратно** и откладывает задачу на минуту. Поэтому удержание не стоит ни одной единицы бюджета повторов, сколько бы окно ни длилось, а операция сохраняет свою запись, свой идентификатор корреляции и свой ключ идемпотентности. Она выполняется, как только окно закрывается. См. [Задачи и очереди](https://omniflow-vpn.mintlify.site/ru/operations/jobs-and-queues.md).

## Что видит клиент

**В боте** поверхность покупки заменяется экраном обслуживания в точке входа — до того, как клиент что-то выберет, а не после того, как он нажал «оплатить». Экран показывает ваше объявление на его языке или встроенный локализованный текст, когда объявление пустое, плюс ожидаемое время возврата, если оно задано, с кнопками «повторить» и «открыть поддержку». Неудачное чтение состояния никогда не блокирует клиента: обслуживание — это сигнал оператору, а не отказоустойчивая блокировка, поэтому, если состояние прочитать не удалось, путь покупки остаётся открытым.

**В клиентской веб-панели** дашборд показывает служебное объявление поверх всего остального, потому что инцидент объясняет состояние каждой подписки ниже. Он использует русское объявление для клиента с русской локалью, когда оно было задано, и английское в остальных случаях. Оформление заказа или покупка в магазине, предпринятая вопреки этому, отвечает `503` с кодом `maintenance_active`, который приложение отрисовывает как «покупки приостановлены», а не как общую ошибку. См. [Клиентская веб-панель](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md).

Экраны только для чтения не блокируются никогда. Клиент в окне обслуживания по-прежнему видит свою подписку, свои заказы и свой кошелёк — ровно то, что он и захочет проверить.

## Управление из панели

**1. Откройте System → Health**

Войдите на `/admin` и откройте **System** (право доступа `system.read`), вкладку Health. Она обновляется каждые 30 секунд. Карточка обслуживания показывает бейдж вкл/выкл и, пока режим включён, бейдж **источника**.

**2. Прочитайте источник, прежде чем что-то трогать**

`manual` означает, что кто-то включил режим и выключить его тоже может только кто-то. Любой другой источник означает, что окно открыло автоопределение и оно же закроет его при восстановлении. Показ того, что именно действует, — это то, что не даёт оператору ждать восстановления, которое никогда не наступит; а когда действует автоматическое окно, карточка объясняет, что снятие его вручную заново включит покупки, пока зависимость всё ещё лежит.

**3. Заполните причину и оба объявления**

С правом `system.write` вам доступны поле **Reason**, обязательное всегда, а также **Notice RU** и **Notice EN**, обязательные при включении. Переключатель остаётся заблокированным, пока все три не заполнены. Клиент, который читает только на одном языке и получает пустой экран, обслужен хуже, чем тот, кто получает объявление, которое может прочитать.

**4. Переключите тумблер**

Панель отправляет `PUT /v1/panel/overview/maintenance` с причиной в заголовке `X-Operator-Reason`. Для снятия нужна одна лишь причина.

Изменение, сделанное из панели, **всегда** записывается с `source: "manual"`, даже если зависимость в этот момент лежит. Изменение из панели никогда не должно выдавать себя за автоопределение, потому что снимаются они разными вещами.

Каждый переход пишет строку `maintenance_events` с действием, источником, причиной, типом и идентификатором актора, а также событие аудита — `panel.maintenance.activated` или `panel.maintenance.cleared`, категория `system`. Именно это позволяет оператору объяснить сбой потом. См. [Панель администратора](https://omniflow-vpn.mintlify.site/ru/operations/admin-panel.md).

> [!NOTE]
>
> `expectedReturnAt` принимается API и отрисовывается клиентам, но в панели для него нет элемента управления. Задавайте его по bearer-API, если хотите видеть время возврата в объявлении.

## Управление по bearer-API

Поверхность `/v1/admin` аутентифицируется через `APP_OPERATOR_TOKEN` и существует для скриптов и регламентов. Её правила отличаются от правил панели, и эта разница важна.

```http theme={null}
PUT /v1/admin/maintenance
Authorization: Bearer <operator token>
Content-Type: application/json

{
  "active": true,
  "operatorId": "ops@example.com",
  "reason": "planned Remnawave upgrade",
  "noticeRu": "Проводим плановое обновление. Доступ работает, покупки временно недоступны.",
  "noticeEn": "We are running a planned upgrade. Your access keeps working; purchases are paused.",
  "expectedReturnAt": "2026-08-11T15:00:00Z"
}
```

Снятие — это тот же вызов с `"active": false`.

| Поле | Bearer-API | Панель |
| - | - | - |
| `operatorId` | **Обязателен** — без него `400 missing_operator` | Берётся из сессии |
| Причина | Необязательна | Обязательна, в `X-Operator-Reason` |
| `noticeRu` / `noticeEn` | Необязательны; пустое объявление откатывается на встроенный локализованный текст | Оба обязательны при включении |
| Источник | Всегда принудительно `manual` | Всегда принудительно `manual` |

`GET /v1/admin/maintenance` возвращает текущее состояние, источник, причину, оба объявления, время включения и ожидаемое время возврата.

> [!WARNING]
>
> Поскольку bearer-путь принимает пустую причину и пустые объявления, скрипт может открыть окно, которое ничего не сообщает клиенту и не оставляет объяснения в `maintenance_events`. Задавайте и то и другое и предпочитайте формулировки, которые говорят что-то конкретное о причине и об ожидаемом возврате.

| Эндпоинт | Аутентификация |
| - | - |
| `GET /v1/admin/maintenance` | Bearer `APP_OPERATOR_TOKEN` |
| `PUT /v1/admin/maintenance` | Bearer; требуется `operatorId` |
| `GET /v1/panel/overview/maintenance` | Сессия, право доступа `system.read` |
| `PUT /v1/panel/overview/maintenance` | Сессия, право доступа `system.write`, требуется `X-Operator-Reason` |

## Автоопределение

Определение работает только в процессе **API**, потому что это тот процесс, который и так опрашивает каждую зависимость для своего эндпоинта готовности. Оно переиспользует тот же реестр проб, поэтому переключатель обслуживания и `/readyz` не могут разойтись во мнении о том, работает ли PostgreSQL.

```bash theme={null}
APP_MAINTENANCE_AUTO_DETECT=true
APP_MAINTENANCE_PROBE_SECONDS=30
APP_MAINTENANCE_FAILURE_STREAK=3
APP_MAINTENANCE_RECOVERY_STREAK=3
```

| Переменная | По умолчанию | Действие |
| - | - | - |
| `APP_MAINTENANCE_AUTO_DETECT` | `true` | Может ли автоопределение вообще двигать переключатель. Когда выключено, замеры по-прежнему обновляют метрики; прекращаются только переходы |
| `APP_MAINTENANCE_PROBE_SECONDS` | `30` | Интервал замеров; значение ноль или меньше становится 30 секундами |
| `APP_MAINTENANCE_FAILURE_STREAK` | `3` | Сколько нездоровых замеров подряд нужно, чтобы покупки приостановились; значение меньше 1 поджимается до 1 |
| `APP_MAINTENANCE_RECOVERY_STREAK` | `3` | Сколько здоровых замеров подряд нужно, чтобы автоматическое окно закрылось |

* Окно открывается после `FAILURE_STREAK` нездоровых замеров подряд, так что один тайм-аут никогда не закрывает установку.
* Оно снимается после `RECOVERY_STREAK` здоровых замеров подряд, так что мигающая зависимость не открывает покупки заново в тот момент, когда она один раз ответила.
* **Окно, открытое оператором, никогда не снимается автоматически.** Восстановление снимает только то окно, которое автоопределение открыло само.

### Что оно отслеживает на самом деле

Автоопределение отслеживает **`postgres`, `remnawave` и `valkey`**.

Сбой Valkey уже ломает ограничение частоты запросов и защиту от повтора однократного использования, отказывая закрыто, — см. [Безопасность](https://omniflow-vpn.mintlify.site/ru/operations/security.md#ограничение-частоты-запросов). Без наблюдения за Valkey клиент посреди оформления заказа получал бы противоречивые отказы вместо понятного уведомления об обслуживании. Наблюдение закрывает этот пробел: после той же серии отказов, что и для любой другой зависимости, сбой Valkey приостанавливает новые покупки так же, как это делает сбой PostgreSQL или Remnawave.

Когда одновременно отказали несколько отслеживаемых зависимостей, источник записывается сначала как `database`, затем `remnawave`, затем `valkey`. База данных названа первой, потому что именно она объясняет остальные.

| Источник | Значение |
| - | - |
| `manual` | Оператор, с любой из поверхностей. Никогда не снимается автоматически |
| `database` | PostgreSQL не проходил пробу серию отказов подряд |
| `remnawave` | Панель Remnawave не проходила пробу серию отказов подряд |
| `valkey` | Valkey не проходил пробу серию отказов подряд |

Проба Remnawave — это аутентифицированный вызов, поэтому отозванный или истёкший токен панели считается нездоровой зависимостью и, после набора серии, приостановит покупки. Обычно это то поведение, которого вы и хотите, — выдача доступа всё равно бы провалилась, — но это значит, что небрежно проведённая смена токена закрывает магазин. См. [Интеграция с Remnawave](https://omniflow-vpn.mintlify.site/ru/integrations/remnawave.md).

## Как за этим следить

Каждый замер выставляет `omniflow_maintenance_mode` в `1` или `0` и `omniflow_dependency_up` по каждой пробе, и только в процессе API. Настройте оповещение на то, что датчик остаётся в `1` дольше запланированного окна: сигнал — это автоопределение, открывшее окно, которого вы не планировали, а инцидент — это то, что окно не закрывается. См. [Наблюдаемость](https://omniflow-vpn.mintlify.site/ru/operations/observability.md).

Каждый переход также логируется на уровне warn:

```json theme={null}
{"level":"WARN","msg":"maintenance mode changed","active":true,"source":"remnawave","reason":"remnawave is unavailable"}
```

Полоса инцидентов в панели на `/admin` читает строки `maintenance_events`, так что окно, которое открылось и закрылось ночью, утром всё ещё видно.

Каждый переход — автоматический или ручной — также публикуется в топик `incident` операторской группы. Контроллер работает в процессе `api`, а группа принадлежит `bot`, поэтому api ставит уведомление в очередь `operator_notifications`, а бот его доставляет; если операторская группа не настроена, уведомление просто ждёт. См. [Уведомления оператора](https://omniflow-vpn.mintlify.site/ru/operations/operator-notifications.md).

## Отказы и краевые случаи

* Панель: отсутствующая причина или включение без обоих объявлений — это ошибка валидации, и переключатель не двигается.
* Bearer: отсутствующий `operatorId` — это `400 missing_operator`; отклонённое изменение состояния — `422 maintenance_rejected`.
* Покупка, предпринятая во время окна, падает с `maintenance_active` — `503` в клиентском веб-API, экран обслуживания в боте.
* Строка `maintenance_state` создаётся при первом чтении, поэтому свежая установка сообщает «неактивен», а не «отсутствует».
* Таблица обеспечивает `CHECK (NOT active OR activated_at IS NOT NULL)`: активное окно всегда знает, когда оно началось.
* Снятие окна ничего не отменяет, не возвращает и не просрочивает. Удержанные операции выдачи доступа просто снова становятся к исполнению.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.