Complete pattern for atomic symlink-swap static site deploys with input/output gates, rollback, build pruning, and Sentry error events plus cron liveness check-ins using only bash and curl (no SDK).
For static sites served by nginx, an atomic deploy uses ln -sfn <new> public.tmp followed by mv -T public.tmp public — rename(2) is atomic so nginx never sees a partial state. Each build lands in a timestamped directory and public is always a symlink.
rename(2) cannot atomically replace a non-empty directory with a symlink. The one-time migration requires a two-rename gap: copy the existing directory into the builds area, create the new symlink alongside, then swap with two mv commands.
Sentry's store endpoint accepts raw JSON events with DSN-derived auth. Parse the DSN to extract the key, host, and project ID. POST to the store endpoint with Content-Type application/json and an X-Sentry-Auth header containing the sentry version, key, and client name. Returns HTTP 200 on success.
Hit the cron endpoint with the monitor slug and key as path segments, status as a query param. Returns HTTP 202. The monitor auto-creates on first check-in (no UI setup required on current Sentry plans).
Compare the readlink target suffix against git rev-parse short HEAD — skip render and deploy when the live build already matches HEAD.
Input gate: parse the data file (e.g. JSON) before rendering. Output gate: check for expected markers in the rendered HTML and verify no unreplaced placeholders remain. Both fire before the symlink swap, so a bad render never goes live.
The deploy script lives in the repo, but cron must git-pull before running it. A server-local stub does the pull, then execs the repo script — so the freshly-pulled version always runs, and the stub itself never self-updates mid-execution.
Store the previous symlink target in a dotfile. The rollback subcommand flips the symlink; running rollback twice rolls forward.
List timestamped build directories sorted by mtime, skip the newest N, remove the rest. A legacy directory is exempt by using a glob that only matches timestamp-prefixed names.