mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
Merge remote-tracking branch 'origin/release/v1.46.0' into fix/v146-sec-review
This commit is contained in:
commit
722c6d1db5
15 files changed
+367
-245
No files matched your search
@@ -17,7 +17,9 @@ Postgres 18 database is up and before the app, and it exits in a second when
|
||||
there is nothing to move. When the old `pgdata` volume holds a Postgres 16
|
||||
database and the new one is empty, it:
|
||||
|
||||
1. checks there is room on the disk for a second copy;
|
||||
1. checks that the database service runs Postgres 18, and that there is room
|
||||
on the disk for a second copy and for the dump the app takes before its
|
||||
first migration;
|
||||
2. starts Postgres 16 inside its own container on the old volume, reachable
|
||||
only from inside that container;
|
||||
3. streams a dump into Postgres 18 in a single transaction;
|
||||
@@ -34,8 +36,10 @@ If any step fails, the new database stays empty, the app does not start, and
|
||||
the log says what happened. A move cut short by a restart or a full disk is
|
||||
run again from scratch on the next `docker compose up -d`.
|
||||
|
||||
The old `pgdata` volume is read and never changed, apart from the marker
|
||||
file. That is the way back.
|
||||
The old `pgdata` volume keeps the database as it was. Postgres 16 runs on it
|
||||
during the move, so it writes a checkpoint there (and recovers first after an
|
||||
unclean stop), and the marker file is added; no data in it changes. That is
|
||||
the way back.
|
||||
|
||||
## Going back
|
||||
|
||||
@@ -55,6 +59,29 @@ HEALTHLOG_IMAGE_REF=:v1.45.0 docker compose up -d
|
||||
```
|
||||
|
||||
Anything written after the move lives only in the Postgres 18 volume.
|
||||
`db-upgrade` can stay in the file: it sees that the database service runs
|
||||
Postgres 16 and leaves that volume alone. This also holds after a move that
|
||||
stopped half-way.
|
||||
|
||||
## Updating again after going back
|
||||
|
||||
If you used Postgres 16 again after going back, the Postgres 18 volume holds
|
||||
the state of the day of the move, not what you wrote since. `db-upgrade`
|
||||
notices that the old database changed after the move and refuses to start on
|
||||
the old copy. To move the current 16 database, remove the old copy and the
|
||||
marker, then start again:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker volume rm <project>_postgres-data
|
||||
docker compose run --rm --entrypoint rm db-upgrade \
|
||||
/var/lib/postgresql/data/HEALTHLOG_MOVED_TO_POSTGRES_18
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
`docker volume ls` shows the volume's full name. Removing it deletes what was
|
||||
written on Postgres 18 before you went back; take a dump of it first if any
|
||||
of that matters.
|
||||
|
||||
## Afterwards
|
||||
|
||||
|
||||
@@ -434,7 +434,9 @@ Keep the dump until the new version has run for a few days, and keep your
|
||||
encryption key with it: a dump without the key cannot be read.
|
||||
|
||||
The database image is a separate matter: a new Postgres minor is a change of
|
||||
the `image:` line, a new major (16 to 18) is a dump and restore, described in
|
||||
the `image:` line. The move from 16 to 18 runs by itself on the first start
|
||||
of the compose file from v1.46; a database you run yourself moves by dump
|
||||
and restore. Both are in
|
||||
[`docs/ops/postgres-major-upgrade.md`](../ops/postgres-major-upgrade.md).
|
||||
|
||||
The start log says what is about to happen before the migrations run:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": 3,
|
||||
"title": "HealthLog",
|
||||
"name": "healthlog",
|
||||
"description": "Self-hosted personal health tracker (PWA + native iOS client): weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications on one encrypted timeline. AES-256-GCM at rest, no vendor cloud, no telemetry. Ships app + PostgreSQL 16 as one stack.",
|
||||
"description": "Self-hosted personal health tracker (PWA + native iOS client): weight, blood pressure, pulse, blood glucose, body composition, sleep, mood, menstrual cycle, and medications on one encrypted timeline. AES-256-GCM at rest, no vendor cloud, no telemetry. Ships app + PostgreSQL 18 as one stack.",
|
||||
"note": "Generate each secret with: openssl rand -hex 32 (a 64-char hex string). For plain-HTTP / LAN access set SESSION_COOKIE_SECURE to false, or login fails. Set NEXT_PUBLIC_APP_URL and APP_URL to how you reach the app. Full guide: https://github.com/MBombeck/HealthLog/blob/main/docs/self-hosting/getting-started.md",
|
||||
"categories": ["Tools", "Other"],
|
||||
"platform": "linux",
|
||||
@@ -33,7 +33,7 @@
|
||||
{
|
||||
"name": "HEALTHLOG_PLATFORM",
|
||||
"label": "Platform hint",
|
||||
"description": "Opens the admin's encryption key backup step on the Portainer instructions.",
|
||||
"description": "Opens the admin's recovery kit on the Portainer instructions.",
|
||||
"default": "portainer"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -95,19 +95,31 @@ 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 → Advanced → Delete all data** removes the rows outright, tombstones
|
||||
- **Settings → Advanced → Delete all data** removes the rows, 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
|
||||
cascades every record with it.
|
||||
with; the confirmation dialog lists it. Before it does, it keeps a copy
|
||||
of the account on the server for the deletion grace window (14 days by
|
||||
default), so a mistaken wipe can be restored; the copy is erased when
|
||||
the window ends.
|
||||
- **Deleting the account** locks it at once and erases it, with every
|
||||
record, when the grace window ends. Until then an admin can undo it.
|
||||
|
||||
Two consequences follow, and they matter for the seizure threat model
|
||||
`ACCOUNT_DELETION_GRACE_DAYS=0` makes both immediate again, without the
|
||||
copy and without an undo.
|
||||
|
||||
Three consequences follow, and they matter for the seizure threat model
|
||||
above. A host imaged while a per-entry deletion is the only deletion you
|
||||
have performed still holds that entry. And a backup taken before any of
|
||||
these actions still holds whatever it captured, because a backup is a
|
||||
copy of a moment. If your reason for deleting is that the data must not
|
||||
be recoverable from the machine, use one of the two whole-record actions
|
||||
and rotate your backups.
|
||||
have performed still holds that entry; so does a host imaged inside the
|
||||
grace window. A backup taken before any of these actions still holds
|
||||
whatever it captured, because a backup is a copy of a moment: the nightly
|
||||
database dumps keep a deleted record until they age out (`DB_BACKUP_KEEP`
|
||||
nights on the server, and your bucket's lifecycle rule for `database/`).
|
||||
And after the move to Postgres 18 the old `pgdata` volume is a full copy
|
||||
of the database from that day until you remove it. If your reason for
|
||||
deleting is that the data must not be recoverable from the machine, set
|
||||
the grace window to 0 or wait it out, remove the old volume, and let the
|
||||
dumps age out or delete them
|
||||
([backup-restore.md](../ops/backup-restore.md)).
|
||||
|
||||
## Practical hardening
|
||||
|
||||
|
||||
@@ -46,14 +46,14 @@
|
||||
|
||||
<Config Name="WebUI Port" Target="3000" Default="3000" Mode="tcp" Description="HTTP port HealthLog listens on." Type="Port" Display="always" Required="true" Mask="false">3000</Config>
|
||||
|
||||
<Config Name="DATABASE_URL" Target="DATABASE_URL" Default="postgresql://healthlog:CHANGE-ME@POSTGRES-HOST:5432/healthlog?schema=public" Mode="" Description="Connection string for your separately-installed PostgreSQL 16. Replace CHANGE-ME with the DB password and POSTGRES-HOST with the Postgres container IP or name." Type="Variable" Display="always" Required="true" Mask="true"/>
|
||||
<Config Name="DATABASE_URL" Target="DATABASE_URL" Default="postgresql://healthlog:CHANGE-ME@POSTGRES-HOST:5432/healthlog?schema=public" Mode="" Description="Connection string for your separately-installed PostgreSQL (18, or 16). Replace CHANGE-ME with the DB password and POSTGRES-HOST with the Postgres container IP or name." Type="Variable" Display="always" Required="true" Mask="true"/>
|
||||
|
||||
<Config Name="ENCRYPTION_KEY" Target="ENCRYPTION_KEY" Default="" Mode="" Description="AES-256-GCM key for data at rest. Generate with: openssl rand -hex 32 (64-char hex). Back this up — losing it makes encrypted data unrecoverable." Type="Variable" Display="always" Required="true" Mask="true"/>
|
||||
|
||||
<Config Name="API_TOKEN_HMAC_KEY" Target="API_TOKEN_HMAC_KEY" Default="" Mode="" Description="HMAC key for API token hashing. Generate with: openssl rand -hex 32 (64-char hex)." Type="Variable" Display="always" Required="true" Mask="true"/>
|
||||
|
||||
<Config Name="SESSION_COOKIE_SECURE" Target="SESSION_COOKIE_SECURE" Default="false" Mode="" Description="Set to false for LAN / plain-HTTP installs (http://tower:3000) or login fails. Set to true only behind an HTTPS reverse proxy." Type="Variable" Display="always" Required="true" Mask="false">false</Config>
|
||||
<Config Name="HEALTHLOG_PLATFORM" Target="HEALTHLOG_PLATFORM" Default="unraid" Mode="" Description="Where this server is configured. Opens the admin's encryption key backup step on the Unraid instructions." Type="Variable" Display="advanced" Required="false" Mask="false">unraid</Config>
|
||||
<Config Name="HEALTHLOG_PLATFORM" Target="HEALTHLOG_PLATFORM" Default="unraid" Mode="" Description="Where this server is configured. Opens the admin's recovery kit on the Unraid instructions." Type="Variable" Display="advanced" Required="false" Mask="false">unraid</Config>
|
||||
<Config Name="ENCRYPTION_KEY_CHECK" Target="ENCRYPTION_KEY_CHECK" Default="enforce" Mode="" Description="Start-up encryption key check. enforce refuses to serve when the key does not open the stored data. warn only logs it: a last resort for a refusal you are certain is false, and it disables a safety check." Type="Variable" Display="advanced" Required="false" Mask="false">enforce</Config>
|
||||
|
||||
<Config Name="NEXT_PUBLIC_APP_URL" Target="NEXT_PUBLIC_APP_URL" Default="http://tower:3000" Mode="" Description="Public URL users reach HealthLog at. Use the LAN URL (e.g. http://tower:3000) or your HTTPS domain behind a reverse proxy." Type="Variable" Display="always" Required="true" Mask="false"/>
|
||||
|
||||
Reference in new issue
Block a user