From 669a9c3483279a5e072f9420ad9699715b6e0c7e Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 25 Aug 2026 17:29:59 +0600 Subject: [PATCH] [2026-08-25] eagle: family/how-to/zont-modbus-bridge-udev-race-protection.md family/plans/zont-modbus-bridge-udev-race-protection.md --- ...zont-modbus-bridge-udev-race-protection.md | 72 ++++++++++++++++ ...zont-modbus-bridge-udev-race-protection.md | 84 ------------------- 2 files changed, 72 insertions(+), 84 deletions(-) create mode 100644 family/how-to/zont-modbus-bridge-udev-race-protection.md delete mode 100644 family/plans/zont-modbus-bridge-udev-race-protection.md diff --git a/family/how-to/zont-modbus-bridge-udev-race-protection.md b/family/how-to/zont-modbus-bridge-udev-race-protection.md new file mode 100644 index 00000000..c6d128dc --- /dev/null +++ b/family/how-to/zont-modbus-bridge-udev-race-protection.md @@ -0,0 +1,72 @@ +--- +status: implemented +tags: + - family + - homeautomation + - zont + - modbus +title: Защита modbus-bridge от гонки с udev (ZONT датчики) +updated: '2026-08-25' +--- +# Защита modbus-bridge/mbusd от гонки с udev (ZONT датчики) + +> Статус: **внедрено** (2026-08-25, шаги 1–3). Датчики в ZONT снова отвечают, скрипт и init script на месте. + +## Проблема +ZONT (192.168.0.10) — Modbus master на RS-485 шине `ttyZONT`. 485-датчики: Dining=1, Kids=2, Bedroom=3. +Контейнер `modbus-bridge` сниффит их и подставляет виртуальные slaves 101/102/103 → ZONT опрашивает как внешние датчики. Если modbus-bridge не слушает шину → **ZONT показывает датчики «недоступными»**. + +**Первопричина (2026-08-25):** гонка загрузки TrueNAS — docker стартовал раньше udev, `/dev/ttyZONT` и `/dev/ttyVent` ещё не существовали → контейнеры `modbus-bridge` (Exit 128) и `mbusd` (Exit 255) упали на старте: +`error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory` (RestartCount=0). +Фикс был: ручной `docker start modbus-bridge mbusd`. + +## Почему контейнеры сами НЕ поднялись (RestartCount=0) +`restart: unless-stopped`/`always` ретраит в ДВУХ случаях: +1. Процесс контейнера **стартовал и завершился** с ненулевым кодом → демон ретраит с backoff. +2. **Перезапуск docker daemon** → демон пробует поднять `unless-stopped`/`always` контейнеры. + +Наш случай — ТРЕТИЙ, особый: контейнер **вообще не стартовал** — ошибка на этапе монтирования устройства. +- Нет процесса, который «завершился бы» → **restart policy не на что применять** → `RestartCount=0`, демон не ретраит. +- После падения (13:24) демон не перезапускался → второго триггера не было. +**Вывод:** отказ на этапе mount устройства НЕ перезапускается restart policy. Авто-подъём гарантирован только скриптом, ждущим устройства. + +## Ключевое ограничение (задержка в entrypoint НЕ помогает) +У обоих контейнеров устройство проброшено через `devices: /dev/ttyZONT:/dev/ttyUSB0` / `devices: /dev/ttyVent:/dev/ttyUSB0`. +**Docker при `start` монтирует устройство ДО запуска процесса** — если `` отсутствует на момент старта, контейнер вообще не стартует, и `sleep` внутри entrypoint/CMD **не выполняется**. Поэтому решение — на уровне init скрипта. + +## Внедрённое решение + +### 1. Скрипт `/mnt/RED_2TB/system/start-modbus.sh` (root, `-rw-r--r--`) +Ожидает появления tty-алиасов от udev (до ~50с), затем запускает контейнеры: +```bash +#!/bin/bash +# Ждём tty-алиасы от udev (макс ~25с), затем стартуем modbus контейнеры. +for i in $(seq 1 50); do + [ -e /dev/ttyZONT ] && [ -e /dev/ttyVent ] && break + sleep 1 +done +sleep 2 +docker start modbus-bridge mbusd 2>/dev/null +``` + +### 2. Init script (TrueNAS, id=3) +- **when:** POSTINIT, **type:** COMMAND, **timeout:** 30, **enabled:** true +- **command:** `bash /mnt/RED_2TB/system/start-modbus.sh` +- **comment:** `Start modbus-bridge/mbusd after udev tty aliases` + +### Порядок POSTINIT скриптов +| id | command | comment | enabled | +|----|---------|---------|---------| +| 1 | `cp .../99-tty-alias.rules /etc/udev/rules.d/ && udevadm control --reload-rules && udevadm trigger` | Map ttyUSB | ✅ | +| 2 | `bash /mnt/RED_2TB/system/tunnel.sh &` | — | ✅ | +| 3 | `bash /mnt/RED_2TB/system/start-modbus.sh` | Start modbus-bridge/mbusd after udev tty aliases | ✅ | + +## Проверка +- `/dev/ttyZONT` → `ttyUSB1` (CH340), `/dev/ttyVent` → `ttyUSB0` — симлинки появляются после POSTINIT id=1. +- После reboot: `docker ps` — modbus-bridge и mbusd Up; датчики в ZONT отвечают. +- Просмотр init scripts: `midclt call initshutdownscript.query` +- Откат: `midclt call initshutdownscript.delete ` + удалить скрипт с диска. + +## Связанные заметки +- [[family/how-to/home-automation.md]] — карта slave ID, ZONT, датчики +- [[family/how-to/truenas-infrastructure.md]] — docker, modbus-bridge, mbusd, udev rules, init scripts diff --git a/family/plans/zont-modbus-bridge-udev-race-protection.md b/family/plans/zont-modbus-bridge-udev-race-protection.md deleted file mode 100644 index c95e55d6..00000000 --- a/family/plans/zont-modbus-bridge-udev-race-protection.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: 'План: защита modbus-bridge от гонки с udev (ZONT датчики)' -tags: - - plan - - family - - homeautomation - - zont - - modbus -updated: '2026-08-25' -status: proposed ---- -# План: защита modbus-bridge/mbusd от гонки с udev (после рестарта TrueNAS) - -> Статус: **план на согласование** (2026-08-25), ничего не применено. -> Задача: исключить повторение «485-датчики недоступные в ZONT» после перезагрузки TrueNAS. - -## Контекст / причины -- ZONT (192.168.0.10) — Modbus master на RS-485 шине `ttyZONT`. 485-датчики: Dining=1, Kids=2, Bedroom=3. -- Контейнер `modbus-bridge` сниффит их и подставляет виртуальные slaves 101/102/103 → ZONT опрашивает как внешние датчики. -- Если modbus-bridge не слушает шину → ZONT показывает датчики «недоступные». -- Первопричина 2026-08-25: **гонка загрузки** — docker стартовал раньше udev, `/dev/ttyZONT` и `/dev/ttyVent` ещё не существовали → контейнеры упали на старте: - `error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory` (ExitCode 128/255, RestartCount=0). -- `restart: unless-stopped` **не перезапускает** контейнер, упавший на этапе `docker start` (binding device fail) — отказ до старта процесса. - -## Ключевое ограничение (почему задержка в entrypoint НЕ поможет в чистом виде) -У modbus-bridge и mbusd устройство проброшено через `devices: /dev/ttyZONT:/dev/ttyUSB0` / `devices: /dev/ttyVent:/dev/ttyUSB0`. -**Docker при `start` монтирует устройство ДО запуска процесса** — если `` не существует на _момент создания/старта контейнера_, контейнер вообще не стартует, и `sleep` внутри entrypoint/CMD никогда не выполнится. -→ Простой «sleep N в entrypoint» проблему НЕ решает. - -## Почему контейнеры сами НЕ поднялись (RestartCount=0) — разбор -`restart: unless-stopped`/`always` ретраит в ДВУХ случаях: -1. **Процесс контейнера стартовал и завершился** с ненулевым кодом → демон ретраит с backoff (RestartCount растёт). -2. **Перезапуск самого docker daemon** → демон пробует поднять `unless-stopped`/`always` контейнеры. - -Наш случай — ТРЕТИЙ, особый: контейнер **вообще не стартовал** — ошибка на этапе монтирования устройства: -`error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory`. -- Нет процесса, который «завершился бы» — docker не смог примонтировать устройство, процесс не запущен вовсе. -- Раз нет завершённого процесса, **restart policy не на что применять** → `RestartCount=0`, демон не ретраит. -- После падения (13:24) демон НЕ перезапускался (docker.pid с 07:14) → второго триггера не было. - -Вывод: **отказ на этапе mount устройства НЕ перезапускается restart policy**. Авто-подъём при перезагрузке гарантирован только если устройства существуют на момент старта демона. Иначе — контейнеры молча остаются в exited до ручного `docker start`. - -## Решение (рекомендуемое): POSTINIT init script с ожиданием + docker start -Уже есть init script id=1 «Map ttyUSB» (POSTINIT), который копирует udev-правила и делает `udevadm trigger`. Он НЕ ждёт создания симлинков и не запускает докер-контейнеры. - -Добавить новый POSTINIT init script (COMMAND, when=POSTINIT, enabled=true), который: -1. Ждёт появления `/dev/ttyZONT` и `/dev/ttyVent` (poll до ~20 сек). -2. Как только оба есть — `docker start modbus-bridge mbusd`. -3. Если docker ещё не готов — также пробует переждать. - -Пример скрипта (положить в `/mnt/RED_2TB/system/start-modbus.sh`): -```bash -#!/bin/bash -# Ожидание tty-алиасов от udev (макс ~25 сек) -for i in $(seq 1 25); do - [ -e /dev/ttyZONT ] && [ -e /dev/ttyVent ] && break - sleep 1 -done -sleep 2 # небольшая пауза на устойчивость udev -docker start modbus-bridge mbusd 2>/dev/null -``` -Команда в TrueNAS init: `bash /mnt/RED_2TB/system/start-modbus.sh` -Timeout скрипта в TrueNAS: `30`. - -> Альтернатива (не рекомендуется): поменять `devices:` на прямой `/dev/ttyUSB1`/`/dev/ttyUSB0` — но номер ttyUSB может прыгать, алиасы для того и нужны. - -> Примечание: менять Dockerfile/entrypoint и/или `restart: always` здесь **недостаточно** — отказ на этапе mount устройства restart policy не обрабатывает. - -## Шаги внедрения (после согласования) -1. Создать `/mnt/RED_2TB/system/start-modbus.sh` на TrueNAS (chmod +x). -2. Добавить init script через midclt: - ```bash - midclt call initshutdownscript.create '{"type":"COMMAND","command":"bash /mnt/RED_2TB/system/start-modbus.sh","when":"POSTINIT","enabled":true,"timeout":30,"comment":"Start modbus-bridge/mbusd after udev tty aliases"}' - ``` -3. Проверить: `midclt call initshutdownscript.query` -4. Проверить после reboot: контейнеры Up, `ttyZONT`/`ttyVent` созданы, датчики в ZONT отвечают. - -## Вердикт по исходному вопросу «добавить задержку в entrypoint» -- Задержка внутри entrypoint/CMD для этих контейнеров **не выполняется** из-за device mount до старта процесса → этот путь отпадает. -- Правильный эквивалент «задержки до готовности» — скрипт выше на уровне TrueNAS init (ожидание устройств, затем `docker start`). - -## Связанные заметки -- [[family/how-to/home-automation.md]] — карта slave ID, ZONT, датчики -- [[family/how-to/truenas-infrastructure.md]] — docker, modbus-bridge, mbusd, udev rules, init scripts