← back to Dw Domain Fleet
Add fleet-heroes.sh driver: repeatable fleet-wide hero refresh + global dedupe (report-only default, --deploy gated, batched reload)
addb71d1f903b9160fe0f303798eaf402de3df55 · 2026-06-01 14:19:32 -0700 · Steve Abrams
Files touched
A scripts/fleet-heroes.sh
Diff
commit addb71d1f903b9160fe0f303798eaf402de3df55
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 1 14:19:32 2026 -0700
Add fleet-heroes.sh driver: repeatable fleet-wide hero refresh + global dedupe (report-only default, --deploy gated, batched reload)
---
scripts/fleet-heroes.sh | 121 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/scripts/fleet-heroes.sh b/scripts/fleet-heroes.sh
new file mode 100755
index 0000000..095eed2
--- /dev/null
+++ b/scripts/fleet-heroes.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+# fleet-heroes.sh — refresh + dedupe hero images across the whole DW web fleet.
+#
+# Two layers:
+# 1. GENERIC fleet (dw-domain-fleet, 43 rotating-hero sites) — driven by the
+# regenerable manifest data/hero-allocation.json.
+# 2. STANDALONE sister sites — static public/hero-bg.jpg files; duplicates get
+# replaced with unique, clean, niche-relevant catalog images.
+#
+# Usage:
+# scripts/fleet-heroes.sh # REPORT-ONLY (default): regen manifests,
+# # audit live fleet, print what WOULD change.
+# # Zero prod writes.
+# scripts/fleet-heroes.sh --deploy # push generic (BATCHED reload) + apply
+# # unique standalone heroes + verify + backsync
+# scripts/fleet-heroes.sh --pull-catalog # refresh data/catalog.json from live store first
+# # (combine with --deploy)
+#
+# Safety: NEVER mass-reloads pm2 (that OOM-killed the daemon once). dwf apps are
+# reloaded in batches of 6. All standalone hero replacements back up to
+# <file>.pre-uniquehero and validate JPEG/PNG >= 30KB before swapping.
+set -uo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+HOST="root@45.61.58.125"
+REMOTE="/root/public-projects/dw-domain-fleet"
+DEPLOY=0; PULL=0
+for a in "$@"; do
+ case "$a" in
+ --deploy) DEPLOY=1 ;;
+ --pull-catalog) PULL=1 ;;
+ -h|--help) sed -n '2,30p' "$0"; exit 0 ;;
+ *) echo "unknown arg: $a"; exit 2 ;;
+ esac
+done
+cd "$ROOT"
+mode=$([ "$DEPLOY" = 1 ] && echo "DEPLOY" || echo "REPORT-ONLY")
+echo "════════ fleet-heroes ($mode) ════════"
+
+# ---------- 0. optional catalog refresh ----------
+if [ "$PULL" = 1 ]; then
+ echo "==> pulling fresh catalog"; node scripts/pull-catalog.js || { echo "catalog pull failed"; exit 1; }
+fi
+
+# ---------- 1. GENERIC fleet manifest ----------
+echo "==> [generic] regenerating hero-allocation.json"
+node scripts/gen-hero-allocation.js || { echo "generic manifest FAILED (dup or error)"; exit 1; }
+
+if [ "$DEPLOY" = 1 ]; then
+ echo "==> [generic] pushing manifest + server.js to prod"
+ rsync -az server.js "$HOST:$REMOTE/server.js"
+ rsync -az data/hero-allocation.json "$HOST:$REMOTE/data/hero-allocation.json"
+ ssh "$HOST" "node -c $REMOTE/server.js" || { echo "remote syntax check failed"; exit 1; }
+ echo "==> [generic] BATCHED reload of dwf apps (6 at a time)"
+ ssh "$HOST" 'bash -s' <<'RB'
+names=$(pm2 jlist 2>/dev/null | grep -oE '"name":"dwf-[^"]+"' | sed 's/"name":"//;s/"//' | sort -u)
+arr=($names); batch=()
+for n in "${arr[@]}"; do batch+=("$n"); if [ ${#batch[@]} -ge 6 ]; then pm2 restart "${batch[@]}" >/dev/null 2>&1; sleep 3; batch=(); fi; done
+[ ${#batch[@]} -gt 0 ] && { pm2 restart "${batch[@]}" >/dev/null 2>&1; sleep 3; }
+echo " dwf online: $(pm2 list 2>/dev/null | grep -E 'dwf-' | grep -c online)"
+RB
+fi
+
+# ---------- 2. STANDALONE static heroes ----------
+if [ "$DEPLOY" = 1 ]; then
+ echo "==> [standalone] capturing current served hero-bg.jpg paths+hashes from prod"
+ ssh "$HOST" 'for f in $(find /var/www -maxdepth 3 -regextype posix-extended -regex "/var/www/[a-z0-9]+\.(com|org)/public/hero-bg.jpg" 2>/dev/null; find /root/Projects -maxdepth 3 -path "*/public/hero-bg.jpg" 2>/dev/null); do
+ d=$(basename "$(dirname "$(dirname "$f")")"); echo -e "$d\t$f\t$(md5sum "$f"|cut -d" " -f1)\t$(stat -c%s "$f")"; done | sort -u' > data/standalone-hero-paths.tsv
+ echo " captured $(wc -l < data/standalone-hero-paths.tsv) hero files"
+ echo "==> [standalone] regenerating allocation (disjoint from generic + each other)"
+ node scripts/gen-standalone-heroes.js || { echo "standalone alloc FAILED"; exit 1; }
+ # dup-cluster served domains only (skip already-unique + non-served repo twins)
+ node -e '
+ const fs=require("fs"),a=require("./data/standalone-hero-allocation.json");
+ const rows=fs.readFileSync("data/standalone-hero-paths.tsv","utf8").trim().split("\n").map(l=>l.split("\t"));
+ const byHash={}; rows.forEach(([d,p,h])=>{(byHash[h]=byHash[h]||[]).push(d)});
+ const dup=rows.filter(([d,p,h])=>byHash[h].length>1).map(([d])=>d);
+ const wwwSlugs=new Set(dup.filter(d=>a[d]&&a[d].path.startsWith("/var/www")).map(d=>d.replace(/\.(com|org|net)$/,"")));
+ const served=dup.filter(d=>{const w=a[d]&&a[d].path.startsWith("/var/www");const s=d.replace(/\.(com|org|net)$/,"");return w||!wwwSlugs.has(s);});
+ fs.writeFileSync("/tmp/fh_served.txt",served.join("\n"));
+ console.log(" duplicate served sites needing unique heroes:",served.length);
+ '
+ if [ -s /tmp/fh_served.txt ]; then
+ ssh "$HOST" "mkdir -p /root/_hero-fix"
+ scp -q data/standalone-hero-allocation.json "$HOST:/root/_hero-fix/alloc.json"
+ scp -q scripts/apply-standalone-heroes.js scripts/apply-retry.js "$HOST:/root/_hero-fix/"
+ scp -q /tmp/fh_served.txt "$HOST:/root/_hero-fix/served.txt"
+ echo "==> [standalone] applying unique heroes on prod (backup + validate >=30KB)"
+ ssh "$HOST" "cd /root/_hero-fix && node apply-standalone-heroes.js alloc.json served.txt 2>&1 | tail -4"
+ ssh "$HOST" "rm -rf /root/_hero-fix"
+ echo " NOTE: any tiny-thumbnail rejects need scripts/gen-retry-candidates.js + apply-retry.js (see skill)."
+ else
+ echo " no new standalone duplicates — nothing to apply"
+ fi
+else
+ echo "==> [standalone] (report-only) live duplicate audit"
+ ssh "$HOST" 'tmp=$(mktemp); served=$(find /var/www -maxdepth 3 -regextype posix-extended -regex "/var/www/[a-z0-9]+\.(com|org)/public/hero-bg.jpg" 2>/dev/null)
+ for f in /root/Projects/ffepurchasing/public/hero-bg.jpg /root/Projects/hospitalitywallcoverings/public/hero-bg.jpg /root/Projects/wallpapercanada/public/hero-bg.jpg; do [ -f "$f" ] && served="$served
+$f"; done
+ for f in $served; do [ -f "$f" ] && md5sum "$f"|cut -d" " -f1 >> "$tmp"; done
+ echo " standalone served: $(wc -l < "$tmp") files / $(sort -u "$tmp"|wc -l) unique / $(sort "$tmp"|uniq -d|wc -l) DUPLICATE clusters"; rm -f "$tmp"'
+fi
+
+# ---------- 3. VERIFY (always) ----------
+echo "==> verifying live fleet uniqueness"
+ssh "$HOST" 'bash -s' <<'VR'
+g=$(mktemp)
+for d in $(ls /etc/nginx/sites-enabled/ | grep "\.conf$" | sed "s/.conf//"); do
+ curl -skL --max-time 7 --resolve "$d:443:127.0.0.1" "https://$d/" | grep -oE "cinema-slide[^>]*background-image:url\('[^']+'\)" | grep -oE "https://[^']+" >> "$g" 2>/dev/null
+done
+echo " GENERIC live: $(wc -l < "$g") slides / $(sort -u "$g"|wc -l) unique / $(sort "$g"|uniq -d|wc -l) dup"
+s=$(mktemp); served=$(find /var/www -maxdepth 3 -regextype posix-extended -regex "/var/www/[a-z0-9]+\.(com|org)/public/hero-bg.jpg" 2>/dev/null)
+for f in /root/Projects/ffepurchasing/public/hero-bg.jpg /root/Projects/hospitalitywallcoverings/public/hero-bg.jpg /root/Projects/wallpapercanada/public/hero-bg.jpg; do [ -f "$f" ] && served="$served
+$f"; done
+for f in $served; do [ -f "$f" ] && md5sum "$f"|cut -d" " -f1 >> "$s"; done
+echo " STANDALONE live: $(wc -l < "$s") files / $(sort -u "$s"|wc -l) unique / $(sort "$s"|uniq -d|wc -l) dup"
+rm -f "$g" "$s"
+VR
+
+echo "════════ done ($mode) ════════"
+[ "$DEPLOY" = 0 ] && echo "Re-run with --deploy to push changes live. Then backsync prod→Mac2 + commit (see skill)."
← c069168 fix: app.set('trust proxy', 1) — resolves ERR_ERL_UNEXPECTED
·
back to Dw Domain Fleet
·
fleet-heroes: open DW fleet registry viewer (:9774) on launc 6c986ea →