← back to Ga Allsites

gate2_inject.sh

369 lines

#!/usr/bin/env bash
# gate2_inject.sh — GATE-2 GA4 injection for the 34-site DW fleet microsite fleet
# Measurement ID: G-J2SL8P5KDL  (property "DW Fleet Microsites" / 551081041)
#
# IDEMPOTENT: any file already containing G-J2SL8P5KDL is skipped unchanged.
# REVERSIBLE: timestamped .bak files written before every edit; gate2_rollback.sh restores them.
# RUNS ON KAMATERA: ship this entire heredoc over SSH.
#
# Deploy command (Steve pastes):
#   ! ssh root@45.61.58.125 'bash -s' < ~/Projects/ga-allsites/gate2_inject.sh
#
# Rollback command:
#   ! ssh root@45.61.58.125 'bash -s' < ~/Projects/ga-allsites/gate2_rollback.sh
# ---------------------------------------------------------------------------
set -euo pipefail

ID="G-J2SL8P5KDL"

ssh root@45.61.58.125 "ID=$ID" 'bash -s' <<'REMOTE'
set -uo pipefail
ID="${ID:-G-J2SL8P5KDL}"
TS=$(date +%Y%m%d-%H%M%S)

# ────────────────────────────────────────────────────────────────────────────
# Counters
N_INJECTED=0; N_SKIPPED=0; N_FAILED=0; N_MANUAL=0
declare -A RESULT                 # file -> status string
declare -A BAK_MAP                # file -> .bak path (written to /tmp/gate2-bak-manifest.txt at end)

# ────────────────────────────────────────────────────────────────────────────
# STANDALONE TARGETS  (file path | vendor slug | pm2 process name)
# Each gets:  async gtag loader + gtag('js') + gtag('config', ID, {vendor:'<slug>'})
# inserted as the very first child of <head>.
STANDALONE_RAW=(
  "/root/Projects/artmura-site/public/index.html|artmura|artmura-site"
  "/root/Projects/carnegie-site/public/index.html|carnegie|carnegie-site"
  "/root/Projects/thibaut-site/public/index.html|thibaut|thibaut-site"
  "/root/Projects/elitis-site/public/index.html|elitis|elitis-site"
  "/root/Projects/romo-site/public/index.html|romo|romo-site"
  "/root/Projects/la-walls-site/public/index.html|la-walls|la-walls-site"
  "/root/Projects/british-walls-site/public/index.html|british-walls|british-walls-site"
  "/root/Projects/leed-walls-site/public/index.html|leed-walls|leed-walls-site"
  "/root/Projects/surface-stick-site/public/index.html|surface-stick|surface-stick-site"
  "/root/Projects/coleandson-site/public/index.html|cole-and-son|coleandson-site"
  "/root/Projects/fallingstar-site/public/index.html|fallingstar|fallingstar-site"
  "/root/Projects/philipjeffries-site/public/index.html|phillip-jeffries|philipjeffries-site"
  "/root/Projects/primo-leathers-site/public/index.html|primo-leathers|primo-leathers-site"
  "/root/Projects/quadrille-house-site/public/index.html|quadrille|quadrille-house-site"
  "/var/www/fabricut.designerwallcoverings.com/public/index.html|fabricut|fabricut-landing"
  "/root/Projects/fentucci-site/public/index.html|fentucci|fentucci-site"
)

# SHARED TEMPLATE (30-vendor wolfgordon binary)
# Gets:  async loader + gtag('js') in <head>,
#        gtag('config', ID, {vendor:cfg.vendor}) inserted INSIDE the async IIFE
#        on the line immediately after applyConfig(cfg);
SHARED_FILE="/root/Projects/wolfgordon-site/public/index.html"
# All 30 pm2 processes that run wolfgordon-site/server.js — reload all of them.
WOLF_PM2_NAMES=(
  anna-french-site brunschwig-fils-site coordonne-site designers-guild-site
  designtex-site gp-j-baker-site groundworks-site harlequin-site knoll-site
  koroseal-site kravet-site lee-jofa-site maharam-site maya-romanoff-site
  mulberry-site nina-campbell-site nobilis-site osborne-little-site
  phillip-jeffries-site pierre-frey-site ralph-lauren-site rebel-walls-site
  sancar-site sandberg-site scalamandre-site threads-site william-morris-site
  wolf-gordon-site wolfgordon-site zoffany-site
)

# FENTUCCI-WEBSITE (old binary with commented-out placeholder)
# Gets:  commented placeholder uncommented + ID set to G-J2SL8P5KDL.
# The real loader + config are inserted the same way as standalones (head injection),
# but we ALSO remove the now-redundant commented placeholder if both coexist.
FENTUCCI_OLD_FILE="/root/Projects/Designer-Wallcoverings/DW-Websites/Fentucci/public/index.html"
FENTUCCI_OLD_PM2="fentucci-website"

# ────────────────────────────────────────────────────────────────────────────
# Helper: build the head snippet for a static vendor slug
head_snippet_static() {
  local vendor="$1"
  printf '<script async src="https://www.googletagmanager.com/gtag/js?id=%s"></script>\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('\''js'\'',new Date());gtag('\''config'\'','\''%s'\'',{vendor:'\''%s'\''});</script>' \
    "$ID" "$ID" "$vendor"
}

# Helper: build the head snippet for the shared template (no config here; fires in IIFE)
head_snippet_shared() {
  printf '<script async src="https://www.googletagmanager.com/gtag/js?id=%s"></script>\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('\''js'\'',new Date());</script>' \
    "$ID"
}

# ────────────────────────────────────────────────────────────────────────────
# inject_standalone FILE VENDOR PM2_NAME
inject_standalone() {
  local file="$1" vendor="$2" pm2_name="$3"
  local label="$vendor"

  if [ ! -f "$file" ]; then
    RESULT["$label"]="MISSING — file not found: $file"
    (( N_MANUAL++ )) || true; return
  fi

  if grep -qF "$ID" "$file"; then
    RESULT["$label"]="SKIP — already contains $ID"
    (( N_SKIPPED++ )) || true; return
  fi

  if ! grep -qiE '</head>' "$file"; then
    RESULT["$label"]="NEEDS-MANUAL — no </head> anchor found: $file"
    (( N_MANUAL++ )) || true; return
  fi

  local bak="${file}.bak-ga-${TS}"
  cp -a "$file" "$bak"
  BAK_MAP["$file"]="$bak"

  local snippet
  snippet="$(head_snippet_static "$vendor")"

  # Insert the snippet immediately before </head>
  perl -0777 -i -pe "s{(</head>)}{${snippet}\n\$1}i" "$file"

  if grep -qF "$ID" "$file"; then
    RESULT["$label"]="INJECTED — $file [pm2: $pm2_name]"
    (( N_INJECTED++ )) || true
    # Reload pm2 process
    if pm2 reload "$pm2_name" >/dev/null 2>&1; then
      RESULT["$label"]+=" | pm2 reload: OK"
    else
      RESULT["$label"]+=" | pm2 reload: WARN (try restart manually)"
    fi
  else
    # Revert
    cp -a "$bak" "$file"
    RESULT["$label"]="FAIL — perl injection failed, reverted from bak: $file"
    (( N_FAILED++ )) || true
  fi
}

# ────────────────────────────────────────────────────────────────────────────
# inject_shared — wolfgordon-site/public/index.html
inject_shared() {
  local file="$SHARED_FILE"
  local label="wolfgordon-shared-template"

  if [ ! -f "$file" ]; then
    RESULT["$label"]="MISSING — file not found: $file"
    (( N_MANUAL++ )) || true; return
  fi

  if grep -qF "$ID" "$file"; then
    RESULT["$label"]="SKIP — already contains $ID"
    (( N_SKIPPED++ )) || true; return
  fi

  # Both anchors must be present
  if ! grep -qiE '</head>' "$file"; then
    RESULT["$label"]="NEEDS-MANUAL — no </head> anchor: $file"
    (( N_MANUAL++ )) || true; return
  fi
  if ! grep -qF 'applyConfig(cfg);' "$file"; then
    RESULT["$label"]="NEEDS-MANUAL — no 'applyConfig(cfg);' anchor in IIFE: $file"
    (( N_MANUAL++ )) || true; return
  fi

  local bak="${file}.bak-ga-${TS}"
  cp -a "$file" "$bak"
  BAK_MAP["$file"]="$bak"

  local head_snip
  head_snip="$(head_snippet_shared)"

  # 1. Insert loader + gtag('js') before </head>
  perl -0777 -i -pe "s{(</head>)}{${head_snip}\n\$1}i" "$file"

  # 2. Insert gtag('config', ID, {vendor:cfg.vendor}) immediately after applyConfig(cfg);
  #    (single-line match; the line has 6 leading spaces in the current file)
  perl -0777 -i -pe "s{(applyConfig\(cfg\);)(\s*\n)}{\$1\$2      gtag('config','${ID}',{vendor:cfg.vendor});\n}" "$file"

  if grep -qF "$ID" "$file"; then
    RESULT["$label"]="INJECTED — $file [30 pm2 processes]"
    (( N_INJECTED++ )) || true
    # Reload all 30 processes on this binary
    local reload_ok=0 reload_fail=0
    for pname in "${WOLF_PM2_NAMES[@]}"; do
      if pm2 reload "$pname" >/dev/null 2>&1; then
        (( reload_ok++ )) || true
      else
        (( reload_fail++ )) || true
      fi
    done
    RESULT["$label"]+=" | pm2 reload: ${reload_ok}/${#WOLF_PM2_NAMES[@]} OK"
    [ "$reload_fail" -gt 0 ] && RESULT["$label"]+=" (${reload_fail} failed — check pm2 log)"
  else
    cp -a "$bak" "$file"
    RESULT["$label"]="FAIL — injection failed, reverted: $file"
    (( N_FAILED++ )) || true
  fi
}

# ────────────────────────────────────────────────────────────────────────────
# inject_fentucci_old — /root/Projects/Designer-Wallcoverings/DW-Websites/Fentucci/public/index.html
# Strategy: the file has a commented-out placeholder:
#   <!-- <script async src="...G-XXXXXXXXXX"></script> -->
# Uncomment it and set the ID. Then also ensure the config call is present.
# If the file already has G-J2SL8P5KDL anywhere, skip.
inject_fentucci_old() {
  local file="$FENTUCCI_OLD_FILE"
  local label="fentucci-website-OLD-binary"

  if [ ! -f "$file" ]; then
    RESULT["$label"]="MISSING — file not found: $file"
    (( N_MANUAL++ )) || true; return
  fi

  if grep -qF "$ID" "$file"; then
    RESULT["$label"]="SKIP — already contains $ID"
    (( N_SKIPPED++ )) || true; return
  fi

  if ! grep -qiE '</head>' "$file"; then
    RESULT["$label"]="NEEDS-MANUAL — no </head> anchor: $file"
    (( N_MANUAL++ )) || true; return
  fi

  local bak="${file}.bak-ga-${TS}"
  cp -a "$file" "$bak"
  BAK_MAP["$file"]="$bak"

  # Try to uncomment the placeholder line and set ID
  # Pattern: <!-- <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script> -->
  local placeholder_found=0
  if grep -q 'GA4 placeholder\|gtag/js.*G-' "$file"; then
    # Uncomment the script tag and set the correct ID
    perl -i -pe 's{<!--\s*(<script async src="https://www\.googletagmanager\.com/gtag/js\?id=)[^"]*(">[^<]*</script>)\s*-->}{\1'"$ID"'\2}' "$file"
    placeholder_found=1
  fi

  # Whether or not we uncommented a placeholder, we need the full bootstrap in <head>.
  # Insert before </head> if the ID isn't present yet (the uncommented placeholder is only the loader tag).
  if ! grep -qF "$ID" "$file"; then
    # placeholder uncomment didn't land the ID — inject from scratch
    local snippet
    snippet="$(head_snippet_static "fentucci")"
    perl -0777 -i -pe "s{(</head>)}{${snippet}\n\$1}i" "$file"
  else
    # We got the loader via placeholder uncomment but still need the config call + init script.
    # Insert a config+init block right after the uncommented loader line.
    perl -i -pe 's{(<script async src="https://www\.googletagmanager\.com/gtag/js\?id='"$ID"'"></script>)}{$1\n<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('\''js'\'',new Date());gtag('\''config'\'','\'''"$ID"''\'',{vendor:'\''fentucci'\''});</script>}' "$file" || true
  fi

  if grep -qF "$ID" "$file"; then
    RESULT["$label"]="INJECTED — $file [pm2: $FENTUCCI_OLD_PM2]"
    (( N_INJECTED++ )) || true
    if pm2 reload "$FENTUCCI_OLD_PM2" >/dev/null 2>&1; then
      RESULT["$label"]+=" | pm2 reload: OK"
    else
      RESULT["$label"]+=" | pm2 reload: WARN"
    fi
  else
    cp -a "$bak" "$file"
    RESULT["$label"]="FAIL — injection failed, reverted: $file"
    (( N_FAILED++ )) || true
  fi
}

# ────────────────────────────────────────────────────────────────────────────
# EXECUTE

echo ""
echo "========================================================"
echo " GATE-2 GA4 INJECT  |  ID: $ID"
echo " Timestamp: $TS"
echo "========================================================"
echo ""

echo "[1/3] Shared wolfgordon template (30 vendors)..."
inject_shared

echo "[2/3] Standalone sites (16 files)..."
for entry in "${STANDALONE_RAW[@]}"; do
  IFS='|' read -r fpath vendor pm2name <<< "$entry"
  inject_standalone "$fpath" "$vendor" "$pm2name"
done

echo "[3/3] Fentucci-website old binary (placeholder uncomment)..."
inject_fentucci_old

# ────────────────────────────────────────────────────────────────────────────
# Write bak manifest so rollback can find all .bak files for this run
echo "" > /tmp/gate2-bak-manifest-${TS}.txt
for orig in "${!BAK_MAP[@]}"; do
  echo "${orig}|${BAK_MAP[$orig]}" >> /tmp/gate2-bak-manifest-${TS}.txt
done
echo "(bak manifest: /tmp/gate2-bak-manifest-${TS}.txt)"

# ────────────────────────────────────────────────────────────────────────────
# VERIFY — curl each site, grep for the ID in served HTML
echo ""
echo "========================================================"
echo " VERIFY"
echo "========================================================"

# Map vendor slug -> live URL for curl verification
declare -A VERIFY_URL
VERIFY_URL["artmura"]="http://127.0.0.1:$(pm2 jlist 2>/dev/null | python3 -c "import json,sys;d=json.load(sys.stdin);[print(p['pm2_env']['env'].get('PORT',3000)) for p in d if p['name']=='artmura-site']" 2>/dev/null | head -1 || echo 3000)"
# Simpler: pull port from pm2 env for each site
get_port() {
  local pm2name="$1"
  pm2 jlist 2>/dev/null | python3 -c "
import json,sys
try:
  d=json.load(sys.stdin)
  for p in d:
    if p.get('name')=='$pm2name':
      env=p.get('pm2_env',{}).get('env',{})
      port=env.get('PORT') or env.get('port') or 3000
      print(port)
      break
except:
  print(3000)
" 2>/dev/null | head -1
}

# Build verify list: "label|url"
VERIFY_LIST=()
for entry in "${STANDALONE_RAW[@]}"; do
  IFS='|' read -r fpath vendor pm2name <<< "$entry"
  port=$(get_port "$pm2name")
  VERIFY_LIST+=("$vendor|http://127.0.0.1:${port}/")
done
# wolfgordon shared: pick wolf-gordon-site process
wolf_port=$(get_port "wolf-gordon-site")
VERIFY_LIST+=("wolfgordon-shared|http://127.0.0.1:${wolf_port}/")
# fentucci-website old binary
fwo_port=$(get_port "$FENTUCCI_OLD_PM2")
VERIFY_LIST+=("fentucci-website-OLD|http://127.0.0.1:${fwo_port}/")

N_VERIFY_PASS=0; N_VERIFY_FAIL=0
for entry in "${VERIFY_LIST[@]}"; do
  IFS='|' read -r label url <<< "$entry"
  html=$(curl -sf --max-time 6 --retry 2 "$url" 2>/dev/null || true)
  if echo "$html" | grep -qF "$ID"; then
    printf "  VERIFY PASS   %-30s  %s\n" "$label" "$url"
    (( N_VERIFY_PASS++ )) || true
  else
    printf "  VERIFY FAIL   %-30s  %s\n" "$label" "$url"
    (( N_VERIFY_FAIL++ )) || true
  fi
done

# ────────────────────────────────────────────────────────────────────────────
echo ""
echo "========================================================"
echo " SUMMARY"
echo "========================================================"
for label in $(echo "${!RESULT[@]}" | tr ' ' '\n' | sort); do
  printf "  %-40s  %s\n" "$label" "${RESULT[$label]}"
done
echo ""
echo "  Injected : $N_INJECTED"
echo "  Skipped  : $N_SKIPPED"
echo "  Failed   : $N_FAILED"
echo "  Manual   : $N_MANUAL"
echo "  Verify   : ${N_VERIFY_PASS} PASS / ${N_VERIFY_FAIL} FAIL"
echo ""
echo "Rollback: ssh root@45.61.58.125 'bash -s' < ~/Projects/ga-allsites/gate2_rollback.sh"
echo "  (restores all .bak files matching *.bak-ga-${TS})"
echo "========================================================"
REMOTE