#!/usr/bin/env bash # device-install.sh — выполняется НА устройстве (заливается через adb). # Offline-установка пакетов + полный провижининг по образцу README. # Идемпотентен: повторный запуск безопасен, существующий config не трогается. set -euo pipefail BUNDLE_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT=/opt/buttontask USER_NAME=buttontask VER="$(cat "$BUNDLE_DIR/VERSION" 2>/dev/null || echo 0.0)" log() { printf '\033[1;32m[install]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; } [ "$(id -u)" -eq 0 ] || die "нужны root-права (adb root или sudo на устройстве)" # --------------------------------------------------------------------------- # 1. Offline-пакеты # --------------------------------------------------------------------------- if ls "$BUNDLE_DIR"/debs/*.deb >/dev/null 2>&1; then log "Установка пакетов offline (dpkg)..." # Конфликтующие альтернативы (напр. *-gles, elogind) dpkg отвергнет — это нормально: # нужное ставится, лишнее нам не нужно. Код возврата dpkg НЕ фатальный; # реальный рантайм проверяем ниже. dpkg -i "$BUNDLE_DIR"/debs/*.deb || true dpkg -i "$BUNDLE_DIR"/debs/*.deb >/dev/null 2>&1 || true # второй проход — порядок ldconfig else warn "каталог debs/ пуст — пропускаю установку пакетов" fi # --- проверяем, что РЕАЛЬНО нужное на месте (а не код возврата dpkg) --- command -v python3 >/dev/null || die "python3 не установился" python3 -c 'import flask' 2>/dev/null || die "python3-flask не установился" command -v nmcli >/dev/null || warn "nmcli отсутствует — сетевые настройки из web не заработают" ls /usr/lib/*/qt5/qml/QtQuick/Controls.2 >/dev/null 2>&1 \ || warn "QML-модуль QtQuick.Controls 2 не найден — UI может не подняться" # --------------------------------------------------------------------------- # 2. Системный пользователь # --------------------------------------------------------------------------- id "$USER_NAME" >/dev/null 2>&1 || useradd -r -s /bin/false "$USER_NAME" # группы для eglfs/KMS/тача usermod -aG video,render,input "$USER_NAME" 2>/dev/null || true # --------------------------------------------------------------------------- # 3. Версионированная раскладка # --------------------------------------------------------------------------- install -d "$ROOT"/versions/"$VER" "$ROOT"/config "$ROOT"/icons "$ROOT"/run "$ROOT"/scripts [ -f "$BUNDLE_DIR/app/ButtonTask" ] || die "нет бинарника app/ButtonTask в бандле" install -m0755 "$BUNDLE_DIR/app/ButtonTask" "$ROOT/versions/$VER/ButtonTask" cp -a "$BUNDLE_DIR/webconfig" "$ROOT/versions/$VER/" ln -sfn "$ROOT/versions/$VER" "$ROOT/current" # config — по умолчанию не перезатираем (upgrade-safe); BUTTONTASK_RESET_CONFIG=1 — из бандла reset_cfg="${BUTTONTASK_RESET_CONFIG:-0}" case "$reset_cfg" in 1|yes|true|TRUE|Yes|YES) reset_cfg=1 ;; *) reset_cfg=0 ;; esac if [ "$reset_cfg" = "1" ]; then if [ -f "$BUNDLE_DIR/config/config.json" ]; then cp -a "$BUNDLE_DIR/config/config.json" "$ROOT/config/" log "config.json перезаписан из бандла (полный сброс конфига)" else warn "BUTTONTASK_RESET_CONFIG=1, но config.json в бандле нет" fi elif [ -f "$ROOT/config/config.json" ]; then log "config.json уже есть — оставляю как есть" elif [ -f "$BUNDLE_DIR/config/config.json" ]; then cp -a "$BUNDLE_DIR/config/config.json" "$ROOT/config/" log "config.json установлен" else warn "config.json нет ни на устройстве, ни в бандле — приложение создаст дефолтный при старте" fi # network — не перезатираем существующий if [ -f "$ROOT/config/network.json" ]; then log "network.json уже есть — оставляю как есть" elif [ -f "$BUNDLE_DIR/config/network.json" ]; then cp -a "$BUNDLE_DIR/config/network.json" "$ROOT/config/" log "network.json установлен" else warn "network.json нет в бандле — eth0 останется на DHCP" fi # default/shared icons — не удаляем пользовательские иконки, только докладываем новые if ls "$BUNDLE_DIR"/icons/* >/dev/null 2>&1; then cp -an "$BUNDLE_DIR"/icons/. "$ROOT/icons/" log "иконки установлены" else warn "иконок в бандле нет" fi # --------------------------------------------------------------------------- # 4. Привилегированные хелперы + sudoers # --------------------------------------------------------------------------- for s in bt-netconfig bt-netconfig-boot bt-update bt-service \ bt-settime bt-reboot bt-access bt-brightness bt-set-master; do [ -f "$BUNDLE_DIR/scripts/$s" ] || die "нет scripts/$s в бандле" install -m0755 "$BUNDLE_DIR/scripts/$s" "$ROOT/scripts/" done sed -i 's/\r$//' "$ROOT"/scripts/bt-* 2>/dev/null || true # на случай CRLF install -m0440 "$BUNDLE_DIR/scripts/buttontask.sudoers" /etc/sudoers.d/buttontask sed -i 's/\r$//' /etc/sudoers.d/buttontask 2>/dev/null || true visudo -cf /etc/sudoers.d/buttontask || die "sudoers некорректен" # --------------------------------------------------------------------------- # 5. Runtime-каталог (XDG_RUNTIME_DIR). /tmp очищается на ребуте — # пересоздаём через tmpfiles, чтобы «и всё работает» после перезагрузки. # --------------------------------------------------------------------------- cat >/etc/tmpfiles.d/buttontask.conf < override.conf (eventN зависит от платы) # --------------------------------------------------------------------------- detect_touch() { awk 'BEGIN{RS="";FS="\n"} { ev=""; abs=""; for (i=1;i<=NF;i++) { if ($i ~ /Handlers=/ && match($i,/event[0-9]+/)) ev=substr($i,RSTART,RLENGTH); if ($i ~ /^B: ABS=/) abs=$i; } if (ev!="" && abs!="" && abs !~ /ABS=0+$/) { print ev; exit } }' /proc/bus/input/devices 2>/dev/null } TOUCH_EV="$(detect_touch || true)" EX="$BUNDLE_DIR/systemd/override.conf.example" if [ -n "$TOUCH_EV" ] && [ -f "$EX" ]; then install -d /etc/systemd/system/buttontask.service.d sed "s/event[0-9]\+/$TOUCH_EV/g" "$EX" \ > /etc/systemd/system/buttontask.service.d/override.conf log "тачскрин: /dev/input/$TOUCH_EV -> override.conf" else warn "тачскрин не определён автоматически — при необходимости задайте override.conf вручную" fi # --------------------------------------------------------------------------- # 9. eglfs забирает KMS/DRM напрямую -> выключаем display-manager # --------------------------------------------------------------------------- systemctl disable display-manager 2>/dev/null || true systemctl stop display-manager 2>/dev/null || true # --------------------------------------------------------------------------- # 10. Включаем сервисы (старт — после ребута) # --------------------------------------------------------------------------- systemctl daemon-reload systemctl enable buttontask buttontask-web buttontask-network >/dev/null 2>&1 || \ systemctl enable buttontask buttontask-web >/dev/null 2>&1 || \ die "не удалось включить сервисы (проверьте имена юнитов)" # применить сеть сразу (не ждать ребута), если есть сохранённый профиль if [ -f "$ROOT/config/network.json" ] && [ -x "$ROOT/scripts/bt-netconfig-boot" ]; then if BUTTONTASK_NET_ACTIVATE_TIMEOUT=30 "$ROOT/scripts/bt-netconfig-boot"; then log "сеть применена из network.json" else warn "не удалось применить network.json — проверьте nmcli и кабель" fi fi log "версия $VER развёрнута" echo "INSTALL-OK"