Runbook восстановления OPi на новой SD-карте + systemd-юнит
Инцидент 22.07: ФС ушла в read-only — захват кадра и SSH отвалились, heartbeat и MQTT-вес продолжали работать. В репозитории не хватало юнита и инструкции подъёма с нуля. - orange_pi/scales.service (восстановлен по systemctl status) - RECOVERY.md: установка, проверки, продление жизни карты, диагностика через MQTT без SSH
This commit is contained in:
+106
@@ -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 "<RTSP_URL из scales.py>" \
|
||||||
|
-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. Вес должен доезжать до сервера и без фото;
|
||||||
|
поправить при ближайшем доступе к устройству.
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user