The 0.1.352 Docker image contained firmware compiled with CONFIG_ESPTOOLPY_FLASHSIZE=16MB
despite sdkconfig.defaults being updated to 4MB in d837598. Kaniko served a cached
firmware layer, bypassing the sdkconfig.defaults change.
Result: ESP32-S3 (4MB flash) flashed via Web Serial crashed on every boot:
spi_flash: Detected size(4096k) smaller than binary image header(16384k). Probe failed.
Fix:
- Add FIRMWARE_CACHE_BUST ARG before COPY in firmware stage (guarantees cache miss)
- Add RUN rm -f sdkconfig sdkconfig.old so idf.py set-target regenerates from
sdkconfig.defaults (CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y) on every build
Bumps version to 0.1.354 to trigger a fresh CI build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The multi-arch change (2cd4410) derived GOOS/GOARCH from TARGETPLATFORM
with wrong cut field indices (-f2/-f3), yielding the invalid pair
amd64/amd64 -> `go: unsupported GOOS/GOARCH pair amd64/amd64`, failing
every CI image build since May 24. CI builds amd64 only (ESP-IDF firmware
is x86_64-only), so pin linux/amd64 explicitly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds cmd/sim alongside the mothership and copies /spaxel-sim into the
final image so the same image can drive a synthetic-node CSI load against
a deployed mothership. Default ENTRYPOINT still runs the mothership.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Change runtime from debian:12-slim to gcr.io/distroless/static-debian12:nonroot
- Remove wget health check (distroless has no shell)
- Embed dashboard via go:embed (dashboard files now part of binary)
- Add build tag support for conditional embedding (production vs development)
- Dashboard serving code supports both embedded and filesystem-based serving
The dashboard is now embedded in the Go binary using go:embed with the
'embed' build tag. Production Docker builds use -tags=embed to enable
dashboard embedding, while development builds fall back to filesystem
serving. This aligns with the plan's security requirements for non-root
distroless runtime while maintaining developer ergonomics.
Closes: bf-1chgr
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add ARG TARGETPLATFORM/TARGETARCH for cross-platform builds
- Cross-compile Go binary using GOOS/GOARCH from TARGETPLATFORM
- ESP32 firmware build is amd64-only (ESP-IDF is x86_64)
- Creates placeholder on arm64 builds
- Removes placeholder in final stage, adds README
- Supports docker buildx --platform linux/amd64,linux/arm64
Closes: bf-2bxpx
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Dockerfile: use --flash_size 4MB and drop OTA data from merge_bin (OTA
data at 0xc10000 inflated binary to 12.6MB, exceeding 4MB chip flash)
- main.go: seedFirmwareDir now overwrites when source size differs, fixing
PVC staleness where old 1.6MB app-only binary was never replaced
- onboard.js: renderFlashFirmware() rewritten so all elements (button,
progress bar, status text, retry help, log panel) are inline in one
container — no separate floating modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guru Meditation/IllegalInstruction after flashing was caused by the
merged binary using default flash parameters instead of the project's
settings. esptool merge_bin flags use underscores and go after the
subcommand: --flash_mode dio --flash_freq 80m --flash_size 16MB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
esptool merge_bin combines bootloader (0x0), partition table (0x8000),
application (0x10000), and OTA data (0xc10000) into a single binary
flashable at offset 0x0 — matching the manifest address and enabling
correct initial flashing via the onboarding wizard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ESP-IDF 5.x requires explicit set-target even when CONFIG_IDF_TARGET
is present in sdkconfig.defaults.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
espressif/idf entrypoint is not invoked in multi-stage builds, so
idf.py is not in PATH. Sourcing export.sh activates the toolchain.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add espressif/idf:v5.2 as a multi-stage build step so the firmware
binary is baked into the image at /firmware/spaxel-firmware.bin.
On startup the mothership copies it into /data/firmware/ (PVC) if not
already present, making it immediately available for the onboarding
wizard without a manual upload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add complete health check implementation for Docker HEALTHCHECK and
Traefik health routing with:
Response fields:
- status: "ok" or "degraded"
- uptime_s: seconds since mothership boot
- version: mothership version string
- nodes_online: count of connected nodes
- db: "ok" or "failing" (SELECT 1 with 100ms timeout)
- load_level: 0-3 from load shedding state
- reason: human-readable explanation (only when degraded)
HTTP status codes:
- 200 for healthy (status="ok")
- 503 for degraded (status="degraded")
Degraded conditions:
- Database unreachable
- Load level 3 sustained for >60 seconds
- No nodes connected after 5 minutes uptime
Docker HEALTHCHECK updated to verify status="ok" response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>