Output images to iso-out/, and note why arm64 audio is one image for all rooms

Renames the image output directory from build-output/ to iso-out/ (config
default, .gitignore, docs).

Also documents the asymmetry that showed up when building a household with
several endpoints: kiosks and amd64 audio endpoints get one image per array
entry, because their hostname is baked in at build time; the arm64 audio image
is generic and built once no matter how many arm64 rooms are listed, because Pi
Imager sets hostname and Wi-Fi per unit at flash time. That is project-plan
Phase 15.5's existing decision, now reflected in what build-all actually does
rather than only in prose.

Verified with a stubbed full run of a six-endpoint household: one build-all
invocation produced nine distinctly-named images with no overwrites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
digest-per-person-and-agendas
Amir Alexander Abdelbaki 2026-07-31 13:28:08 +02:00
parent bbeabde97e
commit 919ce94d52
6 changed files with 10 additions and 10 deletions

2
.gitignore vendored
View File

@ -101,7 +101,7 @@ CoreSystemConfig.*.json
# Built images. These have every secret above burnt into them by design, so they are # Built images. These have every secret above burnt into them by design, so they are
# credential-bearing artifacts, not just large ones. # credential-bearing artifacts, not just large ones.
build-output/ iso-out/
*.iso *.iso
*.img *.img
*.img.xz *.img.xz

View File

@ -183,7 +183,7 @@
], ],
"build": { "build": {
"_comment": "output_dir is where finished ISOs land. It is gitignored — see .gitignore.", "_comment": "Where finished images land, relative to the repo root. Gitignored — see .gitignore — because these carry every secret in this file.",
"output_dir": "build-output" "output_dir": "iso-out"
} }
} }

View File

@ -102,7 +102,7 @@ chores/ Presence/calendar-driven household chore nudging +
- [ ] `chores` (Phase 20) — presence/calendar-driven household chore nudging: "I don't care who does it, as long as it gets done" — prefers whoever's been assigned a chore in `identity`'s admin panel but falls through to whoever's actually home rather than waiting (`CHORE_ASSIGNMENT_STRICT` flips that), redirects to someone else if a chore goes neglected, keeps a passive fairness tally that never feeds back into who gets nudged, and camera-checks trash bins/dishes/litter via Frigate + an Ollama vision model. **Litter remains the exception to everything** — it ignores both chore-exemption and assignment, because cleaning up what you left out was never a task anyone could be assigned. Built and wired into `setup-container-host.sh` (`ENABLE_CHORES`, off by default, every-2-hours systemd timer), **no Tapo camera hardware chosen and nothing run against real hardware**, see `chores/README.md` - [ ] `chores` (Phase 20) — presence/calendar-driven household chore nudging: "I don't care who does it, as long as it gets done" — prefers whoever's been assigned a chore in `identity`'s admin panel but falls through to whoever's actually home rather than waiting (`CHORE_ASSIGNMENT_STRICT` flips that), redirects to someone else if a chore goes neglected, keeps a passive fairness tally that never feeds back into who gets nudged, and camera-checks trash bins/dishes/litter via Frigate + an Ollama vision model. **Litter remains the exception to everything** — it ignores both chore-exemption and assignment, because cleaning up what you left out was never a task anyone could be assigned. Built and wired into `setup-container-host.sh` (`ENABLE_CHORES`, off by default, every-2-hours systemd timer), **no Tapo camera hardware chosen and nothing run against real hardware**, see `chores/README.md`
- [ ] Music Assistant (optional, additive multi-room audio) — wired into `setup-container-host.sh` (`ENABLE_MUSIC_ASSISTANT`, off by default), **its default port is an unverified guess that collides with `PANTRY_VISION_PORT`** if both are enabled together, see `docs/project-plan.md` open decision #31 - [ ] Music Assistant (optional, additive multi-room audio) — wired into `setup-container-host.sh` (`ENABLE_MUSIC_ASSISTANT`, off by default), **its default port is an unverified guess that collides with `PANTRY_VISION_PORT`** if both are enabled together, see `docs/project-plan.md` open decision #31
- [ ] `docs/network-integration.md` (OPNsense VLAN segmentation, the WireGuard split tunnel that carries arrival notifications, and why nothing here — ntfy included — gets port-forwarded to the WAN) — written, not run against a real OPNsense instance - [ ] `docs/network-integration.md` (OPNsense VLAN segmentation, the WireGuard split tunnel that carries arrival notifications, and why nothing here — ntfy included — gets port-forwarded to the WAN) — written, not run against a real OPNsense instance
- [ ] `tools/` + `CoreSystemConfig.json` — every build and setup script in one place, reading one config. The container host and LLM host build as a **twinned pair**: you set two last octets and the container host's `OLLAMA_HOST` is *derived* from the LLM host's, so the two ISOs cannot be built disagreeing about where the other one is; every kiosk's service URLs derive from the container host's address the same way. `build-all.sh` builds the set, `validate-config.py` refuses a build on duplicate ports (the `music_assistant`/`pantry_vision` 8095 clash, open decision #31), placeholder or padded tokens, duplicate hostnames, or a kiosk pointed at a disabled service. All secrets are burnt into the images so installs are unattended — **which makes every ISO a credential**; the filled-in config and `build-output/` are gitignored. **No ISO has ever been built with this** (`lb build` needs live-build, root and a long fetch) — what is tested is config validation/derivation and every generated artifact, with `lb` stubbed. See `tools/README.md` - [ ] `tools/` + `CoreSystemConfig.json` — every build and setup script in one place, reading one config. The container host and LLM host build as a **twinned pair**: you set two last octets and the container host's `OLLAMA_HOST` is *derived* from the LLM host's, so the two ISOs cannot be built disagreeing about where the other one is; every kiosk's service URLs derive from the container host's address the same way. `build-all.sh` builds the set, `validate-config.py` refuses a build on duplicate ports (the `music_assistant`/`pantry_vision` 8095 clash, open decision #31), placeholder or padded tokens, duplicate hostnames, or a kiosk pointed at a disabled service. All secrets are burnt into the images so installs are unattended — **which makes every ISO a credential**; the filled-in config and `iso-out/` are gitignored. **No ISO has ever been built with this** (`lb build` needs live-build, root and a long fetch) — what is tested is config validation/derivation and every generated artifact, with `lb` stubbed. See `tools/README.md`
## Quick start ## Quick start
@ -124,7 +124,7 @@ kiosk's service URLs derive from the container host's address the same way. See
Built ISOs contain every secret in the config, by design (nothing to configure Built ISOs contain every secret in the config, by design (nothing to configure
post-install) — which makes each one a credential. `.gitignore` covers both the post-install) — which makes each one a credential. `.gitignore` covers both the
filled-in config and `build-output/`. filled-in config and `iso-out/`.
To set up a host by hand instead of from an ISO: To set up a host by hand instead of from an ISO:

View File

@ -97,7 +97,7 @@ doesn't abandon the rest.
### One entry, one image ### One entry, one image
Kiosks and audio endpoints come from **arrays** in the config, and every entry gets its Kiosks and audio endpoints come from **arrays** in the config, and every entry gets its
own artifact in `build-output/`, named `smarthome-<hostname>-<pairid>.iso`. That naming own artifact in `iso-out/`, named `smarthome-<hostname>-<pairid>.iso`. That naming
is load-bearing rather than cosmetic: live-build always writes the same filename into 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 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 overwrite the first — leaving one ISO carrying the second room's hostname and nothing
@ -119,7 +119,7 @@ no env files to edit on a freshly-booted host. It also means **every ISO is a
credential**: Wi-Fi PSK, service tokens, MQTT and HA credentials, all readable by credential**: Wi-Fi PSK, service tokens, MQTT and HA credentials, all readable by
anyone holding the stick. anyone holding the stick.
`.gitignore` covers `CoreSystemConfig.json` and `build-output/`, so neither can be `.gitignore` covers `CoreSystemConfig.json` and `iso-out/`, so neither can be
committed by accident. Wiping old USB sticks is on you. committed by accident. Wiping old USB sticks is on you.
## Two things can't be burnt in ## Two things can't be burnt in

View File

@ -126,7 +126,7 @@ def main(argv: list[str]) -> int:
emit(f"CORE_ENABLE_{flag.upper()}", value) emit(f"CORE_ENABLE_{flag.upper()}", value)
emit("CORE_VOICE_WAKE_WORD", cfg.get("voice", {}).get("wake_word", "ok_nabu")) emit("CORE_VOICE_WAKE_WORD", cfg.get("voice", {}).get("wake_word", "ok_nabu"))
emit("CORE_BUILD_OUTPUT_DIR", cfg.get("build", {}).get("output_dir", "build-output")) emit("CORE_BUILD_OUTPUT_DIR", cfg.get("build", {}).get("output_dir", "iso-out"))
# --- The selected kiosk, if one was asked for --- # --- The selected kiosk, if one was asked for ---
if kiosk_hostname: if kiosk_hostname:

View File

@ -173,10 +173,10 @@ SMARTHOME_LLM_HOST=${CORE_LLM_HOST_IP}
EOF EOF
} }
# Move a finished image out of its live-build tree into build-output/, named for the # Move a finished image out of its live-build tree into iso-out/, named for the
# thing it actually is. # thing it actually is.
# #
# THIS IS LOad-BEARING FOR MULTI-INSTANCE BUILDS, not cosmetic. live-build always # 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, # 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 # 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 # second silently overwrite the first, leaving one ISO carrying the second room's