diff --git a/hosts/thin-client/live-build/config/hooks/normal/0800-eww-widget.hook.chroot b/hosts/thin-client/live-build/config/hooks/normal/0800-eww-widget.hook.chroot old mode 100644 new mode 100755 diff --git a/hosts/thin-client/live-build/config/hooks/normal/0900-firefox.hook.chroot b/hosts/thin-client/live-build/config/hooks/normal/0900-firefox.hook.chroot old mode 100644 new mode 100755 diff --git a/hosts/thin-client/live-build/config/hooks/normal/1000-ydotool.hook.chroot b/hosts/thin-client/live-build/config/hooks/normal/1000-ydotool.hook.chroot old mode 100644 new mode 100755 diff --git a/tools/README.md b/tools/README.md index f75faf7..67429ef 100644 --- a/tools/README.md +++ b/tools/README.md @@ -94,6 +94,24 @@ Building one image is supported but unusual: the images are a set that has to ag with itself, which is why `build-all.sh` is the default and a failure in one image doesn't abandon the rest. +### One entry, one image + +Kiosks and audio endpoints come from **arrays** in the config, and every entry gets its +own artifact in `build-output/`, named `smarthome--.iso`. That naming +is load-bearing rather than cosmetic: live-build always writes the same filename into +the same per-host tree, so two thin clients would otherwise have the second silently +overwrite the first — leaving one ISO carrying the second room's hostname and nothing +to indicate the first was lost. + +Per-type builders take a hostname when several of that type exist, and refuse with the +list of real ones if you omit it or get it wrong. + +**The one deliberate exception is the arm64 audio endpoint.** Raspberry Pi Imager sets +hostname and Wi-Fi per unit at flash time, so one generic `.img` serves every room and +`build-all.sh` builds it once no matter how many arm64 entries are listed. amd64 has no +equivalent tool for a generic x86 ISO, so it bakes the hostname in and genuinely needs +one ISO per room — see `docs/project-plan.md` Phase 15.5. + ## The ISOs contain secrets This is deliberate — burning everything in is what makes installation unattended, with diff --git a/tools/build-all.sh b/tools/build-all.sh index 56b3a04..788fdbf 100755 --- a/tools/build-all.sh +++ b/tools/build-all.sh @@ -137,18 +137,35 @@ if [[ "$MODE" == "all" || "$MODE" == "kiosks" ]]; then run_build "${ktype} — ${khost}" "$builder" "$khost" done + # The two audio-endpoint architectures are asymmetric on purpose, and it changes how + # many images each needs (docs/project-plan.md Phase 15.5): + # amd64 — no x86 equivalent of Pi Imager's OS Customisation exists, so the hostname + # is baked in at build time. One ISO PER ROOM. + # arm64 — Raspberry Pi Imager sets hostname/Wi-Fi per unit at flash time, so one + # generic .img serves every room. Built ONCE no matter how many are listed. + arm64_done="false" for row in "${AUDIO_ROWS[@]:-}"; do [[ -n "$row" ]] || continue IFS=$'\t' read -r aarch ahost _ <<< "$row" - # Two genuinely different toolchains, not one builder with a flag: live-build for - # the amd64 mini-PC, rpi-image-gen for the arm64 Pi. case "$aarch" in - amd64) builder="${SCRIPT_DIR}/build-audio-endpoint-iso-amd64.sh" ;; - arm64) builder="${SCRIPT_DIR}/build-audio-endpoint-image-arm64.sh" ;; - *) core_warn "Unknown audio endpoint arch '${aarch}' — skipping ${ahost}" - FAILED+=("audio/${ahost} (bad arch)"); continue ;; + amd64) + run_build "audio endpoint (amd64) — ${ahost}" \ + "${SCRIPT_DIR}/build-audio-endpoint-iso-amd64.sh" "$ahost" + ;; + arm64) + if [[ "$arm64_done" == "true" ]]; then + core_log "arm64 audio image already built — ${ahost} flashes the same .img (hostname set in Pi Imager)" + continue + fi + run_build "audio endpoint (arm64, generic image)" \ + "${SCRIPT_DIR}/build-audio-endpoint-image-arm64.sh" "$ahost" + arm64_done="true" + ;; + *) + core_warn "Unknown audio endpoint arch '${aarch}' — skipping ${ahost}" + FAILED+=("audio/${ahost} (bad arch)") + ;; esac - run_build "audio endpoint (${aarch}) — ${ahost}" "$builder" "$ahost" done fi diff --git a/tools/build-audio-endpoint-iso-amd64.sh b/tools/build-audio-endpoint-iso-amd64.sh index 3365ff3..09adf85 100755 --- a/tools/build-audio-endpoint-iso-amd64.sh +++ b/tools/build-audio-endpoint-iso-amd64.sh @@ -126,6 +126,10 @@ lb build ISO_PATH="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name '*.iso' -print -quit)" ISO_PATH="${ISO_PATH:-${LIVE_BUILD_DIR}/live-image-amd64.hybrid.iso}" +# Per-room name: unlike the arm64 image below, amd64 bakes the hostname in at build +# time (there is no x86 equivalent of Pi Imager's OS Customisation), so each room is a +# genuinely different ISO and they must not share a filename. +ISO_PATH="$(core_publish_image "$ISO_PATH" "audio-endpoint" "$IMAGE_HOSTNAME")" echo echo "=== Done ===" diff --git a/tools/build-door-panel-iso.sh b/tools/build-door-panel-iso.sh index 9ae390d..10d761b 100755 --- a/tools/build-door-panel-iso.sh +++ b/tools/build-door-panel-iso.sh @@ -242,6 +242,9 @@ lb build ISO_PATH="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name '*.iso' -print -quit)" ISO_PATH="${ISO_PATH:-${LIVE_BUILD_DIR}/live-image-amd64.hybrid.iso}" +# Published under this kiosk's own hostname — live-build reuses one filename per host +# tree, so two of the same kiosk type would otherwise overwrite each other. +ISO_PATH="$(core_publish_image "$ISO_PATH" "door-panel" "$IMAGE_HOSTNAME")" echo echo "=== Done ===" diff --git a/tools/build-kitchen-display-iso.sh b/tools/build-kitchen-display-iso.sh index 907cc71..77a10d8 100755 --- a/tools/build-kitchen-display-iso.sh +++ b/tools/build-kitchen-display-iso.sh @@ -244,6 +244,9 @@ lb build ISO_PATH="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name '*.iso' -print -quit)" ISO_PATH="${ISO_PATH:-${LIVE_BUILD_DIR}/live-image-amd64.hybrid.iso}" +# Published under this kiosk's own hostname — live-build reuses one filename per host +# tree, so two of the same kiosk type would otherwise overwrite each other. +ISO_PATH="$(core_publish_image "$ISO_PATH" "kitchen-display" "$IMAGE_HOSTNAME")" echo echo "=== Done ===" diff --git a/tools/build-thin-client-iso.sh b/tools/build-thin-client-iso.sh index 7e396ad..3becca6 100755 --- a/tools/build-thin-client-iso.sh +++ b/tools/build-thin-client-iso.sh @@ -328,6 +328,9 @@ lb build ISO_PATH="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name '*.iso' -print -quit)" ISO_PATH="${ISO_PATH:-${LIVE_BUILD_DIR}/live-image-amd64.hybrid.iso}" +# Published under this kiosk's own hostname — live-build reuses one filename per host +# tree, so two of the same kiosk type would otherwise overwrite each other. +ISO_PATH="$(core_publish_image "$ISO_PATH" "thin-client" "$IMAGE_HOSTNAME")" echo echo "=== Done ===" diff --git a/tools/build-touch-panel-iso.sh b/tools/build-touch-panel-iso.sh index d380aac..c2f592b 100755 --- a/tools/build-touch-panel-iso.sh +++ b/tools/build-touch-panel-iso.sh @@ -255,6 +255,9 @@ lb build ISO_PATH="$(find "$LIVE_BUILD_DIR" -maxdepth 1 -name '*.iso' -print -quit)" ISO_PATH="${ISO_PATH:-${LIVE_BUILD_DIR}/live-image-amd64.hybrid.iso}" +# Published under this kiosk's own hostname — live-build reuses one filename per host +# tree, so two of the same kiosk type would otherwise overwrite each other. +ISO_PATH="$(core_publish_image "$ISO_PATH" "touch-panel" "$IMAGE_HOSTNAME")" echo echo "=== Done ===" diff --git a/tools/lib/coreconfig.sh b/tools/lib/coreconfig.sh index 913a6a0..a8e875f 100644 --- a/tools/lib/coreconfig.sh +++ b/tools/lib/coreconfig.sh @@ -173,6 +173,42 @@ SMARTHOME_LLM_HOST=${CORE_LLM_HOST_IP} EOF } +# Move a finished image out of its live-build tree into build-output/, named for the +# thing it actually is. +# +# THIS IS LOad-BEARING FOR MULTI-INSTANCE BUILDS, not cosmetic. live-build always +# writes the same filename (live-image-amd64.hybrid.iso) into the same per-host tree, +# so building two thin clients — a living-room one and a bedroom one — would have the +# second silently overwrite the first, leaving one ISO carrying the second room's +# hostname and no indication the first was ever lost. Publishing under +# -- makes each config entry produce its own artifact. +core_publish_image() { + local src="$1" role="$2" instance="${3:-}" + local out_dir="${CORE_REPO_ROOT}/${CORE_BUILD_OUTPUT_DIR}" + mkdir -p "$out_dir" + + # Hostnames usually already carry the role ("thin-client-bedroom"), so appending the + # role verbatim gives "thin-client-thin-client-bedroom". Use the hostname alone when + # it already starts with the role. + local name="smarthome-${role}" + if [[ -n "$instance" ]]; then + if [[ "$instance" == "$role"* ]]; then + name="smarthome-${instance}" + else + name="smarthome-${role}-${instance}" + fi + fi + local dest="${out_dir}/${name}-$(core_pair_id).${src##*.}" + + if [[ ! -f "$src" ]]; then + core_warn "Expected an image at ${src} but found none — nothing published." + return 1 + fi + mv "$src" "$dest" + core_log "Image: ${dest}" + echo "$dest" +} + # Guard for the ISO builders: `lb build` needs root, and finding that out after the # config phase has already written files is worse than finding out now. core_require_root() {