Merge origin/release/v1.46.0 into feat/v146-ai

This commit is contained in:
Marc-André Bombeck committed 2026-10-24 11:03:44 +02:00
commit 96eeec6080
47 files changed
+2588 -1122

No files matched your search

+29 -15
View File
@@ -19662,19 +19662,25 @@ paths:
capability envelope when it is closed), then rate / budget gated. With no provider configured this returns 422
(`documents.inbound.providerUnsupported`, with `meta.capability` and `meta.reason = \"no_provider\"`) — the
stored document is untouched, only the enhancement fails. Three modes: VISION (empty body) decrypts and scans
the stored original (PDF needs an Anthropic vision provider); TEXT (`application/json`, opt-in local OCR) `{
mode: \"text\", text }` structures browser-OCR'd text; STORED (`application/json`) `{ mode: \"stored\" }`
structures the document's own stored extracted text (its content index) — the manual recovery for a
skipped/failed automatic staging run, 422 `documents.inbound.notIndexed` when no stored text exists. Extraction
reproduces what the document states — it never interprets. Nothing reaches the structured stores here; the
confirm route is the only write path. With `Prefer: respond-async` (v1.40) the extraction runs in the
background: after the same quick refusals the route answers 202 with a run id, a second request while an
extraction of this document from the same input is queued or running answers with that run, and `GET
/api/ai-runs/{id}` serves `DocumentExtractRunResult` (`{ documentId, factsStaged, status }`, not the document
detail) as the run's `result`. Staging re-checks the document under a row lock, so a review finished while the
read ran fails the run with `documents.inbound.alreadyPartlyConfirmed` (409) or
`documents.inbound.alreadyConfirmed` (422) and leaves the review untouched; the synchronous form answers the
same in that race."
the stored original (a PDF is rendered and read a page at a time; one that cannot be rendered needs a provider
that reads PDFs natively, else 422 `documents.inbound.pdfNeedsAnthropic`); TEXT (`application/json`, opt-in
local OCR) `{ mode: \"text\", text }` structures browser-OCR'd text; STORED (`application/json`) `{ mode:
\"stored\" }` structures the document's own stored extracted text (its content index) — the manual recovery for
a skipped/failed automatic staging run, 422 `documents.inbound.notIndexed` when no stored text exists. A long
document is read a page or a part at a time under the same per-read limits as the lab scan (`LABS_OCR_EXTRACT`),
each read after the first reserved against the day's budget on its own; an extraction in which no read produced
a complete fact fails with 422 `documents.inbound.outputLimit` when the reads stopped at the provider's output
limit, 429 `documents.inbound.budgetExceeded` when the budget refused the reads, and 422
`documents.inbound.extractFailed` only when the provider's answers were not the schema. Extraction reproduces
what the document states — it never interprets. Nothing reaches the structured stores here; the confirm route is
the only write path. With `Prefer: respond-async` (v1.40) the extraction runs in the background: after the same
quick refusals the route answers 202 with a run id, a second request while an extraction of this document from
the same input is queued or running answers with that run, and `GET /api/ai-runs/{id}` serves
`DocumentExtractRunResult` (`{ documentId, factsStaged, status }`, plus `incomplete` when part of the document
may be missing from the staged facts; not the document detail) as the run's `result`. Staging re-checks the
document under a row lock, so a review finished while the read ran fails the run with
`documents.inbound.alreadyPartlyConfirmed` (409) or `documents.inbound.alreadyConfirmed` (422) and leaves the
review untouched; the synchronous form answers the same in that race."
parameters:
- name: id
in: path
@@ -35482,7 +35488,7 @@ components:
properties:
decisions:
minItems: 1
maxItems: 120
maxItems: 400
type: array
items:
type: object
@@ -38381,7 +38387,7 @@ components:
documents.inbound.factNotPending, documents.inbound.factTypeMismatch, documents.inbound.fileTooLarge,
documents.inbound.fileType, documents.inbound.invalidMetadata, documents.inbound.invalidQuery,
documents.inbound.invalidUpdate, documents.inbound.localOcrDisabled, documents.inbound.notFound,
documents.inbound.notIndexed, documents.inbound.pdfNeedsAnthropic,
documents.inbound.notIndexed, documents.inbound.outputLimit, documents.inbound.pdfNeedsAnthropic,
documents.inbound.providerUnsupported, documents.inbound.quotaExceeded, documents.inbound.rateLimited,
documents.inbound.restoreDuplicate, documents.inbound.restoreGone, documents.inbound.sourceAliasLimit,
documents.inbound.uploadBusy, documents.inbound.uploadTimeout, documents.inbound.vaccinationNotFound,
@@ -75913,6 +75919,14 @@ components:
- FAILED
- CONFIRMED
- DISCARDED
incomplete:
description: "Present when part of the document's facts may be missing from the staged ones: a long document is read a
page or a part at a time, and a read stopped at the provider's output limit (its complete facts kept), a
part could not be read or was not reached (the day's budget, the run's deadline, AI turned off meanwhile),
or the document holds more facts than one review takes. The staged facts are still worth reviewing; the
person should check them against the document."
type: boolean
const: true
required:
- documentId
- factsStaged
+20 -5
View File
@@ -199,11 +199,26 @@ dev --create-only --name vX_Y_Z_rollback_<col>`), edit the SQL,
## Catalog templates
A new required environment variable, or a change to the compose shape, needs a
matching pull request to the TrueNAS apps catalog in the same release cycle
(and an update to `docs/self-hosting/unraid/healthlog.xml` and
`docs/self-hosting/portainer/templates.json`). See
`docs/self-hosting/truenas.md` for what the catalog template carries.
The TrueNAS catalog bot follows new image tags on its own, so a routine
release needs nothing there. These changes do need a matching pull request to
`truenas/apps` (`ix-dev/community/healthlog/`) in the same release cycle, and
an update to `docs/self-hosting/unraid/healthlog.xml` and
`docs/self-hosting/portainer/templates.json`:
- a new required environment variable, or a new format or minimum for an
existing one (the form validates the secrets);
- a changed listening port, health check path or health semantics, or start
time beyond the template's five-minute start period;
- a required PostgreSQL major version, or an extension the stock `postgres`
image does not ship;
- any other change to the compose shape: a new container, a volume the app
must write to, a different user.
The catalog app sets `HEALTHLOG_PLATFORM=truenas`, `PORT`, `HOSTNAME`,
`DATABASE_URL`, `ENCRYPTION_KEY`, `API_TOKEN_HMAC_KEY`, `APP_URL`,
`NEXT_PUBLIC_APP_URL` and `SESSION_COOKIE_SECURE`; everything else reaches it
through the form's additional environment variables. See
`docs/self-hosting/truenas.md` for the operator side.
## Verify the served version
+7 -7
View File
@@ -274,13 +274,13 @@ Encryption: the step shows the key id and a fingerprint (the first 12 hex
characters of SHA-256 over the key) and can check a pasted copy without
storing it. Admins see a dashboard reminder until the active key is confirmed.
| Platform | Where the key is set |
| --------- | --------------------------------------------------------------- |
| Compose | `.env` next to `docker-compose.yml` |
| TrueNAS | Apps, HealthLog, Edit, Environment |
| Coolify | the application's Environment Variables |
| Unraid | the container template (Docker tab, Edit) or the Compose `.env` |
| Portainer | the stack's Environment variables |
| Platform | Where the key is set |
| --------- | -------------------------------------------------------------------------------------------------- |
| Compose | `.env` next to `docker-compose.yml` |
| TrueNAS | Apps, HealthLog, Edit: the Encryption Key field (`ENCRYPTION_KEY` in a YAML install's environment) |
| Coolify | the application's Environment Variables |
| Unraid | the container template (Docker tab, Edit) or the Compose `.env` |
| Portainer | the stack's Environment variables |
Set `HEALTHLOG_PLATFORM` (`compose`, `truenas`, `coolify`, `unraid`,
`portainer`) to open the step on your platform's instructions.
+171 -59
View File
@@ -1,82 +1,152 @@
# HealthLog on TrueNAS SCALE
# HealthLog on TrueNAS
HealthLog runs on TrueNAS SCALE as a Docker app: one `app` container from
`ghcr.io/mbombeck/healthlog` and one PostgreSQL 16 database. The app container
is stateless; everything you care about lives in the database dataset and in
one value you must keep yourself: the encryption key.
HealthLog runs on TrueNAS Community Edition (formerly SCALE) as a Docker app:
one container from `ghcr.io/mbombeck/healthlog` and one PostgreSQL database.
The app container is stateless. Everything you care about lives in the
database dataset and in one value you must keep yourself: the encryption key.
## What the app needs
> **Status: pending catalog review.** A HealthLog app for the TrueNAS
> Community train has been prepared but is not listed yet. Until it is, use
> [Install via YAML](#before-the-listing-install-via-yaml) at the end of this
> page. This note goes away once the app appears under Apps, Discover Apps.
| Setting | Value |
| ------------ | ---------------------------------------------------------------------------------------- |
| Image | `ghcr.io/mbombeck/healthlog`, tag `X.Y.Z` (amd64 and arm64) |
| Port | `3000` (HTTP) |
| User | runs as uid `1001`; no extra capabilities, `no-new-privileges` is fine |
| Health check | `GET /api/health`, `200` when ready |
| Start period | at least 60 s, with more retries than the image's 5 × 30 s: migrations run at every boot |
| Database | PostgreSQL 16 (17 and later are not a drop-in), no extensions |
| Volumes | none for the app; the database gets its own dataset |
## Install from the Community train
## Environment
1. Generate two secrets. Open the TrueNAS shell (System, Shell) and run the
command twice, once for each field:
| Variable | Required | Notes |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `DATABASE_URL` | yes | `postgresql://<user>:<password>@<host>:5432/<db>` |
| `ENCRYPTION_KEY` | yes | 64 hex characters: `openssl rand -hex 32`. Keep a copy outside the NAS (see below). |
| `API_TOKEN_HMAC_KEY` | yes | 64 hex characters, generated the same way. Changing it signs everyone out. |
| `NEXT_PUBLIC_APP_URL` | yes | the address people open, for example `http://truenas.local:30180` |
| `APP_URL` | yes | the same address |
| `SESSION_COOKIE_SECURE` | no | `false` for a LAN install served over plain `http://`; leave unset behind an HTTPS proxy |
| `HEALTHLOG_PLATFORM` | no | `truenas`: the admin's key backup step opens on the TrueNAS instructions |
| `GEOLITE2_DIR` | no | a mounted directory of GeoLite2 `.mmdb` files, so sign-in locations are resolved on the NAS |
```bash
openssl rand -hex 32
```
`HEALTHLOG_PROCESS_TYPE` stays unset (`all`): one container serves the web app
and runs the background jobs.
Each run prints 64 hex characters. Put both in your password manager
before you continue; see [Back up your encryption key](#back-up-your-encryption-key).
2. Open Apps, Discover Apps, search for **HealthLog** and choose Install.
3. Fill in the form:
| Field | What to enter |
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
| Timezone | your timezone |
| Database Password | any strong password; only the two containers use it |
| Encryption Key | the first `openssl rand -hex 32` output |
| Token Signing Key | the second output. Changing it later signs everyone out |
| App URL | the address you open HealthLog at, with scheme and port, for example `http://192.168.1.10:30536` |
| Secure Cookies | leave off when you open HealthLog over `http://` on the LAN; turn on behind an HTTPS reverse proxy (see below) |
| WebUI Port | `30536` unless that port is taken |
| Postgres Data Storage | the default ixVolume, or a host path on a dataset you snapshot |
| Resources | keep memory at 1536 MB or more; the default of 4096 MB is fine |
4. Install. The first start applies the database migrations before the web
server listens, so give it a minute. The app shows as running once
`/api/health` answers.
5. Open the App URL. The first account you register becomes the admin.
The App URL has to match the address in the browser: passkeys, sign-in
redirects and the links in notifications are built from it. If you later put
HealthLog behind a reverse proxy with its own domain, change App URL to that
`https://` address.
Settings the form does not show go under Additional Environment Variables, for
example `UPDATE_CHECK_DISABLED=1`. The full list is in
[environment.md](environment.md). To use your own GeoLite2 databases, add the
directory under Additional Storage and point `GEOLITE2_DIR` at its mount path
([geolite2.md](geolite2.md)).
## Plain HTTP on the LAN
Most NAS installs are reached over `http://`. Set `SESSION_COOKIE_SECURE=false`
for that. If it is left at its default, the browser drops the sign-in cookie
and the login page says so before you type a password: "This server only sets
its sign-in cookie over https://".
Most NAS installs are reached over `http://`. Leave **Secure Cookies** off for
that. With it on, the browser drops the sign-in cookie on a plain-HTTP page,
and the login page says so before you type a password: "Signing in cannot
complete on this page". Turn it on only when HealthLog is served over
`https://`, and set App URL to that `https://` address.
## Back up your encryption key
HealthLog encrypts tokens, notes, documents and in-database backups with
`ENCRYPTION_KEY`. The key is not stored in the database. If the app is deleted
and reinstalled with a new key over the old database dataset, the data is
HealthLog encrypts tokens, notes, documents and in-database backups with the
Encryption Key. The key is not stored in the database. If the app is deleted
and installed again with a new key over the old database dataset, the data is
still there but cannot be read.
1. Copy `ENCRYPTION_KEY` from the app's settings (Apps, HealthLog, Edit,
Environment) into your password manager.
2. Sign in as the admin and open Admin, Encryption. The step shows the key id
and a fingerprint, the first 12 hex characters of SHA-256 over the key.
Compare it with your copy:
TrueNAS keeps the values you typed into the form with the app's configuration
on the apps pool. Treat that as a convenience, not as your copy: deleting the
app removes it, and a reinstall starts with empty fields. Keep the key in a
password manager or another place outside the NAS.
1. Open Apps, select HealthLog and choose Edit. The key is the Encryption Key
field under HealthLog Configuration.
2. Sign in as the admin and open Admin, Encryption. The **Back up your
encryption key** step shows the active key id and a fingerprint, the first
12 hex characters of SHA-256 over the key. Compare it with your copy:
```bash
echo -n "<your key>" | xxd -r -p | sha256sum | cut -c1-12
```
or paste the copy into "Check my copy" (it is compared and dropped, never
stored or logged).
or paste the copy into **Check my copy**. It is compared and dropped, never
stored or logged.
3. Confirm the backup. Until you do, admins see a reminder on the dashboard.
After a key rotation the step comes back for the new key.
3. Press **I have backed up this key**. Until you do, admins see a reminder on
the dashboard. After a key rotation the step comes back for the new key.
The catalog app sets `HEALTHLOG_PLATFORM=truenas`, so the step opens on these
instructions.
## Backups and restore
A restore needs two things: a backup of the database **and** the Encryption
Key that was active when it was taken. One without the other gets you nothing
back.
- **Snapshots.** Snapshot the Postgres Data Storage dataset (the ixVolume
lives under the apps pool, a host path wherever you put it). Stop the app
first, or the snapshot holds a database that was mid-write.
- **A dump.** Open a shell on the `postgres` container (Apps, HealthLog,
the container's Shell) and run:
```bash
pg_dump -U healthlog healthlog > /var/lib/postgresql/healthlog-$(date +%F).sql
```
The file lands in the Postgres Data Storage dataset, next to the database
files, so copy it off the NAS as well.
- **Off-host.** HealthLog can push encrypted nightly backups to any
S3-compatible bucket; see [backup-restore.md](../ops/backup-restore.md).
To restore a dump, run the app with the Encryption Key the dump was taken
under, put the dump file in the Postgres Data Storage dataset, and from the
`postgres` container shell replace the database with it:
```bash
psql -U healthlog -d postgres \
-c "DROP DATABASE healthlog WITH (FORCE);" \
-c "CREATE DATABASE healthlog OWNER healthlog;"
psql -v ON_ERROR_STOP=1 -U healthlog healthlog < /var/lib/postgresql/healthlog-YYYY-MM-DD.sql
```
Then restart the app. Restoring over the existing database without dropping
it first does not work: the tables the background queue creates on its own
refuse a plain overwrite.
## When the key does not match the database
At every start HealthLog checks that its key opens the data already in the
database. If it does not, it refuses to serve instead of failing on every
encrypted value: pages show "HealthLog cannot open its data", API requests
answer `503` with `meta.errorCode` `encryption.key_mismatch`, `/api/health`
answers `503` with `reason: "encryption_key_mismatch"`, and no background jobs
run. TrueNAS shows the app as unhealthy rather than crashed, so the reason
stays visible.
database. A missing or malformed key stops the container at once with
`HealthLog: ERROR — encryption configuration is invalid.` in its log. A
well-formed key that does not open the stored data is different: HealthLog
refuses to serve instead of failing on every encrypted value. Pages show
"HealthLog cannot open its data", API requests answer `503` with
`meta.errorCode` `encryption.key_mismatch`, `/api/health` answers `503` with
`reason: "encryption_key_mismatch"`, and no background jobs run. TrueNAS
shows the app as unhealthy rather than crashed, so the reason stays visible in
the app's logs.
Fix it one of three ways, then restart the app:
1. Put the original `ENCRYPTION_KEY` back.
2. Point `DATABASE_URL` at the database that belongs to this key.
1. Put the original Encryption Key back (Apps, HealthLog, Edit).
2. Point the app at the database that belongs to this key, for example by
restoring that dataset.
3. Start fresh with an empty database. The old data and its backups need the
old key.
@@ -91,9 +161,8 @@ checks again at the next start.
If you are certain the configured key is the one the data was written with,
and the app still refuses because the check recorded a different key earlier,
remove that record and restart. Open a shell on the database container (Apps,
HealthLog, the database container's Shell) and run, with the key id the log
names (`v1` unless you use `ENCRYPTION_KEYS`):
remove that record and restart. Open a shell on the `postgres` container and
run, with the key id the log names (`v1` unless you use `ENCRYPTION_KEYS`):
```bash
psql -U healthlog healthlog \
@@ -108,7 +177,7 @@ id: the rotation script removes its record once no values remain under it.
On a first start there is no record to remove. If the check refuses there and
you are certain the key is right, add `ENCRYPTION_KEY_CHECK` = `warn` under
Apps, HealthLog, Edit, Environment as a last resort. The check still runs and
Additional Environment Variables as a last resort. The check still runs and
logs, `/api/health` reports `warning: "encryption_key_mismatch"` without
failing, the app serves, and no key is recorded until the check passes. This
switches off a safety check: with a wrong key every encrypted value fails and
@@ -117,6 +186,49 @@ confirmed.
## Updates
Pick the new tag, take a snapshot of the database dataset first, and update.
Migrations run on start; the first start of a new install applies all of them,
which is why the start period above is generous.
The catalog follows HealthLog releases on its own: when a new image is
published, the TrueNAS catalog picks it up and the app shows an update. Take a
snapshot or a dump first, then update.
Database migrations run at every start, before the web server listens, and
only go forward. The start log says how many are pending. Going back to an
older version with Roll Back does not undo them, and an older version may not
start against a database a newer one has migrated. The way back is the
database backup you took before the update, restored together with the older
version.
The Postgres Image field offers the Postgres major versions the catalog
supports. Changing it runs a one-way upgrade of the database files, so back up
before you touch it.
## Before the listing: install via YAML
Until the catalog app is listed, install HealthLog with Apps, Discover Apps,
the menu next to Custom App, Install via YAML, using these values:
| Setting | Value |
| ------------ | ---------------------------------------------------------------------------------------- |
| Image | `ghcr.io/mbombeck/healthlog`, tag `X.Y.Z` (amd64 and arm64) |
| Port | `3000` (HTTP) |
| User | runs as uid `1001` by default; no extra capabilities, `no-new-privileges` is fine |
| Health check | `GET /api/health`, `200` when ready |
| Start period | at least 60 s, with more retries than the image's 5 × 30 s: migrations run at every boot |
| Database | PostgreSQL 16 or 18, no extensions, in its own container with its own dataset |
| Volumes | none for the app; the database gets its own dataset |
| Variable | Required | Notes |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `DATABASE_URL` | yes | `postgresql://<user>:<password>@<host>:5432/<db>` |
| `ENCRYPTION_KEY` | yes | 64 hex characters: `openssl rand -hex 32`. Keep a copy outside the NAS. |
| `API_TOKEN_HMAC_KEY` | yes | 64 hex characters, generated the same way. Changing it signs everyone out. |
| `NEXT_PUBLIC_APP_URL` | yes | the address people open, for example `http://truenas.local:30180` |
| `APP_URL` | yes | the same address |
| `SESSION_COOKIE_SECURE` | no | `false` for a LAN install served over plain `http://`; leave unset behind an HTTPS proxy |
| `HEALTHLOG_PLATFORM` | no | `truenas`: the admin's key backup step opens on the TrueNAS instructions |
`HEALTHLOG_PROCESS_TYPE` stays unset (`all`): one container serves the web app
and runs the background jobs. In a YAML install the key sits in the
environment block, so the place to copy it from is `ENCRYPTION_KEY` there.
To move from a YAML install to the catalog app later, take a dump, install the
catalog app with the same key, and restore the dump as described under
[Backups and restore](#backups-and-restore).