Commit Graph

40 Commits

Author SHA1 Message Date
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
edca1e47dc Stop the delegated frontend build from sudoing on the control machine
pi_standard_setup's defaults set `ansible_become: true`. Role defaults are
play-wide host variables, and in Ansible's precedence the `ansible_become`
variable outranks the `become` keyword - so the `become: false` on every
pi_musicmouse task delegated to localhost was silently overridden and each one
tried to sudo on the machine running ansible:

    Premature end of stream waiting for become success or become password prompt

Those defaults earn their keep bootstrapping a fresh Pi, where you connect as
`pi` and become root to enable root login, so override the variable on the
delegated tasks rather than removing them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:46:07 +02:00
ca0271ba44 Disable the Raspberry Pi first-boot user wizard
Removing the SSH banner only hid the symptom. userconfig.service is a
Type=oneshot unit that runs

    whiptail --inputbox "Which user would you like to rename:" 20 60 pi

on tty8 and waits for an answer, which on a headless Pi never comes - it had
been sitting in "activating" for four and a half hours on musicdolphin. It is
WantedBy=multi-user.target, so that target never finished activating either, and
the boot job for it was still queued from boot. Anything ordered
After=multi-user.target then gets a start job that queues behind it and hangs
forever: musicmouse.service never started, and the ansible task that starts it
blocked until killed.

The pi user already exists on these images, so the wizard has nothing to do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:41:07 +02:00
331df52f0b Download the uv build the Pi can actually run
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>
2026-09-19 19:59:43 +02:00
3557fe3f2b Skip the layout guard under --check
Under --check the git task reports "changed" without writing anything, so on a
host that has not been deployed yet there is no checkout to stat and the guard
fired on a perfectly fine configuration - making `just check mediapis.yml
musicdolphin` fail before the first real run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:10:44 +02:00
5763df90f8 Pin each MusicMouse host to a deploy branch, and freeze musicmouse
musicmouse_version pointed both hosts at rearchitect-backend, a branch that only
ever existed on a laptop - so the previous commit's rework could never actually
have run. Point each host at a deploy/<host> branch on the remote instead. Those
branches are the deployment record: what is installed on that box right now,
moved with a force-push when you mean to deploy.

musicmouse (Kinderzimmer) is the reason this matters. It is in daily use and
still runs the pre-rearchitecture backend out of /opt/musicmouse_venv and
espmusicmouse/host_driver/main.py. That commit has no python-backend/
pyproject.toml, no python-backend/musicmouse.service and no web/ at all, so this
role cannot install it - and a run would have force-checked-out incompatible
code over a working device first and failed afterwards. Set
mediapi_install_kidsmusic: false there and leave it alone;
deploy/musicmouse records the commit that is installed, for rollback. Add a
stat + fail right after the checkout so the same mistake stops with an
explanation instead of failing three tasks deeper.

Also in the role:

- Install the typing game's curriculum next to config.yml. A config with a
  tippen: section refuses to start without it - curriculum_file is validated as
  must-exist - so musicdolphin's new config needs it present.

- Stop clobbering config.yml. The app writes to it at runtime: parent mode
  patches the volume keys and the remote-control page rewrites the remote:
  block. A plain copy: undid that on every run. Install-once by default, with
  -e pi_musicmouse_force_config=true to push a change deliberately.

- Fill out config-musicdolphin.yml against the current schema: library root and
  cache, an absolute static_dir (../web/dist would resolve to /media/web/dist
  from /media/musicmouse and silently serve nothing), tippen, and the volume
  range for a screen-driven instance. ha: is left commented out until the
  Wohnzimmer-oben entity ids are picked - the ones in the dev config are
  Arbeitszimmer. No mqtt: (it would publish a device of dead entities on a
  simulate-only host) and no lirc:/remote: (this host runs irserver, a serial
  daemon, not the lircd this speaks TCP to).

The curriculum is a verbatim copy of app content, so exclude it from
ansible-lint rather than reformat it away from its source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:04:57 +02:00
9b1d01115b Deploy rearchitected MusicMouse backend + web frontend
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>
2026-09-19 17:58:51 +02:00
2443a75871 Keep /etc/hosts 127.0.1.1 in sync with the hostname
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>
2026-09-19 16:31:12 +02:00
ddda28be44 Remove dead host esszimmerradio
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>
2026-09-19 16:28:00 +02:00
9c66b7f665 Rename musikserverwohnzimmeroben -> musicdolphin
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>
2026-09-19 16:23:00 +02:00
07e3c2797e Fix pi_standard_setup under --check
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>
2026-09-19 16:22:50 +02:00
85a26db46b single playbook for mediapis 2026-09-10 21:33:19 +02:00
dae2470e67 use same shell setup as in nix home manager 2026-09-09 08:17:48 +02:00
3d1675528b Fix tasks that always report changed regardless of actual state
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>
2026-09-08 21:28:36 +02:00
c49e4db304 Modern tools (rg, fd...) 2026-09-08 18:26:53 +02:00
f043606466 Add deliberate update playbook and unattended-upgrades role
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>
2026-09-08 18:02:41 +02:00
37b75ecf81 Fix remaining ansible-lint violations: file permissions, var-naming, package pinning
- 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>
2026-09-08 17:28:12 +02:00
bb72eaec10 Untrack local .ansible runtime cache and fix stale gitignore path
The .ansible directory is a local ansible-galaxy/ansible-core cache
that got swept into the previous commit by git add -A. Also update
the pi-squeezeserver backup ignore path to match the role rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 17:13:39 +02:00
ab9763ec49 Fix ansible-lint violations: FQCN, formatting, bugs, role renames
- 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.
2026-09-08 17:13:00 +02:00
f79c106437 Automated repo cleanup with claude 2026-09-08 16:52:46 +02:00
5642424697 before cleanup 2026-09-08 16:32:02 +02:00
7c70221723 IR stuff update 2025-01-06 18:01:18 +01:00
Martin Bauer
9092f08481 Bt monitor 2024-07-28 08:45:01 +02:00
Martin Bauer
e9ec94a5f8 Bluetooth Monitor WIP 2024-03-29 09:32:59 +01:00
Martin Bauer
fe744b2285 bt monitor 2024-03-08 13:02:55 +01:00
Martin Bauer
ffeee72652 music mouse setup 2024-03-08 13:02:43 +01:00
Martin Bauer
fb6f10891d Bluetooth monitor and more 2024-03-01 15:01:08 +01:00
Martin Bauer
7501ef18a4 updates raspis to new os based on bookworm 2024-02-19 08:10:58 +01:00
Martin Bauer
7776095180 New server setup based on ubuntu 2023-09-19 10:55:47 +02:00
Martin Bauer
578be1a1cf updates 2023-01-03 20:07:56 +01:00
Martin Bauer
2a251d2700 Server scripts 2021-09-11 10:18:47 +02:00
Martin Bauer
f1d104a224 Updates & fixes 2021-07-20 15:35:12 +02:00
Martin Bauer
6c421a4ae1 Octopi & dht fixes 2021-07-05 19:50:15 +02:00
Martin Bauer
23a2c1fb50 added ir server, squeeze server, ... 2020-05-16 18:35:44 +02:00
Martin Bauer
93034dd0ec Update dht22 sensing code + sispmctl fixes 2020-05-12 20:51:04 +02:00
Martin Bauer
d8c9a491d1 Working sound setup for raspis 2020-05-10 15:25:38 +02:00
Martin Bauer
caf6232dfb Added sysdweb 2020-05-02 11:21:52 +02:00
Martin Bauer
4308dae03d Ansible files 2020-03-30 22:49:44 +02:00
Martin Bauer
274f15b213 update 2020-01-16 21:09:14 +01:00
Martin Bauer
d9cd53477f Raspberry Pi setup as bluetooth speaker 2020-01-08 20:18:23 +01:00