Files
kvitt/docs/spikes/pi-image-arm64.md
T

4.5 KiB

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).