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

7.1 KiB
Raw Blame History

title, tags, updated, status
title tags updated status
План: защита modbus-bridge от гонки с udev (ZONT датчики)
plan
family
homeautomation
zont
modbus
2026-08-25 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):

#!/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:
    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).

Связанные заметки