diff --git a/docs/spikes/pi-image-arm64.md b/docs/spikes/pi-image-arm64.md new file mode 100644 index 0000000..fd89837 --- /dev/null +++ b/docs/spikes/pi-image-arm64.md @@ -0,0 +1,54 @@ +# Pi server image (linux/arm64) and the FCM `fid` target + +Date: 2026-10-18. Branch `agent-pi-image`. Machine: the owner's M4 MacBook, Podman 6-CPU `applehv` machine (arm64 Linux VM). Nothing was pushed to any registry; no Gradle build ran at the same time (the `/tmp/kvitt-gradle.lock` lock was free and no Gradle daemon was busy when each build started). + +## 1. FCM HTTP v1 `fid` field (D-083 follow-up) + +Source: , "Last updated 2026-09-08 UTC", read 2026-10-18. The `Message` union field `target` lists `token`, `fid`, `topic` and `condition` as mutually exclusive. The descriptions: + +* `token` (deprecated): "Deprecated: Use fid instead. During the transition period, this field also accepts a Firebase Installation ID (FID). Registration token to send a message to." +* `fid`: "Firebase Installation ID (FID) to send a message to." + +Change: `push::fcm::message_body` now puts the stored `devices.fcm_token` value (the phone's installation id, D-083) in `message.fid` instead of `message.token`. No database or API change; the column and the E-17 field keep the name `fcm_token`. `contracts/examples/push/*.json` (8 files) use `fid` with an example installation id, and the `push_fcm` tests check `fid` and that `token` is absent. + +## 2. Image build + +Command, from the worktree root (as `infra/dev/build.sh` does; `infra/build-images.sh` does not exist yet, it belongs to M2c-pi): + +``` +podman build --platform linux/arm64 --no-cache -f server/Dockerfile -t localhost/kvitt-server:pi-check server/ +``` + +| Check | Result | +|---|---| +| Build stage C toolchain | `rust:1.99.1-bookworm` carries gcc and cc 12.2.0 (Debian 12.2.0-14+deb12u1); no `cmake` binary, and none is needed | +| Cold compile (new target cache id, registry cache warm) | rc 0, 48 s wall, 232 crates compiled, `aws-lc-sys v0.46.0` and `aws-lc-rs v1.18.2` built, no warnings, `Finished release` in 44.7 s | +| Warm rebuild (shared target cache) | rc 0, 14 s | +| Image | arm64/linux, 63.8 MB, user `10001:10001`, entrypoint `kvitt-server`, cmd `serve` | +| CA roots | `/etc/ssl/certs/ca-certificates.crt` present (224,449 bytes, 150 certificates) from distroless `ca-certificates` | +| Binaries | `/usr/local/bin/kvitt-server` (19.9 MB), `/usr/local/bin/kvitt-import` (8.2 MB) | +| `kvitt-server --version` / `--help` | `kvitt-server 0.1.0`; help lists the six subcommands | +| `kvitt-import --version` | `kvitt-import 0.1.0` | + +The arm64 binaries ran natively in the arm64 Podman VM; qemu is not involved (the VM registers qemu only for x86 and big-endian arm). The binaries target generic Armv8-A (no `target-cpu=native`), so they suit the Pi 3's Cortex-A53; running on the Pi itself is still M2c-pi's. + +No Dockerfile change was needed. Two comments were added: where the C toolchain comes from, and where the CA bundle is. + +## 3. Live probe from the image + +Script: a throwaway Podman volume at `/data`, with the container hardened as on the dev stack (uid 10001, read-only root, no capabilities, `no-new-privileges`). `infra/secrets/` was mounted read-only at `/secrets` and `KVITT_FCM_PROJECT_ID=kvitt-1337` was set. The steps: `bootstrap`, then `serve`. Over the API: admin accepts (E-3), partner invite (E-12), member accepts (E-3), member E-17 with a made-up 22-character installation id that belongs to no installation, account (E-18), admin pushes one expense (E-32). The volume and container were removed afterwards. + +| Step | Result | +|---|---| +| Start-up log | `push enabled`, `project = kvitt-1337` | +| `/health` | 200, `schema_version` 3, `db_ok` true | +| API calls | E-3 200, E-12 201, E-3 200, E-17 204, E-18 201, E-32 200 `accepted` | +| Outbound TLS + OAuth | succeeded: the sender exchanged the service-account JWT at `https://oauth2.googleapis.com/token` and then called `fcm.googleapis.com` `messages:send`. A failed token fetch would end as "push failed" with a token error, never as an unregistered answer | +| FCM answer | `FCM token unregistered: cleared` (HTTP 404 or `UNREGISTERED`) about 0.5 s after start: FCM took the `fid` body and reported the made-up id as unknown. A field FCM did not know would have produced 400 `INVALID_ARGUMENT` | +| Container memory after the run | about 3.7 MB | + +The send call went to an installation id that does not exist, so no device received anything. No real device token was used. + +## Results (owner's phone) + +Still to come: an end-to-end push to a real phone through `fid`, as part of the planned end-to-end pairing, offline and push test (HANDOFF ยง3), and the image running on the Pi (M2c-pi). diff --git a/server/Dockerfile b/server/Dockerfile index 521d5e0..b40a5b4 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -6,6 +6,8 @@ # ---- stage 1: build -------------------------------------------------------------------------------------------- FROM docker.io/library/rust:1.99.1-bookworm AS build ARG TARGETARCH +# The full bookworm image carries gcc 12 and make, which aws-lc-sys (rustls via reqwest, jsonwebtoken) needs to +# compile its C code; no cmake binary is needed (checked 2026-10-18, docs/spikes/pi-image-arm64.md). # Use the image's toolchain as is; rust-toolchain.toml names the same version but would also pull clippy/rustfmt. ENV RUSTUP_TOOLCHAIN=1.99.1 \ CARGO_TERM_COLOR=never @@ -25,7 +27,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ && cp "target/$triple/release/kvitt-server" "target/$triple/release/kvitt-import" /out/ # ---- stage 2: runtime ------------------------------------------------------------------------------------------ -# glibc + libgcc, no shell, no package manager. Pinned by digest (multi-arch index of :nonroot). +# glibc + libgcc, no shell, no package manager. Pinned by digest (multi-arch index of :nonroot). It carries the +# Debian CA bundle at /etc/ssl/certs/ca-certificates.crt, which rustls-platform-verifier reads to reach Google. FROM gcr.io/distroless/cc-debian12:nonroot@sha256:9dac0a79194e45a7da0158a9c6da57b217585af0786db3845d1f0ec1a0dd182f COPY --from=build /out/kvitt-server /out/kvitt-import /usr/local/bin/ # Runs as uid/gid 10001 with a read-only root filesystem; /data is the only writable volume and /secrets is