From 3d0ae051920047bd352dc382609f20834a8b2ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 3 Nov 2026 20:53:28 +0100 Subject: [PATCH] docs: say the published image renders documents on a Raspberry Pi 4 The image runs the glibc canvas build now, which needs no ARMv8.2 features, so the Pi 4 gets thumbnails and scanned-PDF rendering back without a code change: the gate only ever closed for musl on arm64. The gate's comment, the env example and the getting-started prerequisites say so, and keep NATIVE_CANVAS as the override. The x64 AVX2 gate stays: the glibc x64 build rendered a PDF page under an emulated core without AVX2, but it still carries AVX2 code and that one run does not cover every path. --- .env.production.example | 11 ++++---- docs/self-hosting/getting-started.md | 9 ++++++- src/lib/documents/native-canvas-support.ts | 31 +++++++++++++--------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.env.production.example b/.env.production.example index b82aae357..207bd3d33 100644 --- a/.env.production.example +++ b/.env.production.example @@ -706,11 +706,12 @@ API_TOKEN_HMAC_KEY="" # ENVIRONMENT_AIR_QUALITY_DISABLED="1" # ── Native canvas renderer (document thumbnails, scanned-PDF rasterization) ── -# The bundled renderer needs a recent CPU: AVX2 on x86-64, and ARMv8.2 features -# (LSE atomics, RCpc) on arm64. On older CPUs (Celeron/Atom-class NAS chips, the -# Raspberry Pi 4) it is disabled automatically and those features degrade -# cleanly (no thumbnails; scanned PDFs read as text only). Override only if -# auto-detection misfires: +# On x86-64 the renderer is enabled only on CPUs with AVX2. On older chips +# (Celeron/Atom-class NAS CPUs) it is disabled automatically and those features +# degrade cleanly (no thumbnails; scanned PDFs read as text only). On arm64 the +# published image renders on every 64-bit core, the Raspberry Pi 4 included. +# Only a musl (Alpine) build of your own needs ARMv8.2 (LSE atomics, RCpc) and +# is gated the same way. Override only if auto-detection misfires: # off — force-disable the renderer # on — force-enable (crashes PDF processing if the CPU truly lacks them) # NATIVE_CANVAS="off" diff --git a/docs/self-hosting/getting-started.md b/docs/self-hosting/getting-started.md index 2575600c4..156a7333e 100644 --- a/docs/self-hosting/getting-started.md +++ b/docs/self-hosting/getting-started.md @@ -9,7 +9,14 @@ already installed, longer if a TLS-fronted public hostname is in scope - **Docker 24+ with Compose v2.** The bundled `docker-compose.yml` pulls a multi-arch image from GitHub Container Registry (linux/amd64 - and linux/arm64), so a Raspberry Pi 5 or an x86 VPS both work unchanged. + and linux/arm64), so a Raspberry Pi 4 or 5 or an x86 VPS all work + unchanged. The image is Debian-based (glibc). Document thumbnails and + scanned-PDF rendering use a native renderer that runs on every arm64 + board, the Pi 4 included, and on x86-64 CPUs with AVX2; on an older x86 + CPU (Celeron/Atom-class NAS chips) it switches itself off and those two + features fall back to text only. `NATIVE_CANVAS=off` or `on` in `.env` + overrides that detection (`on` crashes PDF processing on a CPU that truly + lacks the instructions). - **3 GB RAM (4 GB recommended), 10 GB free disk.** The Postgres data volume grows with measurement history; the Apple Health import worker can briefly hold a 1.5 GB upload in `/tmp` while it parses. The app diff --git a/src/lib/documents/native-canvas-support.ts b/src/lib/documents/native-canvas-support.ts index 88f496254..1006d68d9 100644 --- a/src/lib/documents/native-canvas-support.ts +++ b/src/lib/documents/native-canvas-support.ts @@ -1,13 +1,19 @@ /** * v1.28.27 — CPU-feature gate for the native canvas renderer. * - * The bundled `@napi-rs/canvas` (Skia) x64 builds use AVX2 instructions. On an + * The `@napi-rs/canvas` (Skia) x64 builds carry AVX2 instructions. On an * x86-64 host WITHOUT AVX2 — Celeron/Atom-class NAS CPUs (Goldmont Plus and - * older) are the common self-hosting case — the first canvas call dies with - * SIGILL (`trap invalid opcode … in skia.linux-x64-musl.node`), which kills the - * whole Node process and puts the container into an infinite restart loop - * (live incident: a Synology DS1520+ self-host, exit code 132 ~18 s after - * every boot once the thumbnail backfill touched Skia). + * older) are the common self-hosting case — the musl build died on the first + * canvas call with SIGILL (`trap invalid opcode … in skia.linux-x64-musl.node`), + * which kills the whole Node process and puts the container into an infinite + * restart loop (live incident: a Synology DS1520+ self-host, exit code 132 + * ~18 s after every boot once the thumbnail backfill touched Skia). + * + * The published image is Debian (glibc) and runs the glibc build. Under an + * emulated AVX2-less Atom core that build drew a canvas and rendered + * a PDF page to JPEG where the musl build died, but it still carries AVX2 code + * and not every path it can take was exercised, so the x64 gate stays closed + * without the flag. `NATIVE_CANVAS=on` is the way to try it on such a host. * * A SIGILL cannot be caught in-process, so the only safe posture is to never * load the module on an unsupported CPU. Every canvas consumer (document @@ -15,12 +21,13 @@ * the gate closed those features degrade cleanly (no thumbnails, PDFs read as * text/native only) while the rest of the app runs normally. * - * The arm64 musl build (the one the published arm64 image runs) is compiled - * for ARMv8.2 and later: it uses LSE atomics (`ldaddal`, `casal`) and RCpc - * loads (`ldapr`) throughout, with no runtime fallback, and dies with SIGILL - * as soon as the module loads on an ARMv8.0 core such as the Raspberry Pi 4's - * Cortex-A72. The glibc arm64 build uses neither, so a glibc host is left - * alone. + * The arm64 musl build is compiled for ARMv8.2 and later: it uses LSE atomics + * (`ldaddal`, `casal`) and RCpc loads (`ldapr`) throughout, with no runtime + * fallback, and dies with SIGILL as soon as the module loads on an ARMv8.0 + * core such as the Raspberry Pi 4's Cortex-A72 (#1311). The glibc arm64 build + * uses neither and runs there, which is why the published image moved off + * Alpine. The musl branch stays for a host that runs the app on musl itself, + * from source on Alpine or from an image built on it. * * Detection reads `/proc/cpuinfo` once on Linux: x64 requires the `avx2` * flag, arm64 under musl requires the `atomics` and `lrcpc` features. Every