diff --git a/.gitignore b/.gitignore index e7e4021..46a3e1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.tar.gz *.zip *.so +!cli/embedded/ +!cli/embedded/*.so *.swp .DS_Store cli/remagic diff --git a/cli/appload.go b/cli/appload.go new file mode 100644 index 0000000..c3d627c --- /dev/null +++ b/cli/appload.go @@ -0,0 +1,30 @@ +package main + +import ( + _ "embed" + "strconv" + "strings" +) + +// Upstream v0.5.3 hooks target OS <=3.27. On 3.28+ the sidebar QML moved and +// appload panics with "Couldn't resolve the hashed identifier … AppLoad hooks". +// Built from asivery/rm-appload PR #59 until a stable upstream release ships. +// +//go:embed embedded/appload-3.28-aarch64.so +var appload328 []byte + +func osNeedsAppLoad328(osv string) bool { + parts := strings.Split(strings.TrimSpace(osv), ".") + if len(parts) < 2 || parts[0] != "3" { + return false + } + minor, err := strconv.Atoi(parts[1]) + return err == nil && minor >= 28 +} + +func apploadSOForOS(osv string) ([]byte, string) { + if osNeedsAppLoad328(osv) { + return appload328, "3.28 (PR #59 build — upstream v0.5.3 breaks on this OS)" + } + return nil, "" +} diff --git a/cli/embedded/appload-3.28-aarch64.so b/cli/embedded/appload-3.28-aarch64.so new file mode 100755 index 0000000..a88b6b2 Binary files /dev/null and b/cli/embedded/appload-3.28-aarch64.so differ diff --git a/cli/hashtab.go b/cli/hashtab.go new file mode 100644 index 0000000..f501781 --- /dev/null +++ b/cli/hashtab.go @@ -0,0 +1,118 @@ +package main + +import ( + "strings" + + "github.com/maximerivest/remagic/cli/internal/device" +) + +// rebuildHashtabScript drives xochitl with only qt-resource-rebuilder loaded +// and waits until the per-OS-version hashtab file exists. Upstream's +// rebuild_hashtable blocks on "press enter" and remagic's old setup used +// `timeout … rebuild_hashtable &2 + exit 1 +fi + +systemctl stop xochitl 2>/dev/null || true +for sig in 15 9; do + pid=$(pidof xochitl 2>/dev/null || true) + [ -n "$pid" ] || break + kill -$sig $pid 2>/dev/null || true + sleep 1 +done + +rm -rf "$xovi_root" "$log" +mkdir -p "$xovi_root/extensions.d" "$qrr_dir" +ln -sf "$qrr_so" "$xovi_root/extensions.d/" +rm -f "$hashtab" + +export XOVI_ROOT="$xovi_root" +QMLDIFF_HASHTAB_CREATE="$hashtab" \ +QML_DISABLE_DISK_CACHE=1 \ +LD_PRELOAD="$xovi/xovi.so" \ +/usr/bin/xochitl >>"$log" 2>&1 & +xpid=$! + +deadline=$(($(date +%s) + 180)) +ok=0 +while [ "$(date +%s)" -lt "$deadline" ]; do + if [ -s "$hashtab" ]; then + ok=1 + break + fi + if grep -qF "[qmldiff]: Hashtab saved to $hashtab" "$log" 2>/dev/null; then + ok=1 + break + fi + if ! kill -0 "$xpid" 2>/dev/null; then + break + fi + sleep 1 +done + +pid=$(pidof xochitl 2>/dev/null || true) +[ -n "$pid" ] && kill -15 $pid 2>/dev/null || true +sleep 1 +pid=$(pidof xochitl 2>/dev/null || true) +[ -n "$pid" ] && kill -9 $pid 2>/dev/null || true + +rm -rf "$xovi_root" + +if [ "$ok" = 1 ] && [ -s "$hashtab" ]; then + echo "Hashtab rebuilt at $hashtab" + exit 0 +fi + +echo "Hashtab rebuild failed." >&2 +echo "--- last log lines ---" >&2 +tail -30 "$log" 2>/dev/null >&2 || true +exit 1 +` + +const hashtabPath = "/home/root/xovi/exthome/qt-resource-rebuilder/hashtab" + +// rebuildHashtab runs the non-interactive rebuild on the tablet. Returns true +// when qt-resource-rebuilder is absent (nothing to do) or the hashtab exists. +func rebuildHashtab(d *device.Device) bool { + if _, err := d.Run("test -f /home/root/xovi/extensions.d/qt-resource-rebuilder.so"); err != nil { + ok("not needed (no qt-resource-rebuilder in this bundle)") + return true + } + out, err := d.RunIn("bash -s", strings.NewReader(rebuildHashtabScript)) + out = strings.TrimSpace(out) + if err != nil { + if out != "" { + for _, line := range strings.Split(out, "\n") { + warn("%s", strings.TrimSpace(line)) + } + } + warn("AppLoad needs this hashtab on OS 3.27+ — without it xochitl crash-loops") + warn("retry with: remagic rebuild-hashtab") + return false + } + if out != "" { + ok("%s", out) + } else { + ok("hashtable rebuilt") + } + return true +} + +// hasHashtab reports whether the on-device hashtab file exists and is non-empty. +func hasHashtab(d *device.Device) bool { + _, err := d.Run("test -s " + hashtabPath) + return err == nil +} diff --git a/cli/main.go b/cli/main.go index 2fff063..8911819 100644 --- a/cli/main.go +++ b/cli/main.go @@ -42,6 +42,7 @@ usage: remagic [args] config configure an app from your browser — prints a QR code so you can use your phone's keyboard wifi on|off|status SSH over Wi-Fi, so the cable is only ever needed once + rebuild-hashtab rebuild the Qt resource hashtab (needed after OS updates) repair-ssh fix the "connection reset" SSH wedge in-band publish [folder] zip an app folder, create a GitHub release (via gh), and print/update its catalog entry @@ -126,6 +127,8 @@ func main() { die("usage: remagic wifi on|off|status") } cmdWifi(mustConnect(host), args[0]) + case "rebuild-hashtab": + cmdRebuildHashtab(mustConnect(host)) case "repair-ssh": cmdRepairSSH(mustConnect(host)) case "publish": @@ -235,6 +238,19 @@ func cmdDoctor(d *device.Device) { } check("xovi installed", "test -d /home/root/xovi") check("AppLoad installed", "test -f /home/root/xovi/extensions.d/appload.so") + if _, err := d.Run("test -f /home/root/xovi/extensions.d/qt-resource-rebuilder.so"); err == nil { + if hasHashtab(d) { + ok("Qt resource hashtab present") + } else { + warn("Qt resource hashtab missing — AppLoad will crash xochitl on OS 3.27+") + warn("Run: remagic rebuild-hashtab") + } + } + if _, err := d.Run(`pid=$(pidof xochitl 2>/dev/null | awk '{print $1}') && test -n "$pid" && grep -q xovi.so /proc/$pid/maps`); err == nil { + ok("xovi active in running xochitl") + } else { + warn("xovi not loaded in xochitl (stock mode, or crash-looping)") + } // Any of the community persistence flavors counts. if _, err := d.Run("systemctl is-enabled xovi-tripletap 2>/dev/null || systemctl is-enabled xovi-always-on 2>/dev/null || systemctl is-enabled xovi-boot 2>/dev/null"); err == nil { ok("boot persistence (tripletap / always-on unit)") @@ -547,6 +563,21 @@ func cmdWifi(d *device.Device, action string) { // dropbear host-key bind mount down with it — new connections then die at key // exchange until reboot. Restores the /etc overlay, the key mount, and // read-only /. +func cmdRebuildHashtab(d *device.Device) { + defer d.Close() + step("rebuilding the Qt resource hashtable") + if !rebuildHashtab(d) { + die("hashtab rebuild failed — see messages above") + } + step("starting xovi") + if out, err := d.Run(`systemctl stop xovi-firststart 2>/dev/null; systemctl reset-failed xovi-firststart 2>/dev/null; \ +systemd-run --unit=xovi-firststart --collect --service-type=oneshot /home/root/xovi/start`); err != nil { + warn("couldn't start xovi (%s) — triple-press power", tail(out, 120)) + } else { + ok("xovi started — AppLoad should appear on the tablet") + } +} + func cmdRepairSSH(d *device.Device) { defer d.Close() step("repairing the SSH key store") diff --git a/cli/setup.go b/cli/setup.go index ab66bd0..cddd19b 100644 --- a/cli/setup.go +++ b/cli/setup.go @@ -7,7 +7,7 @@ package main // 2. ssh key (generated if you have none, then installed) // 3. xovi bundle (loader + scripts, from asivery's pinned release) // 4. AppLoad (only appload.so into extensions.d; shims into exthome) -// 5. hashtable (best-effort; only UI/QML mods need it) +// 5. hashtable (required for AppLoad on OS 3.27+) // 6. tripletap (power-button persistence; script streamed from the laptop // over verified TLS, executed on the device) // 7. ssh repair (tripletap's installer knocks over the host-key mount on @@ -89,42 +89,60 @@ mv -f /home/root/xovi/inactive-extensions/xovi-message-broker.so /home/root/xovi ok("xovi installed at /home/root/xovi") // ---- 4. AppLoad --------------------------------------------------------- - step("downloading AppLoad") - appload, err := fetchURL(apploadURL) - if err != nil { - die("download AppLoad: %v", err) - } - ok("%d KB", len(appload)/1024) - step("installing the AppLoad launcher") - if err := d.Push(appload, "/tmp/appload.zip", "644"); err != nil { - die("%v", err) - } - // ONLY appload.so is a xovi extension → extensions.d/. The zip also - // carries shims/qtfb-shim*.so (runtime pieces for windowed apps) which - // must NOT land in extensions.d/ or xovi tries to load them and errors. - if out, err := d.Run(`cd /tmp && rm -rf appload-unz && mkdir appload-unz && \ + osv, _ := d.Run(`. /etc/os-release 2>/dev/null; printf %s "$IMG_VERSION"`) + osv = strings.TrimSpace(osv) + if blob, label := apploadSOForOS(osv); blob != nil { + step("installing AppLoad for OS %s", label) + if err := d.Push(blob, "/home/root/xovi/extensions.d/appload.so", "755"); err != nil { + die("%v", err) + } + step("downloading AppLoad shims") + apploadZip, err := fetchURL(apploadURL) + if err != nil { + die("download AppLoad shims: %v", err) + } + if err := d.Push(apploadZip, "/tmp/appload.zip", "644"); err != nil { + die("%v", err) + } + if out, err := d.Run(`cd /tmp && rm -rf appload-unz && mkdir appload-unz && \ (unzip -oq appload.zip -d appload-unz || busybox unzip -o appload.zip -d appload-unz) && \ -cp -f appload-unz/appload.so /home/root/xovi/extensions.d/ && \ mkdir -p /home/root/xovi/exthome/appload && \ if [ -d appload-unz/shims ]; then cp -rf appload-unz/shims /home/root/xovi/exthome/appload/; fi && \ if [ -d appload-unz/exthome ]; then cp -rf appload-unz/exthome/. /home/root/xovi/exthome/; fi && \ rm -rf appload-unz appload.zip`); err != nil { - die("install AppLoad: %v: %s", err, out) - } - ok("AppLoad installed") - - // ---- 5. Qt hashtable (best-effort; only UI/QML mods need it) ----------- - step("rebuilding the Qt resource hashtable (best-effort)") - if _, err := d.Run("test -x /home/root/xovi/rebuild_hashtable"); err == nil { - if _, err := d.Run("timeout 120 /home/root/xovi/rebuild_hashtable /dev/null"); err == nil { - ok("hashtable rebuilt") - } else { - warn("couldn't rebuild non-interactively — AppLoad works without it") + die("install AppLoad shims: %v: %s", err, out) } + ok("AppLoad installed (3.28-compatible build)") } else { - ok("not needed by this bundle") + step("downloading AppLoad") + appload, err := fetchURL(apploadURL) + if err != nil { + die("download AppLoad: %v", err) + } + ok("%d KB", len(appload)/1024) + step("installing the AppLoad launcher") + if err := d.Push(appload, "/tmp/appload.zip", "644"); err != nil { + die("%v", err) + } + // ONLY appload.so is a xovi extension → extensions.d/. The zip also + // carries shims/qtfb-shim*.so (runtime pieces for windowed apps) which + // must NOT land in extensions.d/ or xovi tries to load them and errors. + if out, err := d.Run(`cd /tmp && rm -rf appload-unz && mkdir appload-unz && \ +(unzip -oq appload.zip -d appload-unz || busybox unzip -o appload.zip -d appload-unz) && \ +cp -f appload-unz/appload.so /home/root/xovi/extensions.d/ && \ +mkdir -p /home/root/xovi/exthome/appload && \ +if [ -d appload-unz/shims ]; then cp -rf appload-unz/shims /home/root/xovi/exthome/appload/; fi && \ +if [ -d appload-unz/exthome ]; then cp -rf appload-unz/exthome/. /home/root/xovi/exthome/; fi && \ +rm -rf appload-unz appload.zip`); err != nil { + die("install AppLoad: %v: %s", err, out) + } + ok("AppLoad installed") } + // ---- 5. Qt hashtable (AppLoad hard-crashes xochitl without it on 3.27+) - + step("rebuilding the Qt resource hashtable") + hashtabOK := rebuildHashtab(d) + // ---- 6. tripletap persistence ------------------------------------------- step("installing power-button persistence (xovi-tripletap)") fmt.Println(" triple-press the power button to toggle xovi — survives reboots,") @@ -151,7 +169,10 @@ rm -rf appload-unz appload.zip`); err != nil { // ---- 8. start xovi now --------------------------------------------------- step("starting xovi (no reboot needed)") - if out, err := d.Run(`systemctl stop xovi-firststart 2>/dev/null; systemctl reset-failed xovi-firststart 2>/dev/null; \ + if !hashtabOK { + warn("skipping xovi start — without the hashtab AppLoad crash-loops xochitl on this OS") + warn("after rebuild: remagic rebuild-hashtab then triple-press power") + } else if out, err := d.Run(`systemctl stop xovi-firststart 2>/dev/null; systemctl reset-failed xovi-firststart 2>/dev/null; \ systemd-run --unit=xovi-firststart --collect --service-type=oneshot /home/root/xovi/start`); err != nil { warn("couldn't start immediately (%s) — triple-press power, or reboot", tail(out, 120)) } else { diff --git a/scripts/03-install-xovi.sh b/scripts/03-install-xovi.sh index f735bcd..a6401cd 100755 --- a/scripts/03-install-xovi.sh +++ b/scripts/03-install-xovi.sh @@ -55,22 +55,24 @@ rm_ssh 'cd /tmp && rm -rf appload-unz && mkdir appload-unz && \ || die "failed to install AppLoad" ok "AppLoad installed." -# --- 3. rebuild the Qt hashtable (needed by qt-resource-rebuilder) ----------- +# --- 3. rebuild the Qt hashtable (required for AppLoad on OS 3.27+) ---------- # qt-resource-rebuilder needs a per-OS-version hashtable, rebuilt on first -# install and after every OS update. The bundle ships upstream's -# `rebuild_hashtable`, which drives xochitl to capture it. AppLoad itself does -# not require it — only UI/QML mods do — so this is best-effort and never fatal. +# install and after every OS update. Upstream's rebuild_hashtable blocks on +# "press enter"; without the hashtab AppLoad crash-loops xochitl on 3.27+. step "Rebuilding the Qt resource hashtable" -if rm_ssh 'test -x /home/root/xovi/rebuild_hashtable'; then - if rm_ssh '/home/root/xovi/rebuild_hashtable /dev/null; then +HASHTAB_OK=0 +if rm_ssh 'test -f /home/root/xovi/extensions.d/qt-resource-rebuilder.so'; then + if rm_scp "$HERE/rebuild-hashtab.sh" "$(rm_dest):/tmp/rebuild-hashtab.sh" \ + && rm_ssh 'bash /tmp/rebuild-hashtab.sh && rm -f /tmp/rebuild-hashtab.sh'; then ok "Hashtable rebuilt." + HASHTAB_OK=1 else - warn "Couldn't rebuild the hashtable non-interactively." - info "AppLoad still works. For UI/QML mods, run this once by hand:" - info " ssh $(rm_dest) '/home/root/xovi/rebuild_hashtable' then reboot." + warn "Hashtable rebuild failed — AppLoad will crash xochitl without it." + info "Retry from your computer: remagic rebuild-hashtab" fi else - info "No rebuild_hashtable in this bundle — skipping (AppLoad doesn't need it)." + info "No qt-resource-rebuilder in this bundle — skipping." + HASHTAB_OK=1 fi # --- 4. persistence: tripletap (blessed) ------------------------------------- @@ -102,9 +104,13 @@ fi # --- 5. start it now --------------------------------------------------------- step "Starting xovi now (no reboot needed)" -rm_ssh 'systemd-run --unit=xovi-firststart --collect --service-type=oneshot /home/root/xovi/start 2>/dev/null' \ - && ok "xovi started." \ - || warn "Couldn't start immediately; triple-press power, or reboot." +if [ "$HASHTAB_OK" = 1 ]; then + rm_ssh 'systemd-run --unit=xovi-firststart --collect --service-type=oneshot /home/root/xovi/start 2>/dev/null' \ + && ok "xovi started." \ + || warn "Couldn't start immediately; triple-press power, or reboot." +else + warn "Skipping xovi start until the hashtab is rebuilt (remagic rebuild-hashtab)." +fi cat <&2 + exit 1 +fi + +systemctl stop xochitl 2>/dev/null || true +for sig in 15 9; do + pid=$(pidof xochitl 2>/dev/null || true) + [[ -n "$pid" ]] || break + kill -"$sig" "$pid" 2>/dev/null || true + sleep 1 +done + +rm -rf "$xovi_root" "$log" +mkdir -p "$xovi_root/extensions.d" "$qrr_dir" +ln -sf "$qrr_so" "$xovi_root/extensions.d/" +rm -f "$hashtab" + +export XOVI_ROOT="$xovi_root" +QMLDIFF_HASHTAB_CREATE="$hashtab" \ +QML_DISABLE_DISK_CACHE=1 \ +LD_PRELOAD="$xovi/xovi.so" \ +/usr/bin/xochitl >>"$log" 2>&1 & +xpid=$! + +deadline=$(($(date +%s) + 180)) +ok=0 +while [[ $(date +%s) -lt $deadline ]]; do + if [[ -s "$hashtab" ]]; then + ok=1 + break + fi + if grep -qF "[qmldiff]: Hashtab saved to $hashtab" "$log" 2>/dev/null; then + ok=1 + break + fi + if ! kill -0 "$xpid" 2>/dev/null; then + break + fi + sleep 1 +done + +pid=$(pidof xochitl 2>/dev/null || true) +[[ -n "$pid" ]] && kill -15 "$pid" 2>/dev/null || true +sleep 1 +pid=$(pidof xochitl 2>/dev/null || true) +[[ -n "$pid" ]] && kill -9 "$pid" 2>/dev/null || true + +rm -rf "$xovi_root" + +if [[ "$ok" = 1 && -s "$hashtab" ]]; then + echo "Hashtab rebuilt at $hashtab" + exit 0 +fi + +echo "Hashtab rebuild failed." >&2 +echo "--- last log lines ---" >&2 +tail -30 "$log" 2>/dev/null >&2 || true +exit 1