Steps to take the generated static tree live on pixieengine.com. Plan: a greenfield AWS CDK stack (
infra/,PixieStaticStack) stands up a new private S3 bucket + new CloudFront distribution + edge function. The legacy distributionE3BKYYG8EH9O0Kis never touched. Cutover = move thepixieengine.comDNS alias to the new distribution; rollback = move it back. Companion toarchitecture.md; the canonical infra-as-code runbook is../infra/README.md.(Historical note: an earlier draft of this doc repointed
E3BKYYG8EH9O0K's origin in place. That approach is superseded by the greenfield stack below — it's safer, fully reversible via DNS, and keeps the live distribution untouched until verification passes.)
- New distribution: a fresh CloudFront distribution (separate from
E3BKYYG8EH9O0K).index.htmlroot object;403/404 → /410/index.htmlserved as 404 (CloudFront can't emit a 410 in custom-error responses); rewrite function on viewer-request. - New bucket:
pixieengine-static— private, CloudFront OAC only,RETAINremoval policy. - Edge function:
pixieengine-rewrite(infra/functions/rewrite.civet→ compiled todist/functions/rewrite.js; id/slug rewrite,?tagged=→/tags/301, dir→index). Unit-tested vianpm testininfra/. - Custom domain (cutover only,
-c withDomain=true): ACM cert (DNS-validated in zoneZPF3QICRGSLCF) +pixieengine.comalias + Route 53 A/AAAA → new dist. - Deploy IAM role:
pixie-deploy(defined by the stack, assumed by thepixie-opsuser): rw onpixieengine-static+cloudfront:CreateInvalidationon the dist + scoped backup-recovery access. Keyless — content deploys assume it for short-lived STS creds (nothing to rotate).
The initial upload used a hand-made staging bucket
pixieengine-com-site(256,530 objects), server-side-copied intopixieengine-static. That bucket was deleted 2026-06-02; deploys now sync directly from../static-site/public(below).
cdk deployruns locally as thepixie-opsprofile (scoped to assume the CDK bootstrap roles):npx cdk deploy --profile pixie-ops. Only the one-timecdk bootstrapneeds admin (run from CloudShell) — already done for this account.cdk synth/diffandnpm testneed no creds; contentaws s3 syncuses the keylesspixie-deployprofile (role-chained offpixie-ops);pixieengine-auditremains read-only.infra/is self-contained: Civet sources (bin/,lib/,functions/,build.civet,validate-template.civet) thatnpm install+cdkcompile automatically viabuild.civet(esbuild) — no separate build step. Get it into CloudShell viagit push+clone, or upload a zip.
cd ~/apps/pixieengine.com/infra
npm install
npm test # rewrite-function unit tests (16 assertions)
npx cdk synth # confirms the stack compiles to a valid templateLocally (the account is already bootstrapped — bootstrap is CloudShell/admin-only and rarely needed):
cd infra && npm install
npx cdk deploy --profile pixie-ops # creates bucket + dist + function; alias NOT attachedNote the DistributionId and DistributionDomain (dXXXX.cloudfront.net) outputs.
Content is not managed by CDK (it's a deploy step). Sync the generated tree from local:
aws s3 sync ../static-site/public s3://pixieengine-static --delete --profile pixie-deploy
aws cloudfront create-invalidation --distribution-id <DistributionId> --paths "/*" --profile pixie-deployhttps://dXXXX.cloudfront.net/→ editor shell (200);/sprites/→ gallery;/sprites/page/100/→ logarithmic pager./sprites/257680-ugly-killman-donkey-ass→ sprite page (slug variant 200)./sprites?tagged=mario→ 301 →/tags/mario/.- A missing id (e.g.
/sprites/999999999) → 404 (the/410/index.htmlgone page). /sitemap.xml→ 200.- A sprite with a replay → ▶ Watch replay plays in the editor.
- A tune → ▶ Play in Composer loads in the composer.
A CloudFront alias can live on only one distribution. So, only after verification passes:
- Remove the
pixieengine.comalias (CNAME) from the legacy distE3BKYYG8EH9O0K(console). - Add it to the new dist + create cert + DNS:
(ACM DNS validation auto-creates a CNAME in the zone; the first deploy waits for it.)
npx cdk deploy -c withDomain=true # ACM cert, alias, Route 53 A/AAAA -> new dist - Verify
https://pixieengine.com/(rerun the section-D checklist on the apex domain).
Re-point the pixieengine.com Route 53 alias back to the legacy dist E3BKYYG8EH9O0K (or re-add
the alias there). Nothing in the legacy distribution was modified, so rollback is pure DNS.
aws s3 sync ../static-site/public s3://pixieengine-static --delete --profile pixie-deploy
aws cloudfront create-invalidation --distribution-id <DistributionId> --paths "/*" --profile pixie-deployIncremental regen (enriched titles, etc.) → re-generate → s3 sync → invalidate just the changed
paths. Full tree is ~256k objects.
- Moderation deploys (removals / adult-gates) ride the same path:
generate.mjshonorsstatic-site/{removed.tsv,adult.tsv}, so the regen drops removed pages and gates adult ones; the--deleteis what makes a removed page 404. Full runbook:static-site/README.md"Removing content" + "Moderation review". - Use the default (mtime)
sync, not--size-only. A freshgeneraterewrites every file, so a default sync re-uploads all and is always correct.--size-onlyis faster but skips content changes that don't alter byte size — notably paginated listings whose page count changes digit-preserving (e.g. last page4115→4108), which silently desyncs gallery/tag/profile "Last »" links. If you must use--size-onlyfor speed, follow with a default sync of the listing trees.
- Content-types are inferred from extensions (
.html→text/html,.css→text/css,.xml→application/xml). - After launch: delete the temporary
pixieengine-audituser. (The staging bucketpixieengine-com-sitewas deleted 2026-06-02.)