docs(deploy): name the retention cron the first mirror fires, and probe the root (FRE-1707) #10
@@ -106,6 +106,28 @@ exactly as it is today.
|
|||||||
to GitHub `master`; land it in Gitea and let it mirror.
|
to GitHub `master`; land it in Gitea and let it mirror.
|
||||||
- **A merge that is only a Gitea merge** is not live until the mirror runs. Check
|
- **A merge that is only a Gitea merge** is not live until the mirror runs. Check
|
||||||
`scripts/verify-deploy.sh` before calling a site change published.
|
`scripts/verify-deploy.sh` before calling a site change published.
|
||||||
|
- **The first mirrored deploy switches on a cron that deletes production
|
||||||
|
rows.** `vercel.json` carries two crons. `/api/the-nook/lifecycle`
|
||||||
|
(13:00 UTC) is already on the tree Vercel builds today, so it runs.
|
||||||
|
`/api/analytics-retention` (12:00 UTC, added in `1d9287a`, FRE-369) is
|
||||||
|
only on Gitea master: the first deploy that follows the mirror starts
|
||||||
|
calling it, and the next 12:00 UTC runs
|
||||||
|
`DELETE FROM VisitorAnalytics WHERE created_at < datetime('now','-365 days')`
|
||||||
|
against the production database. Not staged, not reversible — whatever is
|
||||||
|
older than the window
|
||||||
|
is gone the first time it fires. The window is
|
||||||
|
`ANALYTICS_RETENTION_DAYS`, and unset, non-positive, or over 730 each
|
||||||
|
resolve to the 365-day default (`src/server/analytics-retention.ts`).
|
||||||
|
365 is the conservative end — picked so that switching the schedule on is
|
||||||
|
not itself a data-loss event — but conservative still means *over a year
|
||||||
|
old gets deleted*. Set the window in Vercel **before** the first mirrored
|
||||||
|
deploy if 365 is not what you want. The cron call is authorized with
|
||||||
|
`Authorization: Bearer <ANALYTICS_RETENTION_SECRET ?? CRON_SECRET>`
|
||||||
|
(`src/routes/api/analytics-retention.ts`), and the endpoint fails closed:
|
||||||
|
with no secret configured every call is a 401, so a missing secret is a
|
||||||
|
table that quietly stops being pruned, never an unauthenticated bulk
|
||||||
|
delete. Vercel supplies `CRON_SECRET` to its own crons and production
|
||||||
|
already runs the 13:00 job, so assume the secret is there and it runs.
|
||||||
- **The FRE-371 fix's actual exploitability in production** depends on how the
|
- **The FRE-371 fix's actual exploitability in production** depends on how the
|
||||||
edge sets `x-forwarded-for`/`cf-connecting-ip`. That is measured and gated by
|
edge sets `x-forwarded-for`/`cf-connecting-ip`. That is measured and gated by
|
||||||
the Security Engineer, not assumed here.
|
the Security Engineer, not assumed here.
|
||||||
@@ -114,6 +136,15 @@ exactly as it is today.
|
|||||||
|
|
||||||
scripts/verify-deploy.sh
|
scripts/verify-deploy.sh
|
||||||
|
|
||||||
It is read-only and needs no credentials. It checks both remotes' master SHA,
|
It is read-only and needs no credentials — that is what makes it safe to run
|
||||||
the live Nook/Nessa copy, and (when the commit is present locally) that
|
from an agent run as well as by the operator, and it is why the checks stay
|
||||||
`2fd2a4a` is an ancestor of the deployed commit.
|
read-only. It checks both remotes' master SHA, that `https://freno.me/`
|
||||||
|
answers a direct 200, the live Nook/Nessa copy, and (when the commit is
|
||||||
|
present locally) that `2fd2a4a` is an ancestor of the deployed commit.
|
||||||
|
|
||||||
|
The root probe is there because every other check can stay green while the
|
||||||
|
root deploy is broken. It is status-only, and it deliberately does not probe
|
||||||
|
`https://freno.me/terms`: `SITEMAP_ROUTES.main` in
|
||||||
|
`src/lib/sitemap-routes.ts` has no `/terms` — Terms of Use is a Nessa
|
||||||
|
surface (`nessa.freno.me/terms`), so a 404 on the root domain's `/terms` is
|
||||||
|
the design, not a defect.
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
#
|
#
|
||||||
# Proves, in one run:
|
# Proves, in one run:
|
||||||
# 1. Gitea master and GitHub master are the same commit (the mirror converged).
|
# 1. Gitea master and GitHub master are the same commit (the mirror converged).
|
||||||
# 2. The live site serves copy that only exists on the reconciled line:
|
# 2. The root domain answers on its own terms: https://freno.me/ returns a direct 200,
|
||||||
|
# so a broken root deploy cannot read as green while the subdomains look fine.
|
||||||
|
# 3. The live site serves copy that only exists on the reconciled line:
|
||||||
# NOOK-1038's scoped remote claim (GitHub-only before the merge) *and*
|
# NOOK-1038's scoped remote claim (GitHub-only before the merge) *and*
|
||||||
# FRE-997's "Supported agents" (Gitea-only before the merge). Both at once
|
# FRE-997's "Supported agents" (Gitea-only before the merge). Both at once
|
||||||
# can only come from the merge commit.
|
# can only come from the merge commit.
|
||||||
# 3. The served tree therefore contains FRE-371 (`2fd2a4a`), which is an
|
# 4. The served tree therefore contains FRE-371 (`2fd2a4a`), which is an
|
||||||
# ancestor of that merge — checked locally when the commit is in the object
|
# ancestor of that merge — checked locally when the commit is in the object
|
||||||
# store, so run this from a freno-dev checkout.
|
# store, so run this from a freno-dev checkout.
|
||||||
#
|
#
|
||||||
@@ -17,6 +19,7 @@ set -uo pipefail
|
|||||||
|
|
||||||
GITEA_REMOTE="${GITEA_REMOTE:-https://git.freno.me/Mike/freno-dev.git}"
|
GITEA_REMOTE="${GITEA_REMOTE:-https://git.freno.me/Mike/freno-dev.git}"
|
||||||
GITHUB_REMOTE="${GITHUB_REMOTE:-https://github.com/mikefreno/freno-dev.git}"
|
GITHUB_REMOTE="${GITHUB_REMOTE:-https://github.com/mikefreno/freno-dev.git}"
|
||||||
|
ROOT_HOST="${ROOT_HOST:-https://freno.me}"
|
||||||
NOOK_HOST="${NOOK_HOST:-https://nook.freno.me}"
|
NOOK_HOST="${NOOK_HOST:-https://nook.freno.me}"
|
||||||
NESSA_HOST="${NESSA_HOST:-https://nessa.freno.me}"
|
NESSA_HOST="${NESSA_HOST:-https://nessa.freno.me}"
|
||||||
|
|
||||||
@@ -43,6 +46,17 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "== live copy =="
|
echo "== live copy =="
|
||||||
|
# The root is every other surface's entry point and deploys from the same tree, so it is
|
||||||
|
# probed first. No -L: a redirect away from 200 is the failure we want to see, not a pass.
|
||||||
|
root_code=$(curl -sS -o /dev/null -w '%{http_code}' "$ROOT_HOST/" 2>/dev/null)
|
||||||
|
if [ "$root_code" = "200" ]; then
|
||||||
|
ok "$ROOT_HOST/ serves 200"
|
||||||
|
elif [ -z "$root_code" ] || [ "$root_code" = "000" ]; then
|
||||||
|
bad "$ROOT_HOST/ gave no response, expected 200"
|
||||||
|
else
|
||||||
|
bad "$ROOT_HOST/ returned $root_code, expected 200 (green subdomains do not cover the root)"
|
||||||
|
fi
|
||||||
|
|
||||||
page=$(curl -sS "$NOOK_HOST/" 2>/dev/null)
|
page=$(curl -sS "$NOOK_HOST/" 2>/dev/null)
|
||||||
case "$page" in
|
case "$page" in
|
||||||
*"One-click setup for Tailscale and authorized known hosts"*)
|
*"One-click setup for Tailscale and authorized known hosts"*)
|
||||||
|
|||||||
Reference in New Issue
Block a user