Installation was six scripts each carrying its own copy of the container host's
IP, three that had to agree on IDENTITY_TOKEN, and every service URL typed by
hand with a port in it. Any one could be wrong, and the symptom was always the
same and always late: an image that boots fine and then can't reach something,
found after a 40-minute build and a reboot.
Two properties fix that class of bug:
- Nothing is written twice. No script in tools/ contains an IP, port or token.
- Anything derivable is derived. You give the subnet prefix once and one last
octet per host; every address and service URL is computed from those.
THE TWINNED PAIR. container_host.ip_last_octet 12 and llm_host 13 mean the
container host's OLLAMA_HOST *is* http://<prefix>.13:11434 — computed in the
same build, not typed into two files and kept in sync. Move the LLM host to .21
and the container host's Ollama URL follows; change the subnet and both halves
move along with every kiosk's URLs. Neither image can be built pointing at an
address the other isn't using. Both carry the same SMARTHOME_PAIR_ID (a hash of
the config's meaning, not its bytes) so two USB sticks can be checked against
each other later.
validate-config.py runs before every build and refuses to start on an error, so
a mistake costs seconds not an hour. It catches duplicate ports (including the
music_assistant/pantry_vision 8095 clash that Compose can't see because MA runs
network_mode:host — open decision #31), both hosts on one address, duplicate
hostnames across kiosks and audio endpoints, placeholder tokens (checked before
the length check, so padding "changeme" to 32 chars doesn't pass), a private key
pasted where the public one goes, and a kiosk pointed at a disabled service.
build-all.sh is the normal entry point — the images are a set that has to agree
with itself, so building one is the exception. It builds the core pair, every
kiosk, and every audio endpoint including both architectures (amd64 live-build
ISO and arm64 rpi-image-gen img are different toolchains, not one image).
The two new host ISOs install unattended with everything burnt in, including
service env files generated from derived values — which permanently removes the
class of bug that had chores.env shipping IDENTITY_URL=http://127.0.0.1:8097.
setup-container-host.sh and setup-llm-host.sh now read every config value as
${VAR:-default} so the images configure them without editing.
That also makes every ISO a credential: Wi-Fi PSK, tokens, MQTT and HA
credentials are readable by anyone holding the stick. .gitignore covers the
filled-in CoreSystemConfig.json and build-output/.
Tested: 43 config validation/derivation checks and 44 builder checks against the
real code paths with only `lb` stubbed — every generated env file, preseed,
network config, first-boot unit and build stamp is verified, including that a
port collision refuses the build before writing anything. No ISO has been built;
`lb build` needs live-build, root and a long fetch. tools/README.md says so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two additions:
- Thin client: an eww weather/clock overlay over the idle-gallery slideshow.
Time/date come from the local clock, weather from a new retained MQTT topic
(smarthome/weather/current) an HA automation has to publish — degrades to
clock-only if that never happens. idle-gallery.sh owns the overlay's
visibility, mirroring how fullscreen-watcher.sh already owns the
now-playing widget's.
- hosts/audio-endpoint/: headless, per-room-independent Spotify Connect
appliances for rooms without a thin client, no shared/synced stream. Two
build pipelines sharing one architecture-independent systemd
unit/wrapper: arm64 (Raspberry Pi + HiFiBerry Amp2) via rpi-image-gen
producing a real flashable .img, and amd64 (mini PC + USB DAC/amp) reusing
the thin client's own live-build toolchain stripped of its graphical/kiosk
stack. Both reuse the thin client's existing apt-first/documented-fallback
Spotify Connect install logic rather than reinventing it. rpi-image-gen's
exact config schema is flagged explicitly as unverified — no ARM build
environment was available to validate it against the real tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgTwMqiho8DT6ue5S49WBP