Merge the release with the security review fixes into the data-safety review fixes

This commit is contained in:
Marc-André Bombeck committed 2026-10-25 18:36:54 +01:00
commit bcb31c80db
98 files changed
+3166 -608

No files matched your search

+51 -3
View File
@@ -11,7 +11,11 @@
# empty the new database and run the move again.
# 5. Empty the new volume: it must refuse to import the old state again,
# and move it once more after the marker is removed as the guide says.
# 6. A new install with no old volume: nothing to do.
# 6. Going back to 16 after a move that did not finish: db-upgrade leaves
# the live 16 database alone.
# 7. Using 16 again after a move, then updating: refused, not the old copy.
# 8. Updating again as the guide says: the current 16 database moves.
# 9. A new install with no old volume: nothing to do.
#
# Needs Docker and the repo's node_modules (for the migrations). Run it from
# anywhere in the repo: `bash scripts/compose-db-upgrade-check.sh`.
@@ -85,7 +89,7 @@ wait_for_port() {
migrate_old() {
local started=$SECONDS
until DATABASE_URL="postgresql://healthlog:$POSTGRES_PASSWORD@127.0.0.1:$PORT/healthlog?schema=public" \
node "$ROOT/node_modules/prisma/build/index.js" migrate deploy --schema "$ROOT/prisma/schema.prisma" >"$WORK/migrate.log" 2>&1; do
node "$ROOT/node_modules/prisma/build/index.js" migrate deploy --schema "$WORK/old-prisma/prisma/schema.prisma" >"$WORK/migrate.log" 2>&1; do
if [ $((SECONDS - started)) -ge 30 ] || ! grep -q "P1001" "$WORK/migrate.log"; then
cat "$WORK/migrate.log" >&2
return 1
@@ -94,6 +98,18 @@ migrate_old() {
done
}
# The v1.45 schema, migrated with v1.45's own migrations.
mkdir -p "$WORK/old-prisma"
git -C "$ROOT" archive "$OLD_REF" prisma | tar -x -C "$WORK/old-prisma"
# The current compose file with its database pointed back at Postgres 16, the
# way docs/ops/postgres-major-upgrade.md "Going back" describes it.
sed -e 's|image: postgres:18\.[0-9]*-alpine|image: postgres:16.15-alpine|' \
-e 's|- postgres-data:/var/lib/postgresql$|- pgdata:/var/lib/postgresql/data|' \
"$ROOT/docker-compose.yml" >"$WORK/back.yml"
grep -q 'image: postgres:16.15-alpine' "$WORK/back.yml" && grep -q -- '- pgdata:/var/lib/postgresql/data' "$WORK/back.yml" ||
{ echo "FAIL: could not derive the going-back compose file" >&2; exit 1; }
back() { docker compose -p "$PROJECT" -f "$WORK/back.yml" "$@"; }
down_all
step "a v1.45 database on Postgres 16"
old up -d --wait db >"$WORK/up.log" 2>&1 || { cat "$WORK/up.log" >&2; fail "the v1.45 database did not start"; }
@@ -157,10 +173,42 @@ CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE after the marker was removed"
[ "$(probe docker exec healthlog-db)" = "$PROBE" ] || fail "the probe rows differ after starting over"
step "going back to 16 after a move that did not finish leaves 16 alone"
new down >/dev/null 2>&1
docker volume rm "${PROJECT}_postgres-data" >/dev/null
docker run --rm -v "${PROJECT}_pgdata:/old" postgres:16.15-alpine \
mv /old/HEALTHLOG_MOVED_TO_POSTGRES_18 /old/HEALTHLOG_MOVING_TO_POSTGRES_18
back up -d db-upgrade >/dev/null 2>&1 || true
CODE=$(docker wait healthlog-db-upgrade)
upgrade_log
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE beside a Postgres 16 database service"
upgrade_log | grep -q 'not 18: nothing to move' || fail "db-upgrade did not recognise the Postgres 16 service"
[ "$(probe docker exec healthlog-db)" = "$PROBE" ] || fail "the live Postgres 16 data changed beside db-upgrade"
step "using 16 again after a move, then updating, is refused instead of serving the old copy"
back down >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "the move after going back exited $CODE"
back up -d db >/dev/null 2>&1
back exec -T db psql -U healthlog -d healthlog -v ON_ERROR_STOP=1 -qc \
"INSERT INTO check_probe.rows VALUES (50001, 'written on 16 after the move')"
back down >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 1 ] || fail "db-upgrade exited $CODE after 16 was used again, expected 1"
upgrade_log | grep -q 'changed after it was moved' || fail "the refusal did not say that 16 changed"
step "updating again as the guide says moves the current 16 database"
new down >/dev/null 2>&1
docker volume rm "${PROJECT}_postgres-data" >/dev/null
new run --rm --entrypoint rm db-upgrade /var/lib/postgresql/data/HEALTHLOG_MOVED_TO_POSTGRES_18 >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE when moving the current 16 database"
[ "$(sql "SELECT count(*) FROM check_probe.rows")" = 50001 ] || fail "the row written on 16 did not arrive"
step "a new install: nothing to do"
new down -v >/dev/null 2>&1
CODE=$(run_upgrade)
[ "$CODE" = 0 ] || fail "db-upgrade exited $CODE on a new install"
upgrade_log | tail -1 | grep -q 'no Postgres 16 database to move' || fail "the new install did not say there was nothing to move"
echo "OK: the Postgres 16 -> 18 move behaves in all six cases"
echo "OK: the Postgres 16 -> 18 move behaves in all nine cases"
+11 -1
View File
@@ -268,7 +268,12 @@
},
{
"name": "DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP",
"purpose": "\"true\" (or \"1\") accepts only signed deploy-webhook requests (X-Deploy-Webhook-Signature with a fresh X-Deploy-Webhook-Timestamp); the secret header alone is refused with 401. Leave unset for Coolify's stock sender, which can only send the secret header.",
"purpose": "\"true\" (or \"1\") requires a fresh X-Deploy-Webhook-Timestamp (within 5 minutes) on a deploy-webhook request that authenticates with the secret header; one without it is refused with 401. Keeps a stale capture out, not a fresh forgery by someone who saw the secret. Leave unset for Coolify's stock sender, which sends no timestamp.",
"example": "true"
},
{
"name": "DEPLOY_WEBHOOK_REQUIRE_SIGNATURE",
"purpose": "\"true\" (or \"1\") accepts only signed deploy-webhook requests (X-Deploy-Webhook-Signature with a fresh X-Deploy-Webhook-Timestamp); the secret header alone is refused with 401. The setting that stops a replay. Leave unset for Coolify's stock sender, which can only send the secret header.",
"example": "true"
}
]
@@ -432,6 +437,11 @@
"purpose": "Trusted reverse-proxy hop count; the client IP is read that many entries from the right of X-Forwarded-For. Forwarding headers are read only from a peer whose own address is not public (loopback, private, CGNAT, unique-local, link-local, reserved, multicast).",
"example": "1"
},
{
"name": "TRUSTED_PROXY_CIDRS",
"purpose": "Comma-separated CIDRs (IPv4 or IPv6; a bare address is one host) the reverse proxy connects from. Set, only a connection from inside them may report the client address in X-Forwarded-For or X-Real-IP; every other connection is its own client. Unset, any non-public address may, which behind Docker Desktop, OrbStack or docker-proxy includes every caller of a published port. Same host: 127.0.0.1/32,::1/128; Docker network: the proxy's subnet. An entry that is not a CIDR lets no connection report an address and is named at boot.",
"example": "127.0.0.1/32,::1/128"
},
{
"name": "TRUST_CF_CONNECTING_IP",
"purpose": "\"1\" takes the client IP from Cloudflare's CF-Connecting-IP header, from every connection and before the peer check. Safe only when the origin accepts connections from Cloudflare alone (a Tunnel, or a firewall admitting only Cloudflare's ranges); otherwise any caller forges the address rate limits and the audit log record. Off by default.",