mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
docs: correct claims and anchors the code no longer backs
Fix two dead anchors (the MCP trust model, the README quick start) and the statements that no longer match the app: a guardian can connect a managed profile's services, the environment module is on by default and reaches the air-quality host too, its readable location columns were dropped in 0409, settings sections carry their current names, the Withings card lives on /settings/integrations, the TrueNAS start period matches the image, the deploy check names HEALTHLOG_PROD_URL, and the docs index describes what self-hosting/ and integrations/ hold.
This commit is contained in:
1 parent
f85035a8ec
commit
12f8265fdb
18 files changed
+73
-57
No files matched your search
@@ -50,7 +50,7 @@ Try the [live demo](https://demo.healthlog.dev) to see a working install — sig
|
||||
- **Clinical signals, opt-in and additive.** Validated self-checks for mental wellbeing and sleep (WHO-5, PHQ-9, GAD-7, Sleep Condition Indicator — official questionnaire wording, calm crisis-resource handling), grip strength, a 0–10 pain score, waist and body measurements, and a longevity lab panel — each added through one signal registry, so a new signal wires into charts, correlations, the coach, the FHIR map, and the API at once.
|
||||
- **Records, structured.** Allergies and family history as structured records that flow into the doctor report (reactions and notes are encrypted, the substance and condition names are not), plus lab values you key in or scan from a printed report — OCR extracts the stated values, units, and dates for you to review and confirm before anything is saved. HealthLog transcribes what a report says; it never interprets or diagnoses from it. A vaccination record keeps a lifetime immunization log, doses grouped under the disease they protect against, and prints in the doctor report.
|
||||
- **A document vault for the paper trail.** An opt-in module stores every letter, report, and scan encrypted at rest — PDFs and photos preview inline, Office and text files stay download-only, and nothing user-uploaded can ever execute in the app. One searchable timeline with type, condition, and year filters, drag-and-drop and paste-to-upload, bulk filing, undo-able delete with a 30-day grace, and per-file/per-account limits the operator tunes.
|
||||
- **Context that explains the numbers.** An opt-in environmental module folds weather, daylight, and temperature in as influence signals (winter darkness against mood, heat against blood pressure) using a home location you set, so a pattern has somewhere to point.
|
||||
- **Context that explains the numbers.** An environmental module folds weather, daylight, temperature, air quality, and pollen in as influence signals (winter darkness against mood, heat against blood pressure) using a home location you set, so a pattern has somewhere to point. It is on by default, and nothing leaves the host until someone sets that location.
|
||||
- **Your devices, one server.** Withings, WHOOP, Oura and Polar sync over OAuth2, Google Health connects Fitbit and Pixel Watch through your own Google Cloud client, Nightscout streams CGM glucose, an Apple Health `export.zip` folds your full history in, and the native iOS app streams HealthKit live. A per-metric source priority decides which reading is canonical when wearables overlap.
|
||||
- **Medication tracking that tells the truth.** Flexible schedules (weekly injections, weekday-only, intervals, PRN, cyclic), a configurable intake window per dose, and a traceable dose history where every slot reads taken, late, skipped, or missed — the same ledger the compliance rate is computed from, so the percentage can never disagree with the timeline.
|
||||
- **AI insights you own.** Daily briefing, health scores, correlations, and a conversational coach grounded in your own measurements and interpreted against cited guideline reference bands — where a value sits and what that means in plain words, never a diagnosis. The coach proposes concrete if-then plans you accept or decline, and suppresses proactive check-ins when there's nothing genuine to say. Bring your own OpenAI or Anthropic key, or point at a local endpoint (Ollama, LM Studio, vLLM) so AI requests stay on your network.
|
||||
|
||||
+13
-7
@@ -17,9 +17,13 @@ notes, and machine-readable specs that ship alongside the source:
|
||||
encryption-key rotation, password reset and account recovery, env
|
||||
checks, TLS pinning, data repair. These are what an operator reaches
|
||||
for during an incident.
|
||||
- [`self-hosting/`](./self-hosting/) — horizontal-scaling notes
|
||||
(`HEALTHLOG_PROCESS_TYPE=web|worker|all`) and the deploy-pipeline
|
||||
recipe. Routine install steps stay on `docs.healthlog.dev`. Includes
|
||||
- [`self-hosting/`](./self-hosting/) — operator notes next to the code:
|
||||
[`getting-started.md`](./self-hosting/getting-started.md) (install and
|
||||
updates, the page the boot log points at), the TrueNAS, Unraid and
|
||||
Portainer installs with their catalog templates, Tailscale, the reverse
|
||||
proxy, horizontal scaling (`HEALTHLOG_PROCESS_TYPE=web|worker|all`),
|
||||
notification channels, the environment module, document sources and
|
||||
reproductive-health data. Includes
|
||||
[`self-hosting/mcp.md`](./self-hosting/mcp.md) — enabling the
|
||||
off-by-default MCP connector and pointing Claude.ai / ChatGPT /
|
||||
Claude Desktop at it, and [`self-hosting/sso.md`](./self-hosting/sso.md)
|
||||
@@ -44,9 +48,11 @@ notes, and machine-readable specs that ship alongside the source:
|
||||
- [`migration/`](./migration/) — migration notes. Currently only
|
||||
v1.3 to v1.4; later releases are covered by `CHANGELOG.md` and
|
||||
<https://docs.healthlog.dev>.
|
||||
- [`integrations/`](./integrations/) — per-integration reference
|
||||
(Withings, Fitbit, Google Health, WHOOP, Apple Health, AI providers,
|
||||
data import).
|
||||
- [`integrations/`](./integrations/) — per-integration reference:
|
||||
Withings, WHOOP, Fitbit, Google Health, Strava, Nightscout, Dawarich and
|
||||
the Garmin route; the Apple Health, Health Connect, document and data
|
||||
imports; AI providers; and Home Assistant with its blueprint. Oura and
|
||||
Polar are covered on the docs site only.
|
||||
- [`adr/`](./adr/) — architecture decision records.
|
||||
- [`security/`](./security/) — threat-model notes.
|
||||
- [`diagrams/`](./diagrams/) — architecture diagrams rendered through
|
||||
@@ -70,4 +76,4 @@ notes, and machine-readable specs that ship alongside the source:
|
||||
|
||||
If you're a self-hoster looking to spin up an instance, start at
|
||||
<https://docs.healthlog.dev> or jump to the [Quick Start in the project
|
||||
README](../README.md#quick-start).
|
||||
README](../README.md#quick-start-docker-compose).
|
||||
@@ -295,7 +295,7 @@ Concrete consequences:
|
||||
RMSSD HRV has its own ladder, **WHOOP ≻ OURA ≻ POLAR ≻ APPLE_HEALTH**,
|
||||
separate from the SDNN one.
|
||||
|
||||
Override per-user via Settings → Sources (`/settings/sources`).
|
||||
Override per-user via Settings → Source priority (`/settings/sources`).
|
||||
|
||||
## Failure modes
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ The variable is on the `environment:` whitelist in `docker-compose.yml`.
|
||||
- Time away from home and the distance travelled join the correlation scan as
|
||||
two more factors.
|
||||
- Runs of at least two travel days that no trip covers yet are offered as
|
||||
trips under **Settings → Environment → Location periods**. Using one fills
|
||||
trips under **Settings → Location & weather → Location periods**. Using one fills
|
||||
in the dates; you pick the place.
|
||||
- The Coach and the MCP `get_day` tool see the figures and whether it was a
|
||||
travel day, never the country.
|
||||
|
||||
@@ -103,7 +103,7 @@ The Fitbit client id/secret are per-user (Settings), not env vars.
|
||||
|
||||
If Fitbit and another source (Withings, WHOOP, Apple Health) both supply the same
|
||||
vital — resting heart rate, blood oxygen, body temperature or respiratory rate —
|
||||
the source priority ladder in **Settings → Sources** decides which one wins
|
||||
the source priority ladder in **Settings → Source priority** decides which one wins
|
||||
per metric. FITBIT is ranked in every default metric ladder; override
|
||||
per metric on that page.
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ triggers the verification + CASA requirements above.)
|
||||
|
||||
If Google Health and another source (Withings, WHOOP, Apple Health) both supply
|
||||
the same vital — resting heart rate, blood oxygen, respiratory rate — the source
|
||||
priority ladder in **Settings → Sources** decides which one wins
|
||||
priority ladder in **Settings → Source priority** decides which one wins
|
||||
per metric.
|
||||
|
||||
## Disconnect
|
||||
|
||||
@@ -34,7 +34,7 @@ default the device-native capture (Apple Watch, WHOOP, Withings) wins over the
|
||||
Strava copy, because a Strava activity is often a re-upload with lower-fidelity
|
||||
or missing heart-rate data. A run you record **only** on Strava (phone GPS)
|
||||
survives untouched. You can promote Strava above the wearables in
|
||||
**Settings → Sources** if you prefer the Strava copy.
|
||||
**Settings → Source priority** if you prefer the Strava copy.
|
||||
|
||||
## 1. Register a Strava API application
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ or right away after reconnecting Withings once.
|
||||
needed — `user.metrics` covers weight / BP / heart-list / sleep /
|
||||
SpO₂ / body comp / temperature / VO₂ max; `user.activity` is the
|
||||
workout-equivalent stream for steps / energy / distance / floors.
|
||||
See `WITHINGS_OAUTH_SCOPE` in `src/lib/withings/client.ts`.
|
||||
See `WITHINGS_OAUTH_SCOPE` in `src/lib/withings/oauth.ts`.
|
||||
4. Save the application. Withings will issue a **Client ID** and a
|
||||
**Client Secret** — keep the secret out of any chat log; it grants
|
||||
read access to every linked account's full health history.
|
||||
@@ -145,7 +145,7 @@ callback. The portal only owns the OAuth callback URL.
|
||||
## 4. Link from the Settings page
|
||||
|
||||
With the credentials saved in Settings, open
|
||||
`/settings/integrations/withings` and click **Connect Withings**. The flow:
|
||||
`/settings/integrations#withings` and click **Connect Withings**. The flow:
|
||||
|
||||
1. The Connect button redirects to Withings' `oauth2/authorize`.
|
||||
2. The user signs in on Withings and grants the requested scopes.
|
||||
@@ -181,7 +181,7 @@ import:
|
||||
when Apple Health is also linked, so the Apple Health stream wins
|
||||
for the cumulative axis. The Withings rows stay in the DB.
|
||||
|
||||
Override per-user via Settings → Sources (`/settings/sources`).
|
||||
Override per-user via Settings → Source priority (`/settings/sources`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -199,7 +199,7 @@ Override per-user via Settings → Sources (`/settings/sources`).
|
||||
### The webhook stops firing
|
||||
|
||||
Webhook subscriptions can lapse after a token revoke or a portal-
|
||||
side cleanup. Re-subscribe by opening `/settings/integrations/withings`
|
||||
side cleanup. Re-subscribe by opening `/settings/integrations#withings`
|
||||
and clicking **Disconnect** then **Connect Withings** again — the
|
||||
callback exchange re-runs `Notify.subscribe()` with a fresh callback
|
||||
URL.
|
||||
|
||||
+5
-2
@@ -270,10 +270,13 @@ always confirm the served build after a deploy:
|
||||
```sh
|
||||
# Asserts prod + demo serve 1.9.0, prints each target's buildSha,
|
||||
# exits non-zero on a mismatch or timeout after a bounded retry loop.
|
||||
pnpm dlx tsx scripts/assert-deploy.ts 1.9.0
|
||||
HEALTHLOG_PROD_URL=https://healthlog.example.com \
|
||||
pnpm dlx tsx scripts/assert-deploy.ts 1.9.0
|
||||
```
|
||||
|
||||
Pass `--only=prod` / `--only=demo` to check a single target, or
|
||||
`HEALTHLOG_PROD_URL` is the base URL of your instance; it has no default and
|
||||
is required unless you pass `--only=demo`. Pass `--only=prod` /
|
||||
`--only=demo` to check a single target, or
|
||||
`--attempts=N --interval=ms` to widen the retry window while a slow
|
||||
pull settles.
|
||||
|
||||
|
||||
@@ -96,8 +96,9 @@ maintenance refuses to start while one is.
|
||||
|
||||
Have at least twice the largest size free on the database volume.
|
||||
|
||||
- **Take a backup** (Settings, Data, Backups), as before any operation on the
|
||||
whole table.
|
||||
- **Take a backup**: a database dump, as in
|
||||
[Updates](../self-hosting/getting-started.md#updates), as before any
|
||||
operation on the whole table.
|
||||
|
||||
## Starting it
|
||||
|
||||
|
||||
@@ -214,11 +214,15 @@ as something to log in as.
|
||||
or delete the profile under a fresh second factor. Deleting the profile
|
||||
deletes everything recorded for it.
|
||||
- A guardian may keep that record's settings: modules, units, thresholds,
|
||||
its language and timezone, and its notification preferences. Connected
|
||||
services are visible as a status only. Connect, callback, sync,
|
||||
credential, disconnect, resume and test are not guardian surfaces in
|
||||
this release, and their deep links land on the shared-record unavailable
|
||||
page rather than on a raw refusal.
|
||||
its language and timezone, and its notification preferences. A guardian
|
||||
can also connect and disconnect the record's Withings, WHOOP, Oura, Polar,
|
||||
Strava and Google Health from its integrations page; the data lands in the
|
||||
profile's record, and a profile without its own provider app uses the
|
||||
guardian's. Fitbit (no new connections), Nightscout and Dawarich cannot be
|
||||
connected for a profile. Sync, the connection test and provider
|
||||
credentials are not guardian surfaces: once connected, the record syncs on
|
||||
its own schedule, and their deep links land on the shared-record
|
||||
unavailable page rather than on a raw refusal.
|
||||
- **Its reminders go to its guardians.** A record with no login has no
|
||||
channels of its own, so medication reminders, measurement reminders,
|
||||
safety-floor alerts and low-stock alerts fan out to every active
|
||||
|
||||
@@ -86,10 +86,11 @@ backfill button in settings fetches both for the range it is given.
|
||||
Since v1.42 the home, each dated location period and each stored day keep
|
||||
their coarse location sealed with the instance's encryption key (one value
|
||||
per row, `{ lat, lon, label }`, under the label
|
||||
`healthlog/environment-location/v1`). The boot-time encryption backfill seals
|
||||
rows written by older releases and clears their readable columns; the
|
||||
readable columns are dropped in a later release once the backfill reports
|
||||
zero rows everywhere. Key rotation (`scripts/rotate-encryption-key.ts`, or the
|
||||
`healthlog/environment-location/v1`). The boot-time encryption backfill sealed
|
||||
rows written by older releases and cleared their readable columns, and
|
||||
migration `0409` (v1.43) dropped those columns; it refuses to run while a row
|
||||
still holds a readable location
|
||||
([migration-0409-plaintext-location.md](../ops/migration-0409-plaintext-location.md)). Key rotation (`scripts/rotate-encryption-key.ts`, or the
|
||||
in-app rotation) covers all three.
|
||||
|
||||
What the person sees is unchanged: the settings page and
|
||||
|
||||
@@ -9,8 +9,7 @@ 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
|
||||
- linux/arm64), so a Raspberry Pi 5 or an x86 VPS both work
|
||||
unchanged.
|
||||
and linux/arm64), so a Raspberry Pi 5 or an x86 VPS both work unchanged.
|
||||
- **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
|
||||
@@ -23,11 +22,12 @@ already installed, longer if a TLS-fronted public hostname is in scope
|
||||
below the supported floor. On a busy instance, raise it.
|
||||
- **Outbound HTTPS** to `ghcr.io` (image pulls) plus whichever
|
||||
integration endpoints you plan to enable (Withings, OpenAI, etc.).
|
||||
The opt-in environmental-context module additionally reaches
|
||||
`archive-api.open-meteo.com` and `geocoding-api.open-meteo.com` for
|
||||
weather and daylight; it stays off until you
|
||||
enable it and set a home location, and every outbound call goes
|
||||
through the SSRF-guarded egress wrapper.
|
||||
The environmental-context module additionally reaches
|
||||
`archive-api.open-meteo.com`, `air-quality-api.open-meteo.com` and
|
||||
`geocoding-api.open-meteo.com` for weather, air quality and the city
|
||||
search. It is on by default, but nothing leaves the host until someone
|
||||
sets a home location, and every outbound call goes through the
|
||||
SSRF-guarded egress wrapper.
|
||||
Three requests happen by default without anything connected: the
|
||||
HaveIBeenPwned range query when a password is set (only the first five
|
||||
characters of the password's SHA-1 are sent), a daily check of the
|
||||
|
||||
@@ -19,7 +19,7 @@ on top of it, see [building skills](../api/mcp-skills.md).
|
||||
|
||||
> **Read this before enabling writes.** A remote, write-capable connector
|
||||
> reached over OAuth is a powerful combination. Read the
|
||||
> [trust model](#trust-model-read-this-before-enabling-writes) at the end
|
||||
> [trust model](#trust-model--read-this-before-enabling-writes) at the end
|
||||
> of this page first.
|
||||
|
||||
## Prerequisites
|
||||
@@ -43,7 +43,7 @@ LAN-only origin works for Claude Desktop over stdio).
|
||||
The switch lives with every other module: **Settings → Modules**, the
|
||||
**MCP** row. Turn it on and the `/mcp` endpoint goes live for your account.
|
||||
|
||||
**Settings → MCP** is where you use it. The page has three parts:
|
||||
**Settings → MCP connector** is where you use it. The page has three parts:
|
||||
|
||||
- **State** — whether the module is on, with a link to its switch in
|
||||
Modules, and your endpoint URL (`<origin>/mcp`). Turning the module off
|
||||
@@ -55,7 +55,7 @@ The switch lives with every other module: **Settings → Modules**, the
|
||||
|
||||
## 2. Mint a connector token
|
||||
|
||||
Still in **Settings → MCP → Tokens**:
|
||||
Still in **Settings → MCP connector → Tokens**:
|
||||
|
||||
1. Choose the scope with the **write** toggle:
|
||||
- **off** → a read-only token (`health:read`). This is the default and
|
||||
@@ -106,7 +106,7 @@ refused (`403`) as a DNS-rebinding defence.
|
||||
client-id-metadata-document clients with dynamic registration as a
|
||||
fallback.
|
||||
3. Approve the consent screen. The connection then appears under
|
||||
**Settings → MCP → Connections**, where you can revoke it.
|
||||
**Settings → MCP connector → Connections**, where you can revoke it.
|
||||
|
||||
No token to paste — OAuth mints and rotates a short-lived `health:read`
|
||||
access token (60 minutes, with a refresh token) behind the scenes.
|
||||
|
||||
@@ -95,7 +95,7 @@ different things depending on which button you press.
|
||||
two actions below, the row stays. A start that was folded into a later
|
||||
cycle stays for as long as that cycle does, because deleting the later
|
||||
cycle gives it back.
|
||||
- **Settings → Delete All Data** removes the rows outright, tombstones
|
||||
- **Settings → Advanced → Delete all data** removes the rows outright, tombstones
|
||||
included. Everything the account owns goes except what you sign in
|
||||
with; the confirmation dialog lists it.
|
||||
- **Deleting the account** removes the account row, and the database
|
||||
|
||||
@@ -49,9 +49,10 @@ HealthLog behind a reverse proxy with its own domain, change App URL to that
|
||||
|
||||
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)).
|
||||
[`.env.production.example`](../../.env.production.example) and on the
|
||||
[environment variables reference](https://docs.healthlog.dev/reference/environment-variables/).
|
||||
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
|
||||
|
||||
@@ -206,15 +207,15 @@ before you touch it.
|
||||
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 18 (16 works too), no extensions, in its own container with its own dataset |
|
||||
| Volumes | none for the app; the database gets its own dataset |
|
||||
| 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 | 10 min, like the image: migrations run at every boot and wait out a held lock |
|
||||
| Database | PostgreSQL 18 (16 works too), no extensions, in its own container with its own dataset |
|
||||
| Volumes | none for the app; the database gets its own dataset |
|
||||
|
||||
| Variable | Required | Notes |
|
||||
| ----------------------- | -------- | ---------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -116,8 +116,8 @@ PostgreSQL container you installed in Step 1. Back that one up.
|
||||
|
||||
`SESSION_COOKIE_SECURE=false` is required for plain-HTTP LAN installs
|
||||
(`http://tower:3000`). Left at its default, the session cookie carries the
|
||||
`Secure` flag, the browser refuses to send it over HTTP, and login appears to
|
||||
"do nothing" — the page reloads to the login screen with no error.
|
||||
`Secure` flag, which the browser would drop over HTTP. The login page says so
|
||||
before anything is sent: "Signing in cannot complete on this page".
|
||||
|
||||
Set `SESSION_COOKIE_SECURE=true` (or leave it unset) only when a TLS reverse
|
||||
proxy serves HealthLog over HTTPS. In that case also point `NEXT_PUBLIC_APP_URL`
|
||||
|
||||
@@ -311,7 +311,7 @@ Tiles at the top of the dashboard live in a **single row**. Width caps
|
||||
at the chart's width directly below. **Never** wrap to a second row.
|
||||
|
||||
If the user enables more tiles than fit, the dashboard surfaces a banner
|
||||
inviting them to deactivate or reorder via Settings → Dashboard. The
|
||||
inviting them to deactivate or reorder via Settings → Arrangement. The
|
||||
layout editor in Settings shows a live preview of the constraint and
|
||||
flags overflow before save.
|
||||
|
||||
|
||||
Reference in new issue
Block a user