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 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 17:31:52 +02:00
parent 831a1b50a4
commit 8c225bbe06
3 changed files with 45 additions and 11 deletions

View File

@@ -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 --noerrdialogs --disable-infobars --no-first-run"
BROWSER_ARGS="$BROWSER_ARGS --no-default-browser-check --disable-session-crashed-bubble" 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 --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' %} {% if pi_kiosk_mode == 'debug' %}
# Debug mode only. Binds to 127.0.0.1, so it is reachable over an ssh tunnel and from # 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 # nowhere else. Lets the page be driven and measured from a shell - `curl

View File

@@ -26,6 +26,15 @@
src: "{{ pi_musicmouse_build_cache }}/{{ musicmouse_version | regex_replace('/', '_') }}/web/dist/" src: "{{ pi_musicmouse_build_cache }}/{{ musicmouse_version | regex_replace('/', '_') }}/web/dist/"
dest: /opt/musicmouse/web/dist/ dest: /opt/musicmouse/web/dist/
delete: true 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 notify: Restart musicmouse
- name: Restart musicmouse - name: Restart musicmouse

View File

@@ -194,21 +194,34 @@
become: false become: false
vars: vars:
ansible_become: false # see the first delegated task in tasks/main.yml ansible_become: false # see the first delegated task in tasks/main.yml
register: pi_musicmouse_web_checkout
notify: Build frontend notify: Build frontend
- name: Check whether the device has a built frontend - name: Read which commit the frontend on the device was built from
ansible.builtin.stat: # Same guard as the venv's .installed-commit above, and for the same reason. Hanging
path: /opt/musicmouse/web/dist/index.html # the build chain off the control machine's checkout changing has a hole: a run that
register: pi_musicmouse_dist # 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 - name: Queue a frontend build when the device's is missing or from another commit
# 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.
ansible.builtin.debug: ansible.builtin.debug:
msg: "No frontend at /opt/musicmouse/web/dist - queueing a build" msg: >-
when: not pi_musicmouse_dist.stat.exists 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 changed_when: true
notify: Build frontend notify: Build frontend