Files
2026-08-24 16:40:48 +03:00

196 lines
10 KiB
Bash

#!/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 <<EOF
d /tmp/runtime-buttontask 0700 $USER_NAME $USER_NAME -
EOF
systemd-tmpfiles --create /etc/tmpfiles.d/buttontask.conf || true
# ---------------------------------------------------------------------------
# 6. Владелец: всё buttontask, кроме root-хелперов
# ---------------------------------------------------------------------------
chown -R "$USER_NAME:$USER_NAME" "$ROOT"
chown root:root "$ROOT"/scripts/bt-*
# мастер-пароль (аварийный вход web + Qt) — один раз при первой установке
if [ "$reset_cfg" = "1" ] && [ -f "$BUNDLE_DIR/config/.master" ]; then
install -m0600 -o "$USER_NAME" -g "$USER_NAME" \
"$BUNDLE_DIR/config/.master" "$ROOT/config/.master"
log "мастер-пароль перезаписан из бандла (полный сброс конфига)"
elif [ -f "$ROOT/config/.master" ]; then
log "config/.master уже есть — оставляю как есть"
elif [ -f "$BUNDLE_DIR/config/.master" ]; then
install -m0600 -o "$USER_NAME" -g "$USER_NAME" \
"$BUNDLE_DIR/config/.master" "$ROOT/config/.master"
log "мастер-пароль установлен из бандла"
else
warn "мастер-пароль не задан — после установки: sudo $ROOT/scripts/bt-set-master 'секрет'"
fi
# ---------------------------------------------------------------------------
# 7. systemd-юниты
# ---------------------------------------------------------------------------
# все юниты из бандла (buttontask, buttontask-web и, если есть, buttontask-network)
install -m0644 "$BUNDLE_DIR"/systemd/*.service /etc/systemd/system/
# ---------------------------------------------------------------------------
# 8. Авто-детект тачскрина -> 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"