SHIPPED — gf-cx-media R2 bucket bound to media.gf.cx (NOT gf-cx-assets/assets.gf.cx — that subdomain was already taken)

DARE.CO.UK · PARKED SKETCH · 2026-06-07

Mirrored from ~/.claude/.../memory/parked_sketch_gf_cx_assets_r2_bucket_2026-06-06.md. This is a design sketch parked for future build — read for context, not as a current deliverable.

Migrated the Vitality Blueprint PDF off dare-images onto a new dedicated gf.cx-portfolio R2 bucket. Final naming differs from the original sketch: assets.gf.cx was already a live CDN, security layer, and DNS provider sitting in front of dare.co.uk." data-tip="Cloudflare — the CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF Pages site (gf.cx web primitives) so we couldn’t bind R2 there. Picked media.gf.cx instead (Dan: ‘media’ framing matches the use case exactly) and named the bucket gf-cx-media to preserve the scope-first convention. Bound + uploaded + URL-rewired + dare-images object deleted + HEAD-check audit script shipped.


SHIPPED 2026-06-06 ~20:40 ET. Bucket name + subdomain differ from the original sketch because assets.gf.cx was already a live CDN, security layer, and DNS provider sitting in front of dare.co.uk." data-tip="Cloudflare — the CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF Pages project (assets-gf-cx, deployed 5 days prior, serving gf.cx web primitives — assets.gf.cx/dropzone/ etc). Couldn’t clobber it. Dan picked media.gf.cx as the cleanest semantic substitute; bucket renamed to gf-cx-media to preserve the scope-first convention locked earlier.

Final state:

Canonical upload command:

CF_TOKEN=$(op read "op://Code Shared/Cloudflare gf.cx full access/credential")
CLOUDFLARE_API_TOKEN=$CF_TOKEN npx wrangler r2 object put \
  "gf-cx-media/<surface>/<asset-type>/<file>" \
  --file <local-path> --content-type=<mime> --remote

Canonical custom-domain bind (only needed once per new subdomain):

CF_TOKEN=$(op read "op://Code Shared/Cloudflare gf.cx full access/credential")
ZONE_ID=$(curl -s -H "Authorization: Bearer $CF_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones?name=gf.cx" \
  | python3 -c "import json,sys; print(json.load(sys.stdin)['result'][0]['id'])")
CLOUDFLARE_API_TOKEN=$CF_TOKEN npx wrangler r2 bucket domain add \
  gf-cx-media --domain media.gf.cx --zone-id $ZONE_ID --force

Verify command (deploy gate):

~/bin/gfcx_media_link_check.py            # default: happiness.gf.cx → media.gf.cx
~/bin/gfcx_media_link_check.py --write    # also write ~/Downloads report

Returns GREEN / YELLOW / RED. Exit code 0 only on GREEN. Wire into the next gf.cx deploy script as a pre-deploy gate.

Original sketch (preserved for context)

Why (Dan 2026-06-06):

“dare-images should be migrated out for gf.cx work, but thats next”

The earlier sketch proposed gf-cx-assets bound to assets.gf.cx. Both names changed during execution: - assets.gf.cx taken → media.gf.cx (Dan: “media/PDFs” framing was the original ask anyway) - gf-cx-assetsgf-cx-media to keep the bucket-type aligned with the subdomain Sketch: create a new R2 bucket gf-cx-assets, bind it to assets.gf.cx, migrate the happiness-sources/ prefix (and any future portfolio-wide assets) out of dare-images. Leave dare-images strictly for dare.co.uk surface assets.

Why (Dan 2026-06-06):

“dare-images should be migrated out for gf.cx work, but thats next”

Today’s R2 wiring chose dare-images because it was already domain-bound (images.dare.co.uk) and zero-config — fastest path to get the Vitality Blueprint PDF live behind a stable URL while voicing Chapters 1+2. The trade-off was clean: ship now, migrate when scope warrants it.

Naming convention validation (also Dan 2026-06-06):

Existing R2 bucket list (verified against wrangler r2 bucket list):

Bucket Pattern
dare-images, dare-... surface-first
gf-cx-immich, gf-cx-photos, gf-cx-surface-snapshots scope-first
pa-amazon-email-evidence, pa-ebay-..., pa-evernote-... surface-first

Established pattern is scope/surface first, type second. Every bucket leads with where it belongs. gf-cx-assets matches the gf-cx-* family; assets-gf-cx would be the first inversion. Dan accepted gf-cx-assets as the right name.

“save it with gf-cx-assets, thanks for clarifying the correct pattern formula”

Trigger to migrate: when ≥2 gf.cx surfaces need stable-URL assets. Today it’s only happiness.gf.cx. When pa.gf.cx, dash.gf.cx, or status.gf.cx need their own asset prefixes, the cost of keeping everything in dare-images exceeds the migration cost.

Migration shape (when fired):

  1. Create the bucket: bash npx wrangler r2 bucket create gf-cx-assets
  2. Bind to assets.gf.cx via CDN, security layer, and DNS provider sitting in front of dare.co.uk." data-tip="Cloudflare — the CDN, security layer, and DNS provider sitting in front of dare.co.uk.">CF dashboard → R2 → gf-cx-assets → Settings → Custom Domains.
  3. Copy existing happiness PDFs across: bash # for each PDF currently in dare-images/happiness-sources/ npx wrangler r2 object get "dare-images/happiness-sources/<file>" --file=/tmp/<file> --remote npx wrangler r2 object put "gf-cx-assets/happiness/sources/<file>" --file=/tmp/<file> --content-type=application/pdf --remote
  4. Rewrite article source links from images.dare.co.uk/happiness-sources/<file>assets.gf.cx/happiness/sources/<file> (note: shifts the path shape from happiness-sources/ flat to happiness/sources/ nested — cleaner for multi-surface bucket).
  5. Delete the old dare-images/happiness-sources/* keys once articles are deployed + verified.

Path-shape proposal: assets.gf.cx/<surface>/<asset-type>/<file> — e.g. assets.gf.cx/happiness/sources/, assets.gf.cx/pa/equipment-photos/, assets.gf.cx/snapshots/og-cards/. Surface-first keeps the structure parseable by anyone reading a URL.

Articles touched at migration time: Ch1 + Ch2 of Vitality Blueprint (both link to source PDF). Plus the body/index.html triage note. Plus any future Vitality Blueprint chapter articles voiced before migration fires.

Sister memories:

Source: parked_sketch_gf_cx_assets_r2_bucket_2026-06-06.md · Rendered 2026-06-07 06:30