feat(addons): self-refresh inside the container, drop sync workflow#211
Conversation
…ntent Previously the byte-skip short-circuit returned without touching postguard-outlook-manifest.json, so when the outlook addon re-tagged v0.1.3 → v0.1.4 → v0.1.5 with byte-identical manifest.xml content the served file was correct but the metadata stayed pinned to v0.1.3 — tag, releaseUrl and publishedAt all rotted. When the cached sha matches the upstream sha but the cached tag does not, rewrite the metadata using the new release info without re-downloading. The served xml stays as-is.
The image now bundles node + scripts/sync-addons.mjs and a small /docker-entrypoint.d/50-sync-addons.sh shim that backgrounds a refresh loop (default 6h, override with SYNC_ADDONS_INTERVAL, disable with SYNC_ADDONS_DISABLE). The loop writes via temp-file + rename so live download requests never see a partial file. The committed binaries in static/downloads/ stay as the build-time / cold-boot fallback. Removes the Sync addons workflow + the chore PRs it produced — addon releases now reach prod by way of the running container picking them up on its next loop iteration. The script also gained a DOWNLOADS_DIR env override so it can target the nginx htdocs dir at runtime instead of the in-repo static/ tree. Verified by building the image and running it with SYNC_ADDONS_INTERVAL=3: loop output appears in docker logs, nginx serves /downloads/postguard-outlook-manifest.xml with 200.
|
/dobby review this pr |
|
Dobby is on it! Dobby will read the PR and leave a careful review for Master Ruben. Dobby promises to be thorough! |
|
Reviewed this end-to-end. Overall the approach is solid — atomic rename, sha256 verification against the API digest, lookback window for assets-not-yet-uploaded races, and the runtime Operational
Script
Docker
Visibility regression (worth thinking about, not blocking)Removing
Not a blocker — flagging because the PR description leans on "addon releases reach prod by way of the running container" which is exactly the visibility tradeoff. Test planThe checklist in the PR description covers the happy path well. Two suggested additions:
Nits
Nothing here is a hard block. (4), (6) and the rate-limit doc note in (1) feel worth landing alongside; the rest are judgment calls. |
The auth branch was a holdover from when this ran in GitHub Actions, where the runner's shared egress IP made the 60/h unauthenticated cap real. The container has a dedicated egress and at the 6h default makes 2 listing calls / 6h — 0.5% of the unauth quota. Asset downloads come from browser_download_url and aren't API-rate-limited. Removes the conditional so we're not tempted to plumb a token through the entrypoint solving a non-problem.
|
Thanks for the thorough pass /dobby. On point (1) specifically: The token plumbing was a holdover from when this ran in GitHub Actions, where the runner's shared egress IP made the 60/h unauthenticated cap real. The container ships with a dedicated egress IP, so the math changes: 2 listing calls per 6h iteration = ~0.33 calls/h, or 0.5% of the unauthenticated quota. The asset itself comes from So rather than plumb Will look at the other points (4, 6, signal-handling, visibility regression) separately. |
|
Dobby sees the update, master Ruben! Dobby is most pleased to learn the runtime is no longer behind a shared-NAT egress — the math checks out and dropping the Authorization conditional is the right call. The bot will go verify 35329f8 now and await your follow-ups on the other points. |
|
Verified 35329f8 — the conditional is gone, the inline comment captures the 6h-interval math, and the commit message preserves the why for git archaeology. Point (1) is resolved on this end; the bot will look out for your follow-ups on (4), (6), signal-handling, and the visibility regression. |
Summary
scripts/sync-addons.mjs+ a Node binary (copied fromnode:24-slim) into the nginx runtime image, plus a/docker-entrypoint.d/50-sync-addons.shshim that backgrounds a refresh loop (default 6h,SYNC_ADDONS_INTERVALto tune,SYNC_ADDONS_DISABLEto skip entirely).DOWNLOADS_DIRenv override so it can target the live nginx htdocs dir (/usr/share/nginx/html/postguard/downloads) at runtime, and uses tmp-file +rename(2)so concurrent download requests never observe a partial file..github/workflows/sync-addons.ymland thechore: sync PostGuard addons …PRs it produced — addon releases now reach prod by way of the running container picking them up on its next loop iteration, no human merge required.GITHUB_TOKENplumbing from the fetch path. At 6h intervals from a dedicated egress IP we're at 2 listing calls / 6h — 0.5% of GitHub's 60/h unauthenticated cap. Asset downloads come frombrowser_download_urland aren't API-rate-limited at all.8664f87): when upstream re-tags byte-identical content (v0.1.3 → v0.1.5 etc.), the metadata json is refreshed in place without a redundant re-download.Why
chore/sync-addonsPR needed a human to merge it. Today's symptom: outlook addon v0.3.0 published 2026-05-11 12:13 UTC, daily cron had already run at 09:56 UTC, no PR opened, staging stayed on v0.1.3.static/downloads/*binaries stay as the build-time / cold-boot fallback, so the image is always functional even withSYNC_ADDONS_DISABLE=1or network failures.Test plan
docker build -f docker/Dockerfile -t pg-website:test .succeeds.-e SYNC_ADDONS_INTERVAL=60;docker logsshows the loop iterating and reports[outlook] updating: v0.1.5 -> v0.3.0on first tick. (Don't drop below ~60s on a shared NAT — at 2 API calls per iteration the 60/h unauthenticated cap is reachable.)curl -I http://localhost/downloads/postguard-outlook-manifest.xmlreturns 200 with the v0.3.0 size (10457).-e SYNC_ADDONS_DISABLE=1; logs showdisabled via SYNC_ADDONS_DISABLE; nginx still serves the baked-in fallback./downloads/postguard-outlook-manifest.xmlupdates to the new sha withinSYNC_ADDONS_INTERVAL.Notes
static/downloads/postguard-outlook-manifest.json(v0.1.3 → v0.1.5) is what the cherry-picked re-tag commit left behind; the loop will overwrite it with v0.3.0 on first container start, so it's only the cold-boot fallback that's slightly stale.node:24-slim(samedebian:bookwormbase asnginx:1.27.4) so the runtime libs match without an extra apt install.