Refuse to publish a site built from a hollow index.
Health answering 200 and the index being complete are different claims. A year of ~31-event backfills left a perfectly healthy API serving a real, correct, complete list of eight mints. A build against that succeeds — it prerenders eight cards — and rsync --delete-after then replaces fifty-five with eight. cashumints-web.service gains a second ExecStartPre after the health wait: count /api/mints, and exit non-zero below MIN_MINTS_FOR_BUILD (default 20, overridable with `systemctl edit`). A refusal aborts the unit before `pnpm build`, and publishing is ExecStartPost, so the previously published site is untouched; the OnFailure alert added in the last commit says why. Counted by the "host": key rather than by counting braces, because the list payload is about to carry a nested object per mint. A curl that fails at all counts as zero, which is below every floor — so an API that fell over between the health check and this line refuses the build instead of sailing through it. Verified against three live APIs: 73 mints passes, a doctored 8-mint database fails with the reason, and a dead port fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0ebc8ada54
commit
060c7f1a59
@@ -1368,6 +1368,23 @@ Publishing is a separate step from building, and the separation is the point: th
|
||||
the site server reads is only touched once a build has succeeded, so a failed build
|
||||
leaves the previous site up rather than replacing it with a half-written one.
|
||||
|
||||
**Two gates before the build starts.** The first waits for `/api/health` to answer 200,
|
||||
because `After=` orders a start and does not wait for a port. The second counts
|
||||
`/api/mints` and refuses to build below `MIN_MINTS_FOR_BUILD`, default 20.
|
||||
|
||||
The second exists because health answering 200 and the index being complete are
|
||||
different claims. A year of ~31-event backfills left a perfectly healthy API serving a
|
||||
real, correct, complete list of eight mints; a build against that succeeds, prerenders
|
||||
eight cards, and `rsync --delete-after` replaces fifty-five with eight. The gate runs as
|
||||
an `ExecStartPre`, so a refusal aborts the unit before `pnpm build` — and publishing is
|
||||
`ExecStartPost`, after the build — which means the previously published site is never
|
||||
touched. The `OnFailure=` alert says why.
|
||||
|
||||
```bash
|
||||
# Raise or lower it for this host without editing the unit:
|
||||
sudo systemctl edit cashumints-web # [Service] / Environment=MIN_MINTS_FOR_BUILD=40
|
||||
```
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/cashumints-web.service
|
||||
[Unit]
|
||||
@@ -1401,6 +1418,40 @@ Environment=PUBLIC_API_URL=
|
||||
# rather than letting the first fetch die on ECONNREFUSED. /api/health answers 503 until
|
||||
# it is genuinely ready, and curl -f treats that as a failure, so the loop keeps waiting.
|
||||
ExecStartPre=/usr/bin/timeout 90 /bin/sh -c 'until curl -sf -o /dev/null http://127.0.0.1:8788/api/health; do sleep 1; done'
|
||||
|
||||
# Then: does the API actually have an index to build a site out of?
|
||||
#
|
||||
# Health answering 200 says the process is up and its last backfill read something. It
|
||||
# does not say how many mints are in the table, and those are different questions — the
|
||||
# year of ~31-event backfills had a healthy API serving a real, complete, correct list of
|
||||
# eight mints. A build against that succeeds, prerenders eight cards, and rsync happily
|
||||
# replaces fifty-five with eight.
|
||||
#
|
||||
# So count the list before spending twenty minutes building from it. Below the floor
|
||||
# this exits non-zero, systemd abandons the unit at ExecStartPre, and — because publishing
|
||||
# is ExecStartPost, after the build — the previously published site is never touched. The
|
||||
# site stays exactly as it was and the OnFailure alert says why.
|
||||
#
|
||||
# Counted by the `"host":` key, one per item, rather than by counting `{`: the list
|
||||
# payload carries a nested object per mint (its NUT capability switches), so brace
|
||||
# counting would report roughly double. No jq: it is not installed on this host and a
|
||||
# build gate should not add a dependency to run.
|
||||
#
|
||||
# `Q` is a double-quote character, built with printf rather than written literally,
|
||||
# because this whole command is already inside systemd's single quotes and a quote of
|
||||
# either kind in the grep pattern would end the argument early.
|
||||
#
|
||||
# A curl that fails for any reason leaves `n` empty, `$${n:-0}` reads that as zero, and
|
||||
# zero is below every floor — so an API that fell over between the health check above and
|
||||
# this line refuses the build rather than sailing through it.
|
||||
Environment=MIN_MINTS_FOR_BUILD=20
|
||||
ExecStartPre=/bin/sh -c 'Q=$$(printf "\\042"); \
|
||||
n=$$(curl -sf --max-time 30 http://127.0.0.1:8788/api/mints | grep -o "$${Q}host$${Q}:" | wc -l); \
|
||||
if [ "$${n:-0}" -lt "$$MIN_MINTS_FOR_BUILD" ]; then \
|
||||
printf "<3>%s\\n" "refusing to build: /api/mints returned $${n:-0} mints, floor is $$MIN_MINTS_FOR_BUILD. Previous site left untouched."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
printf "%s\\n" "build gate: $$n mints, floor $$MIN_MINTS_FOR_BUILD"'
|
||||
# Check `which pnpm` on the host: a corepack or pnpm-home install sits outside /usr/bin,
|
||||
# and systemd's PATH does not include it.
|
||||
ExecStart=/usr/bin/pnpm build
|
||||
|
||||
@@ -52,6 +52,40 @@ Environment=PUBLIC_API_URL=
|
||||
# rather than letting the first fetch die on ECONNREFUSED. /api/health answers 503 until
|
||||
# it is genuinely ready, and curl -f treats that as a failure, so the loop keeps waiting.
|
||||
ExecStartPre=/usr/bin/timeout 90 /bin/sh -c 'until curl -sf -o /dev/null http://127.0.0.1:8788/api/health; do sleep 1; done'
|
||||
|
||||
# Then: does the API actually have an index to build a site out of?
|
||||
#
|
||||
# Health answering 200 says the process is up and its last backfill read something. It
|
||||
# does not say how many mints are in the table, and those are different questions — the
|
||||
# year of ~31-event backfills had a healthy API serving a real, complete, correct list of
|
||||
# eight mints. A build against that succeeds, prerenders eight cards, and rsync happily
|
||||
# replaces fifty-five with eight.
|
||||
#
|
||||
# So count the list before spending twenty minutes building from it. Below the floor
|
||||
# this exits non-zero, systemd abandons the unit at ExecStartPre, and — because publishing
|
||||
# is ExecStartPost, after the build — the previously published site is never touched. The
|
||||
# site stays exactly as it was and the OnFailure alert says why.
|
||||
#
|
||||
# Counted by the `"host":` key, one per item, rather than by counting `{`: the list
|
||||
# payload carries a nested object per mint (its NUT capability switches), so brace
|
||||
# counting would report roughly double. No jq: it is not installed on this host and a
|
||||
# build gate should not add a dependency to run.
|
||||
#
|
||||
# `Q` is a double-quote character, built with printf rather than written literally,
|
||||
# because this whole command is already inside systemd's single quotes and a quote of
|
||||
# either kind in the grep pattern would end the argument early.
|
||||
#
|
||||
# A curl that fails for any reason leaves `n` empty, `$${n:-0}` reads that as zero, and
|
||||
# zero is below every floor — so an API that fell over between the health check above and
|
||||
# this line refuses the build rather than sailing through it.
|
||||
Environment=MIN_MINTS_FOR_BUILD=20
|
||||
ExecStartPre=/bin/sh -c 'Q=$$(printf "\\042"); \
|
||||
n=$$(curl -sf --max-time 30 http://127.0.0.1:8788/api/mints | grep -o "$${Q}host$${Q}:" | wc -l); \
|
||||
if [ "$${n:-0}" -lt "$$MIN_MINTS_FOR_BUILD" ]; then \
|
||||
printf "<3>%s\\n" "refusing to build: /api/mints returned $${n:-0} mints, floor is $$MIN_MINTS_FOR_BUILD. Previous site left untouched."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
printf "%s\\n" "build gate: $$n mints, floor $$MIN_MINTS_FOR_BUILD"'
|
||||
# Check `which pnpm` on the host: a corepack or pnpm-home install sits outside /usr/bin,
|
||||
# and systemd's PATH does not include it.
|
||||
ExecStart=/usr/bin/pnpm build
|
||||
|
||||
Reference in New Issue
Block a user