Files
obsidian-vault/family/plans/zont-modbus-bridge-udev-race-protection.md
T

85 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` монтирует устройство ДО запуска процесса** — если `<src>` не существует на _момент создания/старта контейнера_, контейнер вообще не стартует, и `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