9b87751ab8
План 5 ч.2: поднял Lima-VM и довёл сквозной E2E до зелёного из чистого yaml (just vm-reset && just e2e — exit 0). Приёмка §9.4 (v0.1 + v0.6 + шагающий скелет). Shell (lib+bin split): - режим --screenshot <path>: headless software-render первого кадра в PNG (Slint software-renderer, без дисплея/композитора, §6); TDD-тест «кадр не пустой + тема отражена», зелёный и на dev-Mac, и в VM (Linux). - shturman-shell.service → oneshot software-render → /run/shturman/frame.png (RemainAfterExit → is-active детерминированно, без хрупкого weston; живой weston-shell — v0.5). just shell-frame — инспекция кадра. E2E (tests/e2e/run.sh, двухфазно pre→reboot→post): - /data+power-safe опции, volatile-tmpfs, first-boot идемпотентность, per-unit active, имена на шине + GetPowerState, fake-ACC SetAcc→AccChanged, первый кадр PNG, base-бюджеты (journald volatile / zram / oomd / fake-hwclock→/data / eMMC-прокси), персист Settings + machine-id every-boot bind после reboot. Провижининг (lima/shturman.yaml) — правки по реальным ошибкам Lima: - build-deps Slint/winit на Linux (libfontconfig1-dev/libxkbcommon-dev/libwayland-dev); - linux-modules-extra (zram/vcan не в vz-ядре); systemd-oomd; rm стокового /etc/fake-hwclock.data (A11); VM-локальный CARGO_TARGET_DIR. Док-синхронизация (спека §13/§8.1/§7.5 + CLAUDE.md): швы реализации, eMMC-порог T=4096 сект, fake-hwclock masked-в-Lima, dev-mock policy не нужен. Перф-вердикт — на RK3588 (в VM — функционально, performance §2). just ci зелёный. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Alexander <akotenev2003@gmail.com>
203 lines
13 KiB
Bash
203 lines
13 KiB
Bash
#!/usr/bin/env bash
|
|
# Сквозной E2E Штурмана в Lima-VM (приёмка v0.1/v0.6 + шагающий скелет, спека §9.3/§9.4).
|
|
# Запуск: just e2e (двухфазно через reboot) или just run (однофазно, без reboot).
|
|
#
|
|
# Фазы (env E2E_PHASE):
|
|
# pre — сборка → install бинарей/юнитов → старт shturman.target → проверки §9.3 (1–8)
|
|
# → выставить персист-пробу (Settings.Set + запомнить machine-id); [по умолчанию]
|
|
# post — после reboot: персист настройки сохранился + machine-id стабилен (every-boot bind, §9.3.4).
|
|
#
|
|
# Системная шина устройства (§5.1). dev-mocks включён дефолт-фичей сборки (fake-ACC).
|
|
set -uo pipefail
|
|
|
|
REPO=/shturman
|
|
cd "$REPO" || { echo "нет $REPO"; exit 1; }
|
|
|
|
PHASE="${E2E_PHASE:-pre}"
|
|
IDLE_SECS="${E2E_IDLE_SECS:-20}" # окно простоя для eMMC-прокси (§7.5)
|
|
EMMC_MAX_SECTORS="${E2E_EMMC_MAX_SECTORS:-4096}" # порог T (🟡 калибруется; 4096 сект ≈ 2 МБ/окно)
|
|
FRAME=/run/shturman/frame.png
|
|
PROBE_KEY=ui.theme
|
|
PROBE_VAL=night
|
|
MID_BEFORE=/data/state/e2e-mid-before # снимок machine-id до reboot (персист в /data)
|
|
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$HOME/.cache/shturman/target}"
|
|
|
|
pass() { echo " ✓ $*"; }
|
|
fail() { echo "E2E FAIL: $*" >&2; exit 1; }
|
|
info() { echo; echo "== $* =="; }
|
|
|
|
# Имена на шине (зеркало crates/shturman-ipc/src/names.rs).
|
|
P_NAME=ru.shturman.Power; P_PATH=/ru/shturman/Power; P_IFACE=ru.shturman.Power1
|
|
P_MOCK=ru.shturman.dev.PowerMock1
|
|
S_NAME=ru.shturman.Settings; S_PATH=/ru/shturman/Settings; S_IFACE=ru.shturman.Settings1
|
|
|
|
settings_get() { busctl --system call "$S_NAME" "$S_PATH" "$S_IFACE" Get s "$1" 2>/dev/null; }
|
|
|
|
# =========================== POST-reboot фаза ===========================
|
|
if [ "$PHASE" = post ]; then
|
|
info "POST-reboot: персист настроек + machine-id стабилен (§9.3.4)"
|
|
# дождаться, пока сервисы поднимутся после автозагрузки target
|
|
for _ in $(seq 1 30); do systemctl is-active --quiet shturman-settings && break; sleep 1; done
|
|
|
|
findmnt /data >/dev/null || fail "/data не смонтирован после reboot"
|
|
pass "/data смонтирован после reboot"
|
|
|
|
# настройка пережила reboot
|
|
got=$(settings_get "$PROBE_KEY")
|
|
echo "$got" | grep -q "\"$PROBE_VAL\"" || fail "Settings.$PROBE_KEY != $PROBE_VAL после reboot (got: $got)"
|
|
pass "Settings.$PROBE_KEY = $PROBE_VAL пережил reboot"
|
|
|
|
# machine-id стабилен (every-boot bind из /data/state/machine-id)
|
|
sudo test -f "$MID_BEFORE" || fail "нет снимка $MID_BEFORE (фаза pre не отработала?)"
|
|
before=$(sudo cat "$MID_BEFORE"); now=$(cat /etc/machine-id)
|
|
[ -n "$now" ] || fail "/etc/machine-id пуст"
|
|
[ "$before" = "$now" ] || fail "machine-id изменился: было $before, стало $now"
|
|
src=$(sudo cat /data/state/machine-id)
|
|
[ "$now" = "$src" ] || fail "/etc/machine-id($now) != /data/state/machine-id($src) — bind не сработал"
|
|
pass "machine-id стабилен после reboot ($now), привязан из /data"
|
|
|
|
# journald volatile теперь естественно (drop-in присутствовал на boot)
|
|
test -d /run/log/journal && ! test -d /var/log/journal \
|
|
&& pass "journald volatile (/run/log/journal)" || echo " WARN: journald не строго volatile"
|
|
|
|
echo; echo "E2E POST OK ✅"
|
|
exit 0
|
|
fi
|
|
|
|
# ============================ PRE фаза ============================
|
|
info "сборка (release, VM-локальный target=$CARGO_TARGET_DIR)"
|
|
cargo build --release --workspace || fail "сборка"
|
|
for b in firstboot settings power shell; do
|
|
sudo install -m755 "$CARGO_TARGET_DIR/release/shturman-$b" /usr/local/bin/ || fail "install shturman-$b"
|
|
done
|
|
pass "бинари установлены в /usr/local/bin"
|
|
|
|
info "раскладка systemd-юнитов + dbus policy (из репо — подхватить правки)"
|
|
sudo install -m644 systemd/shturman.target systemd/data.mount /etc/systemd/system/
|
|
sudo install -m644 systemd/shturman-firstboot.service systemd/shturman-machineid.service \
|
|
systemd/shturman-power.service systemd/shturman-settings.service \
|
|
systemd/shturman-shell.service /etc/systemd/system/
|
|
sudo install -d /etc/dbus-1/system.d
|
|
sudo install -m644 systemd/dbus/ru.shturman.conf /etc/dbus-1/system.d/
|
|
sudo install -d /etc/systemd/journald.conf.d /etc/systemd/oomd.conf.d
|
|
sudo install -m644 systemd/journald-shturman.conf /etc/systemd/journald.conf.d/shturman.conf
|
|
sudo install -m644 systemd/oomd-shturman.conf /etc/systemd/oomd.conf.d/shturman.conf
|
|
sudo install -m644 systemd/zram-generator.conf /etc/systemd/zram-generator.conf
|
|
sudo systemctl daemon-reload
|
|
# применить конфиги детерминированно (на свежем boot drop-in’ы появились после старта демонов)
|
|
sudo systemctl reload dbus 2>/dev/null || true
|
|
sudo systemctl restart systemd-journald 2>/dev/null || true
|
|
sudo rm -rf /var/log/journal 2>/dev/null || true # устаревший persistent-журнал (до drop-in); volatile его не пересоздаст
|
|
sudo modprobe zram 2>/dev/null || true # zram-модуль (linux-modules-extra); может отсутствовать в vz-ядре
|
|
sudo systemctl start "systemd-zram-setup@zram0.service" 2>/dev/null || true
|
|
sudo systemctl restart systemd-oomd 2>/dev/null || sudo systemctl start systemd-oomd 2>/dev/null || true # подхватить oomd.conf.d
|
|
pass "юниты/политики разложены"
|
|
|
|
info "старт shturman.target"
|
|
sudo systemctl start shturman.target || fail "shturman.target не стартовал"
|
|
sudo systemctl restart shturman-shell.service || true # свежий кадр (на повторном прогоне)
|
|
for _ in $(seq 1 15); do systemctl is-active --quiet shturman-shell && break; sleep 1; done
|
|
|
|
# ---- 1. /data до сервисов + реальные power-safe опции (§9.3.1) ----
|
|
info "1. /data смонтирован, реальные non-default опции + volatile-слой"
|
|
findmnt /data >/dev/null || fail "/data не смонтирован"
|
|
opts=$(findmnt -no OPTIONS /data)
|
|
echo "$opts" | grep -q errors=remount-ro || fail "нет errors=remount-ro (opts: $opts)"
|
|
pass "/data: $opts"
|
|
# volatile-слой (tmpfs) присутствует — кадр/журнал/транзиент пишутся сюда, не на flash (A11).
|
|
# Полный RO-rootfs + overlay(upper на tmpfs) — на HW/v4 (A/B boot-select нет в VM, §7.1); тут — дисциплина + tmpfs.
|
|
findmnt -t tmpfs /run >/dev/null || fail "/run не tmpfs (нет volatile-слоя)"
|
|
pass "volatile-слой: /run = tmpfs"
|
|
|
|
# ---- 2. first-boot маркер + идемпотентность (§9.3.2) ----
|
|
info "2. first-boot маркер + machine-id"
|
|
sudo test -f /data/.shturman-provisioned || fail "нет маркера .shturman-provisioned"
|
|
sudo test -f /data/state/machine-id || fail "нет /data/state/machine-id"
|
|
sudo systemctl start shturman-firstboot.service # повторно — Condition гейтит → no-op
|
|
pass "first-boot маркер на месте, повторный запуск no-op"
|
|
|
|
# ---- 3. per-unit critical set active (degraded не маскирует, §9.3.1) ----
|
|
info "3. per-unit critical set"
|
|
for u in shturman-power shturman-settings shturman-shell; do
|
|
systemctl is-active --quiet "$u" || fail "$u не active ($(systemctl is-active "$u" 2>&1))"
|
|
pass "$u: active"
|
|
done
|
|
for u in shturman-firstboot shturman-machineid; do
|
|
state=$(systemctl is-active "$u" 2>&1)
|
|
[ "$state" = active ] || fail "$u не active(exited): $state"
|
|
pass "$u: $state (oneshot)"
|
|
done
|
|
|
|
# ---- 4. имена на системной шине (own) + сервис отвечает (§9.3.3) ----
|
|
info "4. имена на шине + отклик"
|
|
busctl --system list | grep -q "$P_NAME" || fail "нет $P_NAME на шине"
|
|
busctl --system list | grep -q "$S_NAME" || fail "нет $S_NAME на шине"
|
|
busctl --system call "$P_NAME" "$P_PATH" "$P_IFACE" GetPowerState | grep -q running \
|
|
|| fail "Power.GetPowerState != running"
|
|
pass "$P_NAME / $S_NAME владеют именами; GetPowerState=running"
|
|
|
|
# ---- 5. fake-ACC: SetAcc -> AccChanged (§9.3.5) ----
|
|
info "5. fake-ACC SetAcc -> AccChanged"
|
|
mon=$(mktemp)
|
|
# sudo нужен busctl для eavesdrop системной шины; редирект в $mon — намеренно user-owned mktemp (SC2024 ок).
|
|
# shellcheck disable=SC2024
|
|
sudo busctl --system monitor "$P_NAME" >"$mon" 2>&1 &
|
|
MON=$!
|
|
sleep 1
|
|
busctl --system call "$P_NAME" "$P_PATH" "$P_MOCK" SetAcc b false || { sudo kill "$MON" 2>/dev/null; fail "вызов SetAcc"; }
|
|
sleep 1
|
|
sudo kill "$MON" 2>/dev/null; wait "$MON" 2>/dev/null
|
|
grep -q AccChanged "$mon" || { echo "--- monitor ---"; cat "$mon"; fail "AccChanged не наблюдаем"; }
|
|
rm -f "$mon"
|
|
busctl --system call "$P_NAME" "$P_PATH" "$P_MOCK" SetAcc b true >/dev/null 2>&1 || true # вернуть acc=on
|
|
pass "AccChanged наблюдаем после SetAcc"
|
|
|
|
# ---- 7. первый Slint-кадр: PNG не пустой (§9.3.6) ----
|
|
info "7. первый Slint-кадр (software-render PNG)"
|
|
sudo test -f "$FRAME" || fail "нет кадра $FRAME (shell.service не отрендерил?)"
|
|
sz=$(sudo stat -c%s "$FRAME"); [ "$sz" -gt 10000 ] || fail "кадр подозрительно мал ($sz Б)"
|
|
sudo head -c8 "$FRAME" | od -An -tx1 | tr -d ' \n' | grep -qi "89504e47" || fail "$FRAME не PNG"
|
|
pass "кадр $FRAME: $sz Б, валидный PNG"
|
|
|
|
# ---- 8. base-бюджеты: journald / zram / fake-hwclock / eMMC-прокси (§9.3.7) ----
|
|
info "8. base-бюджеты (функц.)"
|
|
# journald volatile: активный журнал в /run/log/journal, persistent /var/log/journal отсутствует (A10)
|
|
test -d /run/log/journal || fail "journald не volatile (нет /run/log/journal)"
|
|
test -d /var/log/journal && fail "journald пишет в persistent /var/log/journal (нарушение A10)"
|
|
pass "journald volatile (/run/log/journal, без /var/log/journal)"
|
|
# ровно одно zram-устройство (A09); модуль zram может отсутствовать в vz-ядре — честная VM↔HW-граница
|
|
zn=$(zramctl --noheadings 2>/dev/null | wc -l | tr -d ' ')
|
|
if [ "$zn" = 1 ]; then pass "zram: ровно одно устройство ($(zramctl --noheadings --output NAME 2>/dev/null))"
|
|
elif [ "$zn" = 0 ]; then echo " WARN: zram-устройств нет (модуль zram отсутствует в vz-ядре — HW-only, §13)"
|
|
else fail "zram: ожидалось одно устройство, найдено $zn"; fi
|
|
# fake-hwclock пишет в /data, не в /etc (A11). Override — FILE из /etc/default/fake-hwclock
|
|
# (сервис в Lima masked: Lima сам синхронит время — на HW юнит размаскирован, EnvironmentFile тот же).
|
|
sudo sh -c '. /etc/default/fake-hwclock 2>/dev/null; FILE="${FILE:-/data/state/fake-hwclock.data}" fake-hwclock save' || true
|
|
sudo test -f /data/state/fake-hwclock.data || fail "fake-hwclock не записал в /data/state/fake-hwclock.data"
|
|
sudo test -f /etc/fake-hwclock.data && fail "fake-hwclock пишет в /etc (нарушение A11)"
|
|
pass "fake-hwclock → /data/state/fake-hwclock.data (не в /etc)"
|
|
# systemd-oomd: запущен + наша политика загружена (A09). PSI/cgroup2 нужны — в vz есть; иначе honest WARN.
|
|
if [ "$(systemctl is-active systemd-oomd 2>/dev/null)" = active ] && test -f /etc/systemd/oomd.conf.d/shturman.conf; then
|
|
pass "systemd-oomd active, политика oomd.conf.d/shturman.conf загружена"
|
|
else echo " WARN: systemd-oomd не active (нет PSI/пакета — проверь провижининг)"; fi
|
|
# eMMC-прокси: дельта записанных секторов loop-/data за окно простоя
|
|
src=$(findmnt -no SOURCE /data); dev=$(basename "$src")
|
|
if [ ! -e "/sys/block/$dev" ]; then dev=$(losetup -j /var/lib/shturman/data.img -O NAME --noheadings 2>/dev/null | tr -d ' ' | xargs -r basename); fi
|
|
read_w() { awk -v d="$dev" '$3==d {print $10}' /proc/diskstats; }
|
|
s0=$(read_w); echo " окно простоя ${IDLE_SECS}s (loop-dev=$dev)…"; sleep "$IDLE_SECS"; s1=$(read_w)
|
|
delta=$(( ${s1:-0} - ${s0:-0} ))
|
|
echo " eMMC-прокси: записано $delta секторов за ${IDLE_SECS}s (порог $EMMC_MAX_SECTORS, ~калибровка)"
|
|
[ "$delta" -le "$EMMC_MAX_SECTORS" ] || fail "eMMC: дельта $delta > порога $EMMC_MAX_SECTORS секторов"
|
|
pass "eMMC-прокси в пороге"
|
|
|
|
# ---- персист-проба для POST-фазы ----
|
|
info "персист-проба (для проверки после reboot)"
|
|
busctl --system call "$S_NAME" "$S_PATH" "$S_IFACE" Set sv "$PROBE_KEY" s "$PROBE_VAL" || fail "Settings.Set"
|
|
got=$(settings_get "$PROBE_KEY"); echo "$got" | grep -q "\"$PROBE_VAL\"" || fail "Set не применился (got: $got)"
|
|
sudo cp /etc/machine-id "$MID_BEFORE" # снимок до reboot
|
|
pass "Settings.$PROBE_KEY=$PROBE_VAL выставлен; machine-id снят в $MID_BEFORE"
|
|
|
|
echo; echo "E2E PRE OK ✅ (для полной приёмки — reboot + фаза post: just e2e)"
|