196 lines
10 KiB
Bash
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"
|