Embedded

Удалённое управление и обновление Qt-приложений на Raspberry Pi по сети

Введение

Развёрнутое в kiosk-режиме (статья 348) устройство часто физически недоступно или находится в труднодоступном месте — обновление приложения, диагностика проблем и удалённое управление должны быть возможны по сети. В статье разберём практичные подходы — SSH-доступ, удалённое обновление приложения, и удалённый мониторинг состояния устройства.

Концепция

SSH — базовый, универсальный механизм удалённого доступа к Linux-системе Raspberry Pi, дающий полный административный доступ (в отличие от ограниченного, специфичного API). Обновление приложения по сети может реализовываться через простой скрипт, копирующий новую версию бинарного файла через scp/rsync с последующим перезапуском systemd-сервиса, или через более продвинутый механизм OTA-обновления, аналогичный концепции из статьи 317 (ESP32), адаптированный для полноценной Linux-системы (например, через атомарное переключение между двумя установленными версиями для безопасного откэта при неудачном обновлении).

Пример кода: Удалённое управление и

# Включение SSH на Raspberry Pi (через raspi-config или создание пустого файла ssh в загрузочном разделе)
sudo raspi-config  # Interface Options -> SSH -> Enable
# или, при первой настройке через флешку без доступа к raspi-config:
touch /boot/ssh

# Подключение с удалённого компьютера
ssh pi@raspberrypi.local
#!/bin/bash
# deploy.sh — скрипт обновления приложения на удалённом Raspberry Pi
set -e

REMOTE_HOST="pi@raspberrypi.local"
REMOTE_APP_DIR="/home/pi/MyKioskApp"
LOCAL_BUILD="build-pi/MyKioskApp"

echo "Копирование новой версии приложения..."
scp "$LOCAL_BUILD" "$REMOTE_HOST:$REMOTE_APP_DIR/MyKioskApp.new"

echo "Атомарная замена и перезапуск сервиса..."
ssh "$REMOTE_HOST" "
    mv $REMOTE_APP_DIR/MyKioskApp $REMOTE_APP_DIR/MyKioskApp.backup &&
    mv $REMOTE_APP_DIR/MyKioskApp.new $REMOTE_APP_DIR/MyKioskApp &&
    sudo systemctl restart kiosk-app.service
"

echo "Обновление завершено."
// Удалённый мониторинг состояния через простой HTTP-эндпоинт здоровья (health check),
// дополняющий полный административный доступ через SSH более лёгким, программным способом проверки
#include <QHttpServer> // аналогично основному циклу статей про сеть, статьи 1-23

void setupHealthCheckEndpoint(QHttpServer &server)
{
    server.route("/health", [](const QHttpServerRequest &) {
        QJsonObject status;
        status["status"] = "ok";
        status["uptime_seconds"] = QDateTime::currentSecsSinceEpoch() - applicationStartTime();
        status["version"] = APP_VERSION;
        return QHttpServerResponse(QJsonDocument(status).toJson());
    });
}
# Скрипт для отката к предыдущей версии при неудачном обновлении
ssh pi@raspberrypi.local "
    sudo systemctl stop kiosk-app.service &&
    mv /home/pi/MyKioskApp/MyKioskApp /home/pi/MyKioskApp/MyKioskApp.failed &&
    mv /home/pi/MyKioskApp/MyKioskApp.backup /home/pi/MyKioskApp/MyKioskApp &&
    sudo systemctl start kiosk-app.service
"

Пояснения к коду

deploy.sh показывает паттерн атомарного обновления — новый бинарный файл сначала полностью копируется под временным именем (MyKioskApp.new), и только после успешного завершения копирования происходит атомарная замена (mv, операция переименования файла на одной файловой системе — атомарна на уровне ядра Linux) — это предотвращает ситуацию, когда процесс копирования прерывается на середине (обрыв сети), оставляя приложение в частично испорченном, нерабочем состоянии. Сохранение предыдущей версии как .backup перед заменой даёт простой, быстрый путь откэта, показанный в последнем примере — что особенно важно для устройства, развёрнутого удалённо, где физический доступ для исправления неудачного обновления может быть затруднён или недоступен вовсе.

Подводные камни

  • Прерывание процесса обновления (scp/копирование) посередине из-за обрыва сетевого соединения без атомарной замены файла, что оставляет приложение в виде частично скопированного, повреждённого исполняемого файла — без паттерна «скопировать под временным именем, затем атомарно переименовать», показанного в примере, прерванное обновление может вывести устройство из строя до следующего физического вмешательства, особенно проблематично для удалённо расположенных устройств.
  • Использование пароля вместо SSH-ключей для аутентификации, особенно для устройства, доступного через интернет (а не только в пределах локальной, доверенной сети) — пароли подвержены брутфорс-атакам, и для production-развёртывания SSH-доступ должен настраиваться через ключи (ssh-copy-id) с полным отключением парольной аутентификации в конфигурации SSH-сервера, аналогично общим принципам безопасности из статей 215/310/318/329.
  • Развёртывание устройства, доступного по SSH непосредственно из интернета, без дополнительной защиты (VPN, проксирование через защищённый bastion-хост, нестандартный порт с fail2ban) — устройства, доступные по SSH напрямую из публичного интернета, регулярно подвергаются автоматизированному сканированию и попыткам брутфорса; для устройств, не предназначенных для прямого доступа из интернета, стоит использовать VPN или иное промежуточное звено, а не открывать SSH-порт непосредственно наружу.
  • Отсутствие отдельного, независимого от обновляемого приложения способа диагностики устройства, если что-то пойдёт не так именно с самим SSH-доступом или сетевой конфигурацией после неудачного обновления — для критичных, удалённо расположенных развёртываний стоит предусмотреть резервный канал доступа (например, отдельный, не затрагиваемый обычными обновлениями приложения сервис мониторинга, или физический watchdog, перезагружающий устройство при потере сетевой связности на длительное время).