Raspberry Pi OS ships a 64-bit kernel with a 32-bit userland, so uname -m - and
therefore ansible_architecture - reports aarch64 on a box whose /bin/ls is
ELF32 ARM and which has no /lib/ld-linux-aarch64.so.1. The aarch64 uv tarball
unpacked happily and then failed three tasks later with
/usr/local/bin/uv: No such file or directory
which is the dynamic loader missing, not the file, and points at entirely the
wrong thing. Pick the target triple from ansible_userspace_bits instead, which
is the fact that tells the truth, and key the install directory on the triple as
well as the version - otherwise `creates:` would keep a wrong-architecture
binary in place forever. Then run `uv --version` right after installing it, so a
bad download fails where the cause is visible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pi_musicmouse
Deploys the MusicMouse backend (python-backend/) and its built web frontend.
- Checks out
musicmouse_versionfrompi_musicmouse_repoto/opt/musicmouse, then fails early if that checkout has nopython-backend/pyproject.toml- see "Which version" below. - Installs a pinned
uvrelease and uses it to create/opt/musicmouse/.venvwith Pythonpi_musicmouse_python_version(the backend needs 3.12+ for PEP 695 syntax; Raspbian Bookworm's apt python3 is only 3.11) andpip installthe backend into it. - Builds the frontend (
web/) on the control machine (delegated tolocalhost, not the Pi - no Node/npm is installed on the Pi) and syncsweb/distover. - Installs
/media/musicmouse/config.ymlfromfiles/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'spython-backend/musicmouse.service, so it can never drift from what's actually in the app repo. - Sets up a Samba share of
/media/musicmousefor 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.shis 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.