← back to Cncp Mcp
load-spreading: run-on-mac1 wrapper + pm2-cap audit script
0239c2e2b8f9c9ddf336c49481e7e791fc228a23 · 2026-05-11 08:34:01 -0700 · Steve Abrams
Files touched
A scripts/audit-pm2-caps.sh
Diff
commit 0239c2e2b8f9c9ddf336c49481e7e791fc228a23
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 11 08:34:01 2026 -0700
load-spreading: run-on-mac1 wrapper + pm2-cap audit script
---
scripts/audit-pm2-caps.sh | 68 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/scripts/audit-pm2-caps.sh b/scripts/audit-pm2-caps.sh
new file mode 100755
index 0000000..b896cf9
--- /dev/null
+++ b/scripts/audit-pm2-caps.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+# audit-pm2-caps.sh — read-only audit of pm2 procs missing memory/CPU caps.
+#
+# Soft-caps are Steve's protection against a single leaking pm2 proc dragging
+# load to 500+. Each process SHOULD have `max_memory_restart` set in its
+# ecosystem.config.{js,cjs}. This script finds the gaps and prints a punch list.
+#
+# Usage:
+# bash ~/Projects/cncp-mcp/scripts/audit-pm2-caps.sh
+#
+# Output: one line per cap-less proc with the candidate ecosystem file path.
+
+set -u
+
+PM2_BIN="${PM2_BIN:-$HOME/.npm-global/bin/pm2}"
+[[ -x "$PM2_BIN" ]] || PM2_BIN="$(/usr/bin/which pm2 2>/dev/null || echo pm2)"
+
+"$PM2_BIN" jlist 2>/dev/null > /tmp/audit-pm2-caps.json
+/usr/bin/python3 <<'PY'
+import json, os
+try:
+ procs = json.load(open('/tmp/audit-pm2-caps.json'))
+except Exception as e:
+ print(f"pm2 jlist parse failed: {e}")
+ raise SystemExit(1)
+
+uncapped = []
+capped = []
+for p in procs:
+ env = p.get('pm2_env', {})
+ name = p.get('name', '?')
+ max_mem = env.get('max_memory_restart')
+ pm_exec_path = env.get('pm_exec_path', '')
+ pm_cwd = env.get('pm_cwd', '')
+ # Find candidate ecosystem file
+ eco = None
+ for candidate in ('ecosystem.config.js', 'ecosystem.config.cjs'):
+ path = os.path.join(pm_cwd, candidate) if pm_cwd else ''
+ if path and os.path.isfile(path):
+ eco = path
+ break
+ row = (name, max_mem, eco or '(no ecosystem.config.{js,cjs} found)')
+ if max_mem:
+ capped.append(row)
+ else:
+ uncapped.append(row)
+
+print(f"=== capped procs: {len(capped)} ===")
+for n, m, e in capped[:10]:
+ print(f" ✓ {n:<32} max_memory_restart={m}")
+if len(capped) > 10:
+ print(f" ... and {len(capped)-10} more")
+
+print()
+print(f"=== UNCAPPED procs (no max_memory_restart): {len(uncapped)} ===")
+for n, m, e in uncapped:
+ print(f" ✗ {n:<32} → {e}")
+
+print()
+print("Fix pattern in each ecosystem.config.{js,cjs}:")
+print(" module.exports = { apps: [{ name: '...', script: '...',")
+print(" max_memory_restart: '500M', // sister-sites: try 300M; LLM agents: 1G")
+print(" cron_restart: '0 4 * * *', // optional nightly bounce at 4am")
+print(" }] };")
+print()
+print("After editing each ecosystem file:")
+print(" pm2 reload <name> --update-env")
+PY
← f7b88e5 add 5-min cncp-refresh + 1-hr GoDaddy --full sync (update-on
·
back to Cncp Mcp
·
run-on-mac1: use ssh-config alias 'mac1' + add homebrew PATH 1e24705 →