3e6c337d1c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
55 lines
4.5 KiB
Markdown
55 lines
4.5 KiB
Markdown
# 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: <https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages>, "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).
|