Server: web client under /app/ (D-132), analytics E-34 trends and E-35 forecast (D-133)
Build Kvitt images / build (push) Failing after 1m20s

Part A (D-132): KVITT_WEB_DIR (default /app) served under /app/ by a small handler: /app redirects to
/app/, app routes fall back to index.html, a missing assets/* file is 404, assets cached immutable,
index.html no-store, the app's own CSP plus nosniff, DENY and no-referrer. Unsafe paths never reach the
file system. /app paths count against the unauthenticated per-IP limit. Without the directory every /app
path stays 404 not_found. server/Dockerfile gains a Node 24 LTS stage (pinned by digest) building web/,
passed through the Rust stage so npm and rustc never run at once on the Pi 3; the build context is the
repository root with an allowlist .dockerignore (server/.dockerignore removed); workflow, build-images.sh
and infra/dev/build.sh follow.

Part B (D-133): GET /analytics/trends (E-34) and GET /analytics/forecast (E-35), authenticated, read-only,
SQL month buckets; rules (half-up average, trailing 3-month moving average, percent against the rounded
average, template occurrence per month) in kvitt-domain::analytics. OpenAPI, examples, contracts-check
and tests updated (35 operations).

Cargo.lock: rustls-webpki 0.103.16 and webpki-root-certs 1.0.10 (RUSTSEC-2026-0382, -0383) so that
cargo audit passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-21 20:52:46 +02:00
parent 7bddc50a53
commit 50a0d6792a
32 changed files with 2247 additions and 48 deletions
+10 -5
View File
@@ -34,6 +34,7 @@ Configuration is environment variables only (SPEC §10.4, exhaustive):
| `KVITT_FCM_CREDENTIALS` | `/secrets/fcm-service-account.json` | FCM key; missing file = push disabled (warning at start). |
| `KVITT_FCM_PROJECT_ID` | `kvitt-1337` | FCM project. |
| `KVITT_TRUSTED_PROXY` | `none` | IP literal or host name allowed to set `X-Forwarded-For` (on the Pi `HA_HOST`, NPM's LAN address); `none` or a name that does not resolve means no proxy. |
| `KVITT_WEB_DIR` | `/app` | The built web client, served under `/app/` (D-132): `index.html` for app routes, `assets/*` cached for a year, the app's own CSP. Missing directory: every `/app` path is 404 `not_found` (as with `cargo run`, unless you point it at `web/dist`). |
| `RUST_LOG` | `info` | tracing filter; logs are JSON lines on stdout. |
The default data directory `/data` is the container volume. When running outside Docker, set `KVITT_DATA_DIR`
@@ -141,7 +142,8 @@ database was never fetched it prints a message and skips the check; it never fai
protocol and token rules, error shapes against `contracts/examples/errors/`, wire types against every example in
`contracts/examples/`.
* `crates/kvitt-server/tests/core_api/`: E-2 to E-30, E-33, generation, rate limiting, pictures, CLI.
* `crates/kvitt-server/tests/core_api/`: E-2 to E-30, E-33 to E-35 (analytics, D-133), generation, rate limiting,
pictures, CLI, the web client under `/app/` (D-132).
* `crates/kvitt-server/tests/sync_api/`: every rule of SPEC §14.4 (pull, push steps 1 to 12, rejection reasons,
§9.10 conflicts, 14-day offline scenario, the push examples), audit writer, `rotate-epoch`, end to end push → pull,
`ConnectInfo` rate-limit buckets over real TCP.
@@ -161,11 +163,14 @@ target itself); the sync, push and scheduler binaries also include `tests/common
## Docker image
```sh
docker build --platform linux/arm64 -f server/Dockerfile -t kvitt-server:dev server/
# from the repository root: the build context is the root (D-132), filtered by the root .dockerignore
docker build --platform linux/arm64 -f server/Dockerfile -t kvitt-server:dev .
```
Build on the Mac, never on the Pi (`infra/dev/build.sh` does this for the dev stack). Stage 1 is
`rust:<pinned version>-bookworm`, building both binaries in release mode with `--locked`; stage 2 is
`gcr.io/distroless/cc-debian12:nonroot` pinned by digest, binaries in `/usr/local/bin/`, user 10001, entrypoint
`kvitt-server`, command `serve`. `--platform linux/amd64` also works (emulated on the Mac). The container needs only
`node:24.22.1-bookworm-slim` (pinned by digest, on the build platform), running `npm ci && npm run build` in `web/`;
stage 2 is `rust:<pinned version>-bookworm`, building both binaries in release mode with `--locked` (it copies the
web build through, so the two never run at the same time); stage 3 is `gcr.io/distroless/cc-debian12:nonroot`
pinned by digest, binaries in `/usr/local/bin/`, the web client in `/app`, user 10001, entrypoint `kvitt-server`,
command `serve`. `--platform linux/amd64` also works (emulated on the Mac). The container needs only
`/data` writable and runs with a read-only root filesystem.