From 3a12288e4af692f9eea91f3f13676ae63d44e3f6 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 04:52:58 +0000 Subject: [PATCH] docs(cryptify): document /metrics endpoint and X-Cryptify-Source Adds the metrics_scan_interval_secs config row, a Metrics section covering the five Prometheus series shipped in cryptify#102 and the pre-seeded channels from cryptify#165, a channel-detection sub-section covering the X-Cryptify-Source priority chain and sanitization, and a GET /metrics bullet on the API list for parity with architecture.md and postguard.md. Closes #103 --- docs/repos/cryptify.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/repos/cryptify.md b/docs/repos/cryptify.md index 501bcdc..2f6d0a9 100644 --- a/docs/repos/cryptify.md +++ b/docs/repos/cryptify.md @@ -32,10 +32,11 @@ Configuration parameters: | `session_ttl_secs` | Idle TTL for an in-flight upload session, in seconds. The eviction deadline resets on each successful chunk PUT or `/status` call. Defaults to `3600` (60 minutes) | `3600` | | `staging_mode` | When `true`, `send_email` skips SMTP entirely and logs the intended email metadata at info level. The upload finalize still returns `Ok`. Defaults to `false`. Intended for staging deploys where real email delivery is undesirable | `false` | | `usage_db` | Path to the SQLite database used for upload usage accounting | `/app/data/usage.db` | +| `metrics_scan_interval_secs` | Interval in seconds for the background task that samples `data_dir` size and file count for the storage gauges exposed at `GET /metrics`. Defaults to `60`. | `60` | The `chunk_size` setting caps the size of each `PUT /fileupload/{uuid}` body. Clients (such as `@e4a/pg-js` and the PostGuard website) use the same value for their upload chunks, so increasing it server-side without updating the client default will not produce larger chunks on its own. -[Source: src/config.rs#L3-L36](https://github.com/encryption4all/cryptify/blob/ca6be91913d0313791c5bbeb3dd16152c85855c6/src/config.rs#L3-L36) +[Source: src/config.rs#L3-L38](https://github.com/encryption4all/cryptify/blob/2af3ba0736ba138343295669411334af6f6de37a/src/config.rs#L3-L38) ### Staging mode @@ -113,6 +114,7 @@ Cryptify exposes a file upload/download API. An OpenAPI 3.0 specification is ava - `POST /fileupload/finalize/{uuid}`: Finalize the upload (sends the recipient notification email if `notifyRecipients` was `true` on init). - `GET /fileupload/{uuid}/status`: Read rolling-token state to resume an in-flight upload across a page refresh or tab crash. Authenticated via `X-Recovery-Token`. - `GET /filedownload/{uuid}`: Download a file. Supports resumable downloads via the HTTP `Range` header (see [Range support on `/filedownload`](#range-support-on-filedownload) below). +- `GET /metrics`: Prometheus text-format metrics for monitoring (see [Metrics](#metrics) below). Unauthenticated; intended for scraping over a restricted network only. ### `POST /fileupload/init` request body @@ -203,6 +205,42 @@ The CORS preflight allowlist on the service includes `Range` alongside `Authoriz [Source: src/main.rs#L959-L1083](https://github.com/encryption4all/cryptify/blob/b1eff690eae096d4cec55e7dbf323d8d0c3d74c0/src/main.rs#L959-L1083) +## Metrics + +`GET /metrics` returns counters and gauges in the [Prometheus text exposition format](https://prometheus.io/docs/instrumenting/exposition_formats/) so a Prometheus instance can scrape Cryptify for dashboards and alerting. The endpoint is unauthenticated. It is intended to be reachable only from the internal monitoring network; deployments should restrict it with a firewall rule or a reverse-proxy allow-list in front of Cryptify. + +Five series are exposed: + +| Metric | Type | Description | +|---|---|---| +| `cryptify_uploads_total{channel}` | counter | Finalized uploads since process start, labelled by client channel. | +| `cryptify_upload_bytes_total{channel}` | counter | Bytes uploaded across finalized uploads, labelled by client channel. | +| `cryptify_storage_bytes` | gauge | Current bytes of uploads held under `data_dir`. Sampled every `metrics_scan_interval_secs`. | +| `cryptify_active_files` | gauge | Current file count under `data_dir`. Sampled on the same interval. | +| `cryptify_expired_files_total` | counter | Uploads purged before being finalized (idle-TTL eviction). | + +The two storage series are produced by a background task that walks `data_dir` once every `metrics_scan_interval_secs` (default `60`). The counters are updated inline on finalize and on eviction, so they do not depend on the scan cadence. + +At process startup Cryptify pre-seeds `cryptify_uploads_total` and `cryptify_upload_bytes_total` at zero for six known channel values: `website`, `staging-website`, `outlook`, `thunderbird`, `api`, and `unknown`. Without the pre-seed, Prometheus only creates a series the first time a request from a given channel lands, which means PromQL `increase()` over a window can read zero for a channel whose first observed sample is already non-zero. Pre-seeding also keeps the channel always visible on Grafana dashboards before any traffic arrives. + +[Source: src/metrics.rs#L28-L182](https://github.com/encryption4all/cryptify/blob/2af3ba0736ba138343295669411334af6f6de37a/src/metrics.rs#L28-L182) + +### Channel detection + +The `channel` label on `cryptify_uploads_total` and `cryptify_upload_bytes_total` is derived per request from the headers in this priority order: + +1. `X-Cryptify-Source` if present. +2. Otherwise `api` when the request carries `Authorization: Bearer …` or `X-Api-Key`. +3. Otherwise `staging-website` or `website` from the `Origin` header. +4. Otherwise `outlook` or `thunderbird` from the `User-Agent` substring. +5. Otherwise `unknown`. + +Whichever rule fires, the resulting value is lowercased, restricted to `[a-z0-9_-]`, and truncated to 32 characters before it reaches Prometheus, so clients cannot inject label syntax or explode cardinality with arbitrary inputs. An empty or all-dash result falls back to `unknown`. + +The first-party clients now set the header explicitly: the PostGuard website sends `X-Cryptify-Source: website` ([encryption4all/postguard-website#228](https://github.com/encryption4all/postguard-website/pull/228)), the Outlook add-in sends `outlook` ([encryption4all/postguard-outlook-addon#96](https://github.com/encryption4all/postguard-outlook-addon/pull/96)), and the Thunderbird add-in sends `thunderbird` ([encryption4all/postguard-tb-addon#121](https://github.com/encryption4all/postguard-tb-addon/pull/121)). A third party integrating its own Cryptify client should set `X-Cryptify-Source` to a stable identifier for that client to be classified correctly on the dashboards. Without it, the channel falls through to `Origin` or `User-Agent` heuristics and may land on `unknown`. + +[Source: src/metrics.rs#L184-L249](https://github.com/encryption4all/cryptify/blob/2af3ba0736ba138343295669411334af6f6de37a/src/metrics.rs#L184-L249) + ## Development ### Docker (recommended)