The app grew a python-backend/ and a web/ frontend, so pi_musicmouse no
longer matches it. Rework the role:
- Install a pinned uv and build the venv with Python 3.13; Raspbian
Bookworm's python3 is 3.11 and the backend uses PEP 695 syntax.
- pip install python-backend/ instead of a requirements.txt, and take
the systemd unit straight from the checkout so it can't drift.
- Build web/ on the control machine (no npm on the Pi) and rsync
web/dist over. Chain the checkout -> install -> build -> sync -> restart
steps through handlers so a run with no repo change does nothing.
- Per-host config files (config-<host>.yml); the schema now differs
between a host with a real mouse and a display-only one.
- Version to deploy is per host (musicmouse_version), no default.
Add pi_kiosk: autologin user running startx with Firefox in kiosk mode
and no window manager, for a Pi with a monitor attached. Enabled by
mediapi_has_monitor, defaulting off.
Turn musicdolphin into a display-only instance: serial and audio
simulated, driven entirely through the kiosk page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
raspi-config's do_hostname rewrites the 127.0.1.1 line with
sed "s/127\.0\.1\.1.*$CURRENT_HOSTNAME/127.0.1.1\t$NEW_HOSTNAME/"
which only matches if that line already holds the *old* hostname. Ours
still said "raspberrypi", so the substitution has been silently doing
nothing and the box could not resolve its own hostname.
Set the line explicitly instead, after the hostname is re-read, so a
rename and this update happen in the same run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Pi is gone; the name no longer resolves. It was still picked up by
the "hosts: all" playbooks (update-packages.yml, test_keepass.yml),
where it showed up as unreachable on every fleet run.
It was also the only host pointing pi_squeezelite_squeezeserver at
192.168.178.100 (musicdolphin) rather than the central server at .80,
so nothing depends on musicdolphin's LMS instance any more.
The "esszimmer" rows in roles/bluetooth_monitor/other/*.csv and the
analysis notebook are room labels in historical sensor training data,
not this host, and are left alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Pi's OS hostname was changed first (raspi-config do_hostname +
reboot), since no mediapi sets ansible_host and Ansible resolves these
by hostname via mDNS/router DNS. Host now answers as musicdolphin.local
at 192.168.178.100.
The motd file must match the live OS hostname (pi_standard_setup reads
it off the device), the host_vars filename and the pi_musicmouse config
filename must match the inventory key.
sensor_room_name/sensor_room_name_ascii stay WohnzimmerOben - they name
the physical room for MQTT topics, not the host, and renaming them would
orphan the Home Assistant history.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Skipped command tasks return stdout: "" rather than being undefined, so
"src: motd/{{ ...stdout }}" collapsed to "motd/" - a directory - and copy
switched to recursive mode, failing with "/etc/motd/<file>: Not a
directory". The empty boot-target stdout likewise never equals
"multi-user.target", so "Set boot mode to CLI" always reported changed.
Run the three read-only commands in check mode. They are all
changed_when: false already, so this is safe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Several roles reported "changed" on every playbook run even when
nothing on the target had drifted, making real config drift
indistinguishable from noise:
- 7 systemd tasks across 6 roles used state:restarted, which always
issues a restart and always reports changed. Switched to
state:started plus notify-driven handlers that only restart when
the underlying unit file, script, or config actually changes.
- pi_standard_setup's boot mode, timezone, and locale tasks shelled
out to raspi-config with changed_when:true hardcoded. Boot mode now
checks systemctl get-default first; timezone/locale now use the
natively idempotent community.general.timezone/locale_gen modules.
- The pi account password task computed password_hash('sha512')
without a seed, generating a new random salt (and thus an
apparently different hash) on every run. Added a stable seed so the
hash only changes when the underlying secret does.
Also renamed a mislabeled task in pi_squeezelite_custom and fixed a
typo in pi_standard_setup while those files were already touched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Regular playbooks now use state: present, so they no longer upgrade
packages as a side effect. This adds two separate, explicit mechanisms
to keep the fleet patched instead:
- update-packages.yml: ad hoc / to-be-scheduled fleet-wide upgrade
(safe by default, dist available via -e), plus
update-packages-pinned-example.yml as a template for pinning or
bumping a single package outside that.
- roles/unattended_upgrades: automatic security-only patching via
unattended-upgrades, with a scheduled reboot window and mail
left disabled pending a configured MTA. Applied to every host in
full.yml and server.yml.
Also removes a leftover `upgrade: yes` apt task from pi_standard_setup
and server_basic_environment that was still doing a full upgrade on
every routine run, defeating the point of the state: present switch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- risky-file-permissions (32): add explicit mode: to copy/template/file tasks,
matching the umask-derived permissions they already had (0644 for configs
and systemd units, 0755 for created directories) — no functional change.
- var-naming (28): prefix role-scoped vars with their role name across
pi_dhtsensor, pi_dhtsensor_circuitpython, pi_shairport, pi_squeezelite,
pi_squeezelite_custom, pi_sispmctl, pi_standard_setup, and pi_sysdweb's
sysdweb_name (shared by 9 consuming roles). Updated every dependent
template, task reference, and matching inventory.yml override, and
verified resolved values with ansible-inventory before/after.
- Fixes a latent bug found while renaming: pi_standard_setup's "Get/Change
WiFi country" tasks reused the name wifi_country for both the role default
and a register, so the register silently clobbered the default before
do_wifi_country ever read it. Split into distinct names so the intended
default value is used.
- package-latest (2): pin docker-ce/docker-compose-plugin installs in
server_debian_docker to state: present.
- no-handler (1): move pi_lirc's "Reboot if boot overlay changed" into a
proper handler notified by the boot-overlay task.
ansible-lint now passes clean at the production profile.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Auto-fix FQCN, YAML formatting, jinja spacing, and free-form module
syntax via ansible-lint --fix
- Fix comments misplaced inside module args by the auto-fixer
(bluetooth-monitor, pi_standard_setup, pi_musicmouse)
- Fix notify: references left stale (lowercase) after handler names
were re-cased, which would have silently broken reboot/restart
handlers (pi_disable_onboard_bluetooth, pi_hifiberry_amp,
pi_squeezelite, pi_standard_setup)
- Fix a task in pis/debmatic-install.yml missing its module name
(apt_repository), which caused a real syntax-check failure
- Add missing play names, fix comment spacing, literal-compare idiom,
and no-changed-when annotations
- Delete unused/broken roles/better-shell-env (unreferenced, invalid YAML)
- Rename all hyphenated role directories to underscore form to satisfy
ansible-lint's role-name rule, updating every playbook/meta reference
Remaining lint findings (var-naming, package-latest, risky-file-permissions,
no-handler) intentionally left for follow-up per user decision.