From aafbbc64d3f04bddbcd0e429f899175dc942ece8 Mon Sep 17 00:00:00 2001 From: ZeroDay Date: Thu, 23 Jul 2026 09:09:21 +0300 Subject: [PATCH] =?UTF-8?q?Runbook=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20OPi=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B9=20SD-=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=B5=20+=20systemd-=D1=8E=D0=BD=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Инцидент 22.07: ФС ушла в read-only — захват кадра и SSH отвалились, heartbeat и MQTT-вес продолжали работать. В репозитории не хватало юнита и инструкции подъёма с нуля. - orange_pi/scales.service (восстановлен по systemctl status) - RECOVERY.md: установка, проверки, продление жизни карты, диагностика через MQTT без SSH --- RECOVERY.md | 106 +++++++++++++++++++++++++++++++++++++++ orange_pi/scales.service | 28 +++++++++++ 2 files changed, 134 insertions(+) create mode 100644 RECOVERY.md create mode 100644 orange_pi/scales.service diff --git a/RECOVERY.md b/RECOVERY.md new file mode 100644 index 0000000..07db9e4 --- /dev/null +++ b/RECOVERY.md @@ -0,0 +1,106 @@ +# Восстановление Orange Pi на новой SD-карте + +Runbook на случай, когда карта деградировала и устройство надо поднять с нуля. +Составлен во время инцидента 22.07.2026: ФС ушла в read-only — захват кадра и SSH +отвалились, при этом heartbeat и публикация веса в MQTT продолжали работать. + +## Что взять на пост + +- новая SD-карта, **high-endurance** (Samsung PRO Endurance / SanDisk Max Endurance); + 32 ГБ хватает с запасом — занято ~12% +- образ Armbian для Orange Pi PC, записанный заранее +- `orange_pi/scales.py`, `orange_pi/outbox.py`, `orange_pi/scales.service` из этого репозитория +- старую карту НЕ выбрасывать: с неё может понадобиться вытащить остатки очереди outbox + +## 1. Система + + apt update + apt install -y python3 python3-pip ffmpeg + pip3 install paho-mqtt # на свежих Debian/Armbian добавить --break-system-packages + +Больше внешних зависимостей нет — всё остальное в скрипте из stdlib. + +## 2. Файлы + +Рабочий файл на устройстве — именно `/root/scales.py`; репозиторий на OPi не клонируется, +файл доставляется вручную. `outbox.py` кладётся рядом, в `/root`. + + cp scales.py outbox.py /root/ + +Актуальную версию можно скачать прямо с сервера: + + curl -s -o /root/scales.py https://scales.zeroday.su/testphotos/scales.py + +## 3. Сервис + + cp scales.service /etc/systemd/system/scales.service + systemctl daemon-reload + systemctl enable --now scales.service + systemctl status scales.service --no-pager + +## 4. Проверки после старта + + # в логе должны появиться MQTT1/MQTT2, GPIO, OUTBOX, HEARTBEAT + journalctl -u scales.service -n 30 --no-pager + + # весовой терминал + ls -l /dev/ttyUSB0 + + # камера — тот же вызов, что внутри grab_frame() + ffmpeg -y -loglevel error -rtsp_transport tcp -i "" \ + -frames:v 1 -update 1 -q:v 2 -f image2 /tmp/scale_frame.jpg && ls -l /tmp/scale_frame.jpg + + # офлайн-распознавание на боксе + curl -s http://192.168.20.4:5010/health + + # сервер принимает (ожидаемый ответ: {"error":"event_id required"}) + curl -s -X POST -H "Content-Type: application/json" -d '{}' https://scales.zeroday.su/api/local-plate + +Финальная проверка — кнопка «Тест-снимок» в панели: фото должно появиться в заездах. + +## 5. Чтобы карта прожила дольше + +Главный убийца SD-карт — постоянная запись логов. + + mkdir -p /etc/systemd/journald.conf.d + printf '[Journal]\nStorage=volatile\nRuntimeMaxUse=32M\n' > /etc/systemd/journald.conf.d/volatile.conf + systemctl restart systemd-journald + +Убедиться, что `/tmp` — tmpfs (туда пишется кадр перед отправкой): + + findmnt /tmp + +Если нет — добавить в `/etc/fstab`: + + tmpfs /tmp tmpfs defaults,nosuid,nodev,size=128M 0 0 + +На перспективу: перенос системы на USB-SSD или eMMC снимает проблему целиком. + +## Диагностика без SSH + +Устройство управляется через MQTT-брокер даже когда SSH недоступен +(на сервере: `localhost:1883`, пользователь `scales-server`). + + # живой heartbeat раз в 30 с — значит процесс жив + mosquitto_sub -h 127.0.0.1 -p 1883 -u scales-server -P '<пароль>' -t 'scale/device/status' -v + + # команды: ping (мгновенный статус), test-shot (проверка камеры), reboot + mosquitto_pub -h 127.0.0.1 -p 1883 -u scales-server -P '<пароль>' -t 'scale/device/cmd' -m '{"cmd":"ping"}' + +Как читать симптомы: + +| признак | что значит | +|---|---| +| heartbeat идёт, на `ping` отвечает мгновенно | процесс жив, ресурсы в порядке | +| `outbox_pending: 0`, но заездов на сервере нет | `capture_and_send` вышел до постановки в очередь — не получен кадр | +| `test-shot` не даёт фото, а RTSP из VLC играет | причина локальная на OPi: запись в `/tmp` или ffmpeg | +| SSH рвёт соединение на key exchange | sshd не может создать сессию — характерно для read-only ФС | + +Совпадение последних трёх — почти наверняка карта. + +## Известная недоработка + +`capture_and_send()` при неудачном захвате кадра выходит по `return` **до** постановки +события в очередь. Значит отказ камеры теряет не фото, а всю запись о взвешивании — +именно так были потеряны заезды 20.07.2026. Вес должен доезжать до сервера и без фото; +поправить при ближайшем доступе к устройству. diff --git a/orange_pi/scales.service b/orange_pi/scales.service new file mode 100644 index 0000000..5558a9c --- /dev/null +++ b/orange_pi/scales.service @@ -0,0 +1,28 @@ +; systemd-юнит сервиса весов на Orange Pi. +; ВНИМАНИЕ: восстановлен по выводу `systemctl status` (22.07.2026), а не скопирован +; с устройства — SSH был недоступен. Точно известны: Description, ExecStartPre, +; ExecStart, enabled. Restart/WorkingDirectory — разумные значения, сверить при случае. +; +; Установка: +; cp scales.service /etc/systemd/system/scales.service +; systemctl daemon-reload && systemctl enable --now scales.service + +[Unit] +Description=Scales MQTT Service +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=root +WorkingDirectory=/root +; весовой терминал А9: 9600 8N1, порт настраивается до старта скрипта +ExecStartPre=/bin/stty -F /dev/ttyUSB0 raw 9600 cs8 -cstopb -parenb +ExecStart=/usr/bin/python3 /root/scales.py +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target