Three changes:
1. Dockerfile.rust now builds sensing-server with `--features mqtt`
(ADR-115 HA-DISCO publisher) and also builds + ships the
cog-ha-matter binary (ADR-116 Home Assistant + Matter cog with
mDNS, embedded broker, RuVector-backed thresholds, Ed25519 witness).
Adds EXPOSE 1883 for the embedded MQTT broker.
2. docker-entrypoint.sh routes `docker run <image> cog-ha-matter ...`
(or `ha-matter`) to /app/cog-ha-matter, defaulting --sensing-url to
http://127.0.0.1:3000 so a docker-compose deployment works out of
the box. The default entrypoint (no first arg) still launches
sensing-server unchanged.
3. Workflow path filter now also fires on changes to
v2/crates/wifi-densepose-bfld/** and v2/crates/cog-ha-matter/**
so future iteration on those crates rebuilds the image.
DOCKERHUB_TOKEN rotated separately (was expired since 2026-05-13,
which is why the last 5 workflow runs failed at the Docker Hub login
step and `latest` on Docker Hub has stayed amd64-only despite #631
being merged). With this commit + rotated token, the next CI run
should land a multi-arch `:latest` with HA-DISCO + cog-ha-matter +
BFLD support.
Reproduced kutayozdur's pull failure on ruv-mac-mini (Apple Silicon,
Darwin arm64) via Tailscale before fixing.
Refs #794, #631, ADR-115, ADR-116, ADR-118.
Co-Authored-By: claude-flow <ruv@ruv.net>
PR #547 refreshed the sensing-server docker publish and the README badge
advertises 'Docker: multi-arch amd64 + arm64', but
.github/workflows/sensing-server-docker.yml only sets
'platforms: linux/amd64'. The arm64 layer was never actually wired in.
Consequence on Docker Hub today (ruvnet/wifi-densepose:latest, last pushed
2026-05-14 by #547):
$ curl -s https://hub.docker.com/v2/repositories/ruvnet/wifi-densepose/tags/latest/
images:
arch=amd64 os=linux
arch=unknown os=unknown # the 1.5KB attestation layer, not arm64
So Apple Silicon Macs (the platform in #625) hit:
docker pull ruvnet/wifi-densepose:latest
Error: no matching manifest for linux/arm64/v8 in the manifest list
This is the same crash class as the closed-unmerged #136 'Docker error on
MacOS'; #625 is a fresh report (Mac M3 Pro, macOS Tahoe 26.4.1) of the same
bug.
Fix is the standard buildx multi-arch recipe:
1. Add docker/setup-qemu-action@v3 before setup-buildx so the amd64 runner
can cross-build the arm64 layer (QEMU user-mode emulation).
2. Change 'platforms: linux/amd64' -> 'platforms: linux/amd64,linux/arm64'.
docker/Dockerfile.rust is already arch-agnostic — no '--target' flag, no
amd64-only Cargo deps, only 'cc = "1.0"' which is cross-aware — so no
Dockerfile changes are needed. Buildx + QEMU does the rest.
Smoke tests are unaffected: they 'docker pull' on ubuntu-latest (amd64), so
the runner auto-selects the amd64 entry from the multi-arch manifest.
Multi-arch manifests are transparent to single-arch consumers.
Scope discipline: this PR only touches sensing-server-docker.yml (the file
issue #625 is about). nvsim-server-docker.yml has the identical
'platforms: linux/amd64' bug but is out of scope here — happy to file
a follow-up if useful.
Note (not part of this fix): the last 5 runs of this workflow have failed
at the 'Log in to Docker Hub' step (DOCKERHUB_TOKEN secret looks rotated/
expired). That's a separate, secret-side issue I can't touch from a PR.
Once that's resolved, the next push to main will produce a proper
amd64+arm64 manifest for the first time.
Co-authored-by: Mack Ding <mack@claws.ltd>
Closes#520, #514, #443.
## #520 / #514 — stale Docker image, missing UI assets
`ruvnet/wifi-densepose:latest` was published before `ui/observatory*` and
`ui/pose-fusion*` were added; users see /app/ui missing those files and the
v0.6+ packet format doesn't reach the server. Two fixes:
1. `docker/Dockerfile.rust` now `RUN`s a build-time guard after `COPY ui/`
that fails the build if `index.html` / `observatory.html` / `pose-fusion.html`
/ `viz.html` (or the `observatory/` / `pose-fusion/` / `components/` /
`services/` directories) are missing, plus an exec-bit check on
`/app/sensing-server`. A stale image can never be silently produced again.
2. New `.github/workflows/sensing-server-docker.yml` rebuilds + pushes on
every change to the Dockerfile, the server crate, the signal/vitals/
wifiscan crates, the workspace manifests, the `ui/` tree, or itself —
plus `v*` tags and manual dispatch. Pushes to both `docker.io/ruvnet/
wifi-densepose` AND `ghcr.io/ruvnet/wifi-densepose` with `latest` +
`vX.Y.Z` + `sha-<short>` tags, then post-push smoke-tests the artifact:
/health, /api/v1/info, the observatory + pose-fusion HTML, AND the
bearer-auth path (no token → 401, wrong → 401, correct → 200). Uses the
`DOCKERHUB_USERNAME`/`DOCKERHUB_TOKEN` repo secrets; ghcr.io rides on
the workflow's GITHUB_TOKEN.
## #443 — sensing-server REST API auth model
QE security audit raised that 40+ /api/v1/* routes have no auth layer with
a default `0.0.0.0` bind. New `wifi_densepose_sensing_server::bearer_auth`
module + middleware:
- Env-var-gated: `RUVIEW_API_TOKEN` unset/empty ⇒ middleware is a no-op
(current LAN-mode behaviour preserved — **no default change**); set ⇒
every `/api/v1/*` request must carry `Authorization: Bearer <token>`
or the server returns 401.
- Constant-time byte compare via local `ct_eq` (no new dep).
- `/health*`, `/ws/sensing`, and `/ui/*` are intentionally never gated
(orchestrator probes + local browsers).
- Startup logs which mode is active and warns when auth is ON with a
`0.0.0.0` bind.
- 8 unit tests on the middleware via `tower::ServiceExt::oneshot`
(sensing-server lib tests 191 → 199, 0 failures).
Verified locally: `cargo build --workspace --no-default-features` ✓,
`cargo test -p wifi-densepose-sensing-server --no-default-features` ✓.
Co-Authored-By: claude-flow <ruv@ruv.net>