Two problems on musicdolphin, found after its first reboot. getty@tty1 was never enabled. The role installed the autologin drop-in and the handler restarted the unit, which works until the next boot - these images do not enable getty@tty1 themselves, getty.target pulls in getty-static.service and nothing else, so after a reboot nobody logs in and the kiosk never starts. Enable it as well as starting it. And the attached screen reads back 0 bytes of EDID on both micro-HDMI ports while reporting "disconnected", so the kernel offers no modes, no framebuffer is created, and X exits with "no screens found". That is the signature of a cable or adapter whose DDC/HPD lines are broken but whose video pairs are fine. Forcing the connector on is not enough on its own - with no EDID there is still no mode to set - so allow a `video=` kernel parameter, which forces the output and names the mode. hdmi_force_hotplug=1 is no use here; vc4-kms-v3d ignores it. 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.