Files
ansible/roles/pi_musicmouse
Martin Bauer 340eeb5433 Install the backend on every run, not on a checkout change
The role left musicdolphin with an empty venv and a service crash-looping on
ModuleNotFoundError while ansible reported failed=0, because installing the
backend hung off a handler notified by the git checkout:

  - the run where the checkout changed failed later on, so handlers never
    flushed and the notification was dropped;
  - the next run found the checkout already current, notified nothing, and
    installed nothing.

Nothing ever converges from there. Make it an ordinary task that runs every
time - uv is fast when there is nothing to do, and its output says whether it
actually installed anything, so a restart is still only notified on a real
change. Give the frontend the same treatment with a cheap stat, since the build
chain has the identical hole and a device with no dist serves no UI.

Also add libjpeg-dev and zlib1g-dev: Raspberry Pi OS has a 32-bit userland,
Pillow publishes no armv7 wheel, and uv therefore builds it from source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:58:05 +02:00
..

pi_musicmouse

Deploys the MusicMouse backend (python-backend/) and its built web frontend.

  • Checks out musicmouse_version from pi_musicmouse_repo to /opt/musicmouse, then fails early if that checkout has no python-backend/pyproject.toml - see "Which version" below.
  • Installs a pinned uv release and uses it to create /opt/musicmouse/.venv with Python pi_musicmouse_python_version (the backend needs 3.12+ for PEP 695 syntax; Raspbian Bookworm's apt python3 is only 3.11) and pip install the backend into it.
  • Builds the frontend (web/) on the control machine (delegated to localhost, not the Pi - no Node/npm is installed on the Pi) and syncs web/dist over.
  • Installs /media/musicmouse/config.yml from files/config-<inventory_hostname>.yml (one file per host - the schema differs between a host with a real mouse attached and a display-only one; see those files' own comments), the typing game's curriculum beside it, and the systemd unit copied straight from the checked-out repo's python-backend/musicmouse.service, so it can never drift from what's actually in the app repo.
  • Sets up a Samba share of /media/musicmouse for copying music onto the device.

Which version

musicmouse_version is set per host in host_vars, with no default, and points at a deploy/<host> branch on the remote. Those branches are the deployment record: what is installed on that box right now. Moving one is a force-push, and the next run follows it (the git: task uses force: true):

git push -f origin rearchitect-backend:deploy/musicdolphin
just mediapis musicdolphin

This role only understands the post-rearchitecture layout - python-backend/ as an installable package plus a web/ frontend. On older commits the host driver lives at espmusicmouse/host_driver/main.py, there is no pyproject.toml, the unit file is under esp-firmware/, there is no frontend at all, and the config is a different format that this role's config files would fail to load. There is deliberately no legacy path: the role stops at the checkout with an explanation instead.

musicmouse (Kinderzimmer) is on such an older commit and still working, so it has mediapi_install_kidsmusic: false and this role skips it entirely; deploy/musicmouse exists purely as the rollback marker for what is installed there. Migrating it means moving /media/musicmouse/<figure>/ into Figuren/ and switching to the new config schema - files/config-musicmouse.yml is already written for that.

config.yml is install-once

The app writes to its own config at runtime: parent mode (?parentMode=1) patches the volume keys, and the remote-control page rewrites the whole remote: block. A plain copy: would throw those away on every ansible run, so the config task is force: "{{ pi_musicmouse_force_config }}", default false. Push a changed config when you actually mean to:

just run mediapis.yml musicdolphin -e pi_musicmouse_force_config=true

The curriculum file is not written by the app, so it is installed unconditionally. A config with a tippen: section will not start without it - curriculum_file is validated as must-exist - so a host whose config has no tippen: section should set pi_musicmouse_curriculum_file: "".

Manual steps after a first deploy

  • Set the Samba password with smbpasswd.
  • Get the music onto the device. The app repo's scripts/sync-library.sh is the way to do it: as well as the library it carries over the cover and track-analysis cache, which is the part that costs minutes of DSP per track to recompute, and it knows which parts of that cache survive the move and which must not be copied. The Samba share works too, and then the device does all that work itself.