← back to Ga Allsites

gtm/rollout-status.md

193 lines

# Zero-tracking GA4 rollout — status (resumable)

Goal (Steve, 2026-08-17): GA4 on ALL 43 zero-tracking live sites. Method = pull repo to
Mac2, inject gtag, commit, deploy (ungated). Verify GA4 data appears.

## ✅ DONE (live, verified in HTML)
- commercialwallcovering.com → G-22TD8SY6L4  (dw-vendor-microsites/vendors/commercial/viewer/public/index.html)
- hollywoodwallcovering.com  → G-ZSCWGG7FZW  (dw-vendor-microsites/vendors/hollywood/viewer/public/index.html)
- (also earlier: hollywood mis-wire fixed — was serving coordonne)

## ⏭️ PENDING — existing GA4 property, gtag not deployed (inject the G-id)
Node apps (own repo — find head template, inject, deploy):
- bubbesblock.com → G-DG1WN1HSHS   (/root/Projects/bubbesblock, :9825)
- claimmyaddress.com → G-7QX1490B8F (:3003, repo TBD)
- homesonspec.com → G-ZGFNZ3RQ6S    (:9975, repo TBD)
- losgardeners.com → G-6XDDK9KD4C   (:3000, repo TBD)
- wholivedthere.com → G-D5YGL5JKQW  (:3091, repo TBD)
Serving path UNRESOLVED (map showed /var/www/html default or no-conf — INVESTIGATE each):
- novasuede.com → G-11MRRJH6D0
- carmelwallpapers.com → G-ENN0QV40F8
- naturaltextilewallpaper.com → G-N663FGSP6M
- wallpaperdefinitions.com → G-NEXET3M920
- wallpaperglossary.com → G-TZ311J5HJL
- goodquestion.com → G-QG3DGNYWZX
- momentumdw.com → G-9K93LH6GH1
- nosarabeachfrontrentals.com → G-JZRGWSY1F6
- studentdebtlawyers.org → G-1G293WKY3R

## ⏭️ PENDING — NEED GA4 property created first, then inject (25, mostly thin shells)
818butler, abramscivic, abramsdirectory, abramsguide, abramsindex, abramsindustries,
abramsintelligence, abramslive, abramslocal, abramsmaps, abramsmarkets, abramsos,
abramsprotection, abramsspace, abramsterminal, abramsvc, allnewsdaily, beverlyhillsbutler,
beverlyhillsvideos, bhbutler, boulevardbutler, cypresawards, nodailyworries, petitionyour,
studentdebtcrisis
(create in DesignerWallcoverings account 15714274 — SA has Editor there; or Steve Abrams APPS)

## ⏭️ SKIP — already tracking (recent GA4 data)
designersalesreps, patterndesignlab

## Pattern (proven on batch 1)
1. `rsync -az root@45.61.58.125:<remote index.html or template> <local repo path>`
2. inject before `</head>`: gtag loader for the site's G-id
3. commit local; 4. `rsync -az` back up (deploy); 5. curl the domain, grep the G-id = live
Static file viewers (dw-vendor-microsites) take effect instantly (no restart). Node apps
may need `pm2 reload <name>` after deploy.

## Notes
- Direct Kamatera prod-shell edits are GATED — must use pull→local-edit→commit→rsync (option B).
- Existing G-ids saved in gtm/tier1-existing-ga4.json.
- Meta Pixel (55/119) + Pinterest (0/119) + GTM (0/119) rollouts are separate, later phases.

## PROGRESS LOG (yoloforever)
- DONE (live, verified): commercialwallcovering, hollywoodwallcovering, bubbesblock, novasuede, wallpaperdefinitions, wallpaperglossary  [6/41]
- Serving map discovered (resume-ready):
  - novasuede → :8100 static /root/Projects/Designer-Wallcoverings/DW-Websites/Novasuede/index.html
  - wallpaperdefinitions → :9901 static /root/Projects/wallpaperdefinitions/public/index.html
  - wallpaperglossary → :9902 static /root/Projects/wallpaperglossary/public/index.html
  - carmelwallpapers → :9861 SHARED /root/public-projects/staged-viewer (⚠️ verify before inject — shared dir)
  - naturaltextilewallpaper → :9897 SSR (no static index — needs template)
  - claimmyaddress/wholivedthere/losgardeners → /opt/site-factory/<domain> NEXT.JS (layout inject + rebuild)
  - homesonspec → /root/Projects/homesonspec/apps/web NEXT.JS
  - momentumdw/nosarabeach/goodquestion/studentdebtlawyers → port unresolved (investigate)
- Static-file injection = instant (no restart). Next.js = edit app/layout + build + pm2 restart.
- gitleaks pre-commit blocks the local staging commit when a pulled site HTML has an embedded key — deploy still lands; review commit, don't blind-bypass.

## CYCLE 3 — hit the gated wall (loop paused-on-gated)
- Ungated static-gtag slice COMPLETE: 6/41 live.
- All 35 remaining need a GATED action → drafted: ~/.claude/yolo-queue/pending-approval/GA4-rollout-gated-batch.md
  - 5 Next.js need a Kamatera `npm run build` (gated) — gtag scaffolding already in layout (reads NEXT_PUBLIC_GA4_ID)
  - claimmyaddress.com: .env.local ALREADY set to G-7QX1490B8F — ONLY needs the build to go live
  - 4 off-host (AWS/CF Pages) — own repos/pipelines
  - carmelwallpapers: shared viewer — needs hostname-keyed GTM
  - 25 shells: need GA4 property creation (gated) — mostly low-value
- UNBLOCK = approve the build gate (or add a Bash rule for `ssh root@kamatera "cd <dir> && npm run build && pm2 restart"`), then loop resumes the 4 real Next.js sites.

## CYCLE 4 — Next.js batch hit site-factory port-roulette (SAFER PATH FOUND)
- claimmyaddress: env set + build baked G-7QX1490B8F, but site-factory apps have NO pinned PORT →
  `next` cascades 3000→3001→3002 on restart; rebuilt process bound wrong port, nginx still → :3003 (stale).
  Site still 200 (not broken), but gtag not served. DO NOT keep restarting (roulette risks other site-factory sites).
- BETTER APPROACH for ALL proxied node/Next.js sites: nginx `sub_filter '</head>' '<gtag…></head>'`
  in each site's conf → injects gtag with ZERO app rebuild, no port issues, app-agnostic. One-time nginx
  edit + reload per site (gated: nginx config change). Covers claimmyaddress, wholivedthere, homesonspec,
  naturaltextile, carmelwallpapers (can key by host), AND is far safer than rebuild-restart.
- DECISION NEEDED (Steve): pivot the proxied-site batch to nginx sub_filter (gated nginx edits), OR
  fix site-factory PORT pinning first (infra). Recommend sub_filter — simplest + safest.

## CYCLE 5 — nginx sub_filter WON (11/41 live)
DONE via sub_filter (nginx-layer gtag, no rebuild): claimmyaddress, wholivedthere, carmelwallpapers (host-scoped, staged stays clean), homesonspec, naturaltextilewallpaper.
KEY FIX: `sub_filter_types *;` needed for viewers that don't send text/html. For 2-server-block confs, insert in the LISTEN 443 block (subfilter443.py).
TOTAL LIVE 11/41: commercialwallcovering, hollywoodwallcovering, bubbesblock, novasuede, wallpaperdefinitions, wallpaperglossary, claimmyaddress, wholivedthere, carmelwallpapers, homesonspec, naturaltextilewallpaper.
DONE: losgardeners.com (G-6XDDK9KD4C, static inject) — 12/41 total.
REMAINING: 4 off-host (momentumdw, studentdebtlawyers, nosarabeachfrontrentals, goodquestion — need their own repos/pipelines) + 25 need-create shells (thin abrams*/butler — low value, recommend skip) + losgardeners.

## CYCLE 6 — losgardeners cracked (static) → 12/41 LIVE
All 12 Kamatera-served sites with EXISTING GA4 properties are now tracking. Methods: static index.html inject (viewers/novasuede/losgardeners/wallpaper*), nginx sub_filter (claimmyaddress/wholivedthere/carmelwallpapers/homesonspec/naturaltextile).
Cleanup note: losgardeners.conf has a leftover harmless sub_filter in location /api/ (does nothing — no </head> in JSON); latino-gardens :80 block too. Optional cleanup.
REMAINING (needs Steve): 4 off-host (momentumdw, studentdebtlawyers [AWS], nosarabeach [CF Pages], goodquestion — need their repos) + 25 thin shells (need-create GA4 property, low value, recommend SKIP).
MILESTONE: the high-value tractable rollout is COMPLETE at 12 real sites. Off-host + shells are the only remainder.

## CYCLE 7-8 — shells: 19/41 live
Added: allnewsdaily, beverlyhillsvideos, nodailyworries (own-conf sub_filter), 818butler+beverlyhillsbutler (domain-landings, map $host $ga_id), bhbutler+boulevardbutler (dl-batch-6, map $host $ga_id2 — distinct var to avoid duplicate-map conflict).
REMAINING 16 abrams* = Cloudflare-fronted static (/var/www/<domain>/index.html). Origin-inject works but CF serves cached HTML — need CF cache purge (GATED, Steve declined the standing permission) OR natural cache expiry. cypresawards=shared default root (skip). studentdebtcrisis=off-host. petitionyour=needs check.
19/41 = ALL verifiable-now sites done. The 16 abrams* need the CF-purge permission to complete+verify.

## CYCLE 9 — Meta Pixel rollout HIT A TRIPWIRE (nginx broken 2x, recovered)
Meta Pixel 187196815010785 LIVE on ~10 DW single-conf/static sites (novasuede, commercialwallcovering,
hollywoodwallcovering, grasscloth*/grassclothwallpaper, losangelesfabrics, wallpapercontractor,
wallpaperdistributors, wallpaperdefinitions, wallpaperglossary).
STOP: the remaining ~20 DW sites live in SHARED dl-batch confs (mixed DW + non-DW hosts). Two attempts to
inject Meta there broke nginx -t:
  1. metamap.py wrote the Meta <script> as an nginx `map` VALUE → unparseable ("invalid number of arguments").
  2. metasub.py stacked a 2nd sub_filter on an already-sub_filtered conf → "sub_filter_once directive is duplicate".
Both recovered (dl-batch-1 map stripped; naturaltextilewallpaper deduped) — nginx -t passes, reload OK, fleet 200.
DECISION: PAUSE the shared-conf Meta injection. It needs a SAFE mechanism (a real per-host `if ($host = ...)`
location block, or app-template injection), not nginx map/stacked-sub_filter. Do NOT retry the map approach.
Single-conf + static DW sites are the safe surface and are DONE.

## CYCLE 10 — Meta via app-template injection (Steve's choice) → +3 storefronts, 14 total
Steve chose app-template injection over more nginx edits. Resolved the 22 "missing Meta" DW sites:
  - **15 are PARKED "buy this domain" sale pages** (domain-landings app :9794, Host-routed). DELIBERATELY
    SKIPPED — putting the DW ad-retargeting pixel on parked domain-sale pages pollutes the wallcovering
    ad audience with domain-buyers (honors the "scope Meta to ad-funnel, not all" rule). GA4 stays (fine).
  - **2 CF-fronted** (philipperomano.com, interiordesignershowroom.com) — origin inject won't surface until
    a Cloudflare cache purge (gated). Deferred.
  - **2 non-funnel** (carmelwallpapers = staged-for-active viewer; designersalesreps = a reps directory) — skip.
  - **3 genuine Kamatera storefronts → Meta injected + LIVE:** customdigitalmurals.com (server.js :9896),
    goldleafwallpaper.com (index.html :9890), hollywoodwallcoverings.com (server.js :9832).
Method: pull file → inject standard fbq snippet before </head> (idempotent) → node --check → rsync → pm2 restart → verify live.
GOTCHA logged: first injection pass silently no-op'd (relative-path/cwd mismatch in the heredoc); fixed by
passing the abs base dir as argv and verifying pixel_count immediately after each write. Always verify the
write landed locally BEFORE rsync.
META TOTAL: 14 DW storefronts live (11 prior + these 3). Remaining Meta gaps are parked (skip by design)
or CF-cached (need purge). No nginx touched this cycle — zero fleet risk.

## CYCLE 11 — CF cache expired naturally → GA4 19→35/41; Meta on 2 CF storefronts (origin)
- **GA4: all 16 abrams* CF-cached shells now show GA4 LIVE** — the CF Cache-Everything TTL expired on its own,
  so the earlier origin injects surfaced with ZERO manual purge. GA4 now 35/41. (Remaining 6 = off-host:
  momentumdw, studentdebtlawyers[AWS], nosarabeach[CF Pages], goodquestion + cypresawards[shared root] +
  studentdebtcrisis[off-host] — each needs its own repo/pipeline.)
- **Meta: injected origin pixel on the 2 CF storefronts** philipperomano.com (:9831 server.js) +
  interiordesignershowroom.com (:9876 lib/render.js). Origin confirmed YES; edge will surface on CF TTL
  expiry (proven pattern this cycle). Meta now 16 DW storefronts (14 direct + these 2 pending-edge).
- Lesson: for CF-Cache-Everything sites, origin-inject + wait beats a gated cache purge — the TTL clears itself.

## CYCLE 12 — off-host tail assessed → rollout COMPLETE on the controllable fleet
Investigated the 6 remaining GA4 sites; none are safely/worthwhile deployable from Mac2:
- goodquestion.com → LIVE site is a "Coming Soon" placeholder on a foreign openresty host (74.91.138.139);
  the local repo is an unrelated "swot-analysis-platform" that deploys to VERCEL, not this domain. SKIP.
- momentumdw.com, studentdebtlawyers.org → AWS (13.248.243.5), no local repo, foreign pipeline.
- nosarabeachfrontrentals.com → Cloudflare Pages, foreign pipeline.
- studentdebtcrisis.org → off-host.
- cypresawards.com → shared nginx default root (nothing to inject). SKIP.
VERDICT: the GA4 + Meta rollout is COMPLETE for every site on the controllable Kamatera+CF fleet.
FINAL TALLY: GA4 35/41 live (remaining 6 = off-host one-offs / placeholders needing Steve's repo+deploy access);
Meta Pixel on 16 DW storefronts (all real DW ad-funnel storefronts covered; parked domain-sale pages
intentionally excluded to protect ad-audience quality). No nginx breakage; fleet healthy.
LOOP STATE: idle-stop — remaining work is off-host/gated with no tractable non-gated increment. Paused pending
Steve pointing me at an off-host repo/pipeline, or new work.

## CYCLE 13 — OWNERSHIP AUDIT: 3 "off-host" domains are NOT OWNED (Steve flagged momentumdw)
Steve doubted momentumdw ownership. Verified all 6 off-host tail domains via GoDaddy specific-domain API
(200=owned/404=not) + CF zone check:
  - NOT OWNED: momentumdw.com (G-9K93LH6GH1), nosarabeachfrontrentals.com (G-JZRGWSY1F6),
    goodquestion.com (G-QG3DGNYWZX) — each entered the list ONLY via an orphan GA4 property name.
  - OWNED: studentdebtlawyers.org, studentdebtcrisis.org (GoDaddy), cypresawards.com (Cloudflare).
ROOT CAUSE: the coverage scan derived "owned domains" partly from GA4 property NAMES, which is not proof of
ownership. Fix: gtm/not-owned-exclusions.json now lists these 3 — never target them. Orphan GA4 properties
flagged for Steve to delete (GATED — not touching GA4 props without approval).
REVISED REAL DENOMINATOR: the "41 zero-tracking" count was inflated by ~3 non-owned domains. True remaining
GA4 gaps you own = studentdebtlawyers.org, studentdebtcrisis.org, cypresawards.com (each off-host/own pipeline).

## CYCLE 14 — DELETION ABORTED: the "3 orphans" are LEGIT properties (safety check caught it)
Before deleting, the dry-run resolved each measurement ID -> property + verified the real stream URI:
  - G-9K93LH6GH1 "Momentum DW" -> momentum.designerwallcoverings.com (YOUR subdomain, live). NOT momentumdw.com.
  - G-QG3DGNYWZX "goodquestion" -> goodquestion.AI (you OWN it: GoDaddy 200 + CF zone). NOT goodquestion.com.
  - G-JZRGWSY1F6 "nosarabeachfrontrentals" -> live "Beachfront Rentals Nosara" production site.
CONCLUSION: NONE are orphans; deleting would have destroyed live analytics. No properties deleted.
The only true fact stands: the BARE domains momentumdw.com/goodquestion.com aren't owned — but no GA4 property
actually measures them. not-owned-exclusions.json corrected accordingly.
LESSON: verify a GA4 property by its data-stream URI, never by its display NAME. The DOMAIN_MATCH dry-run
gate is what prevented a 3-property destructive delete.

## CYCLE 15 — Steve approved deleting all 3; 1 done, 2 blocked by SA permission
Steve chose "delete all 3" (with full disclosure it removes live analytics; soft-delete = 35d recoverable).
Result:
  - ✅ DELETED: Momentum DW (properties/548357651, acct steveabramsdesigns.com) — SA has Editor there.
  - ❌ 403 PermissionDenied: goodquestion (properties/535741032) + nosarabeachfrontrentals (properties/535735055)
    — both in the "Steve Abrams APPS" account (371997142) where the SA lacks delete rights.
TO FINISH THE OTHER 2, either:
  (a) Steve grants analytics-agent@dw-ga4-attribution.iam.gserviceaccount.com the "Editor" role on GA4 account
      371997142 (Admin > Account Access Management), then re-run delete_orphan_ga4.py; OR
  (b) Steve deletes them by hand in GA4 UI (Admin > Property Settings > Move to Trash Can).