slint-frontend/ in the app repo is a native front-end for the same backend, written because the React UI is sluggish on this device. This installs it beside the web UI, never instead of it: the systemd unit is installed but not enabled, so the device still boots into the kiosk browser and the two can be compared on the same screen. systemctl start musikdelfin # takes over the display systemctl stop musikdelfin # back to the browser underneath Unlike pi_musicmouse, which builds the web front-end on the control machine and rsyncs dist/, this builds on the device. A Rust binary is not portable, and the reason it has to be built here rather than cross-compiled is worth writing down: these Pis run 32-bit armhf Raspbian on a 64-bit kernel. `uname -m` says aarch64 and it is misleading; `dpkg --print-architecture` says armhf and that is the one that matters. Cross-compiling to armv7-unknown-linux-gnueabihf needs a sysroot, the devices carry no -dev packages to build one from, and reconstructing one from the Debian archive means owning a glibc skew risk indefinitely. Spending the device's own CPU once is the smaller liability. The build is stamped with the commit it came from, so it only runs again when that changes. The same fact rules out the Skia renderer that the app's own kiosk profile prefers: rust-skia publishes no prebuilt binaries for armhf. This builds --features desktop - winit, FemtoVG and the software renderer - and runs as an X client in the existing session. Worth noting after the fact: the FemtoVG bug that makes Skia the upstream recommendation (slint-ui/slint#11785, album covers drawn as solid black) was reported on a Pi 5 with a 64-bit userland and does not reproduce here - covers render correctly. Three things had to be found by running it rather than reading anything: The X session belongs to `kiosk`, not root, so the launcher needs that user's cookie and the unit runs as them. Without it winit fails with "could not initialize backend", which says nothing about authorization. winit and glutin dlopen every windowing and GL library they use, so none of them appear in `ldd` and the binary links happily without them - then aborts on first run with "Library libxkbcommon-x11.so could not be loaded". The runtime packages are listed explicitly for that reason. Raspbian ships no font containing the emoji the design uses as iconography, so the shelf badges rendered as empty boxes until fonts-noto-color-emoji went in. First build took 63 minutes: 570 crates, two jobs, nice'd, which bounds peak memory on musicdolphin's 1.8 GB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ansible
Personal Ansible setup for provisioning and maintaining a fleet of Raspberry Pis (audio players, sensors, music mouse, etc.) plus one home server.
Layout
inventory.yml— hosts and group vars (Pis underiot, plusserver).group_vars/andhost_vars/hold vars for themediapisgroup (the four parallel audio-player Pis) and their host-specific overrides.mediapis.yml,working.yml,server.yml,newrpi-provisioning.yml,octopisetup.yml— top-level playbooks.mediapis.ymlis the canonical playbook for themediapisgroup (musicdolphin,kitchenpi,bedroompi,musicmouse); the others are narrower/ad-hoc runs kept around for specific hosts or one-off tasks.roles/— one role per piece of functionality (audio backends, sensors, bluetooth monitoring, server basics, etc.). Each has a shortREADME.md.update-packages.yml— deliberate, fleet-wide package update (see "Keeping packages up to date" below).update-packages-pinned-example.ymlis a template for pinning or bumping a single package outside that.lookup_plugins/keepass.py— custom lookup plugin that fetches secrets (device passwords, wifi passphrase) from a running KeePassXC instance via its browser-integration protocol, instead of storing them in the repo.pis/— loose config files/scripts used when provisioning Pis by hand.scripts/— standalone helper scripts (Raspbian image creation, a network logger) that aren't Ansible roles.archive/— retired setups kept for reference (not actively maintained).
Setup
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Secrets are pulled from KeePassXC at run time via the keepass lookup
plugin — see the header of lookup_plugins/keepass.py for how to enable
Browser Integration in KeePassXC. Freshly-flashed Raspberry Pis are reached
first with the OS-default pi/raspberry credentials (see
roles/pi_standard_setup), which the role then rotates to a KeePassXC-managed
password.
Running a playbook
ansible-playbook mediapis.yml --limit <host>
ansible.cfg points Ansible at inventory.yml and roles/ by default, so
no extra flags are needed for those.
Keeping packages up to date
Regular playbook runs (mediapis.yml, server.yml, etc.) use state: present
for packages, so they only install what's missing — they never upgrade
anything as a side effect of an unrelated config change. Two separate,
deliberate mechanisms handle upgrades instead:
Security patches — automatic. The unattended_upgrades role (applied
to every host in mediapis.yml/server.yml) configures unattended-upgrades
to install security-origin updates automatically, with a scheduled reboot
window (default 03:00, see roles/unattended_upgrades/defaults/main.yml)
for patches that need one. Not scoped to full dist-upgrades.
Everything else — deliberate, ad hoc.
just update <host_or_group> # e.g. `just update kitchenpi` or `just update`
venv/bin/ansible-playbook update-packages.yml --limit <host> --check --diff # dry run first
Defaults to upgrade: safe (upgrades in place, never installs/removes
packages to resolve dependencies — see the comment header in
update-packages.yml for the tradeoff against dist). There is no cron/
schedule wired up for this yet; run it ad hoc when you want the fleet
updated, or add a crontab entry yourself (a starting point is documented in
update-packages.yml's header).
To pin a package to an exact version, or deliberately bump one named
package to latest outside this schedule, copy the pattern in
update-packages-pinned-example.yml.