[object Object]

← back to Cncp Mcp

run-on-mac1: use ssh-config alias 'mac1' + add homebrew PATH prep

1e24705bdf399ded32fbda2cfdd255d54da3e562 · 2026-05-11 08:40:55 -0700 · Steve Abrams

Files touched

Diff

commit 1e24705bdf399ded32fbda2cfdd255d54da3e562
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 11 08:40:55 2026 -0700

    run-on-mac1: use ssh-config alias 'mac1' + add homebrew PATH prep
---
 scripts/apply-pm2-caps.sh | 154 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 154 insertions(+)

diff --git a/scripts/apply-pm2-caps.sh b/scripts/apply-pm2-caps.sh
new file mode 100644
index 0000000..c98b8a2
--- /dev/null
+++ b/scripts/apply-pm2-caps.sh
@@ -0,0 +1,154 @@
+#!/usr/bin/env bash
+# apply-pm2-caps.sh — generate ecosystem.config.cjs + apply memory caps for any
+# pm2 proc that's currently uncapped. Per-project, idempotent, dry-run default.
+#
+# Why: Steve has 21+ pm2 procs without ecosystem.config files → no
+# max_memory_restart → any leaking proc drags Mac2 load to 500+.
+#
+# Usage:
+#   bash ~/Projects/cncp-mcp/scripts/apply-pm2-caps.sh              # dry-run (default)
+#   bash ~/Projects/cncp-mcp/scripts/apply-pm2-caps.sh --write      # write ecosystem.config.cjs files only
+#   bash ~/Projects/cncp-mcp/scripts/apply-pm2-caps.sh --cycle      # write + delete/restart each proc serially
+#
+# What it does:
+#   --write : creates ecosystem.config.cjs in each proc's cwd (only if missing)
+#             with the proc's current script + name + port env + cap. Doesn't restart.
+#   --cycle : --write, then for each proc: pm2 delete → pm2 start ecosystem.config.cjs
+#             with a 2s gap between procs to avoid load spike. pm2 save at end.
+#
+# Safety:
+#   - Never overwrites an existing ecosystem.config.{js,cjs}.
+#   - Default cap is 300M for sister-sites; can override via SISTER_CAP env.
+#   - Skips procs whose script path can't be resolved.
+#   - Logs every action to /tmp/pm2-caps-<ts>.log.
+
+set -u
+
+MODE="dry"
+for arg in "$@"; do
+  case "$arg" in
+    --write) MODE="write" ;;
+    --cycle) MODE="cycle" ;;
+    --dry-run) MODE="dry" ;;
+    *) echo "Unknown arg: $arg" >&2; exit 2 ;;
+  esac
+done
+
+SISTER_CAP="${SISTER_CAP:-300M}"
+LLM_CAP="${LLM_CAP:-1G}"
+PM2_BIN="${PM2_BIN:-$HOME/.npm-global/bin/pm2}"
+[[ -x "$PM2_BIN" ]] || PM2_BIN="$(/usr/bin/which pm2 2>/dev/null || echo pm2)"
+TS="$(date +%Y%m%d-%H%M%S)"
+LOG="/tmp/pm2-caps-${TS}.log"
+
+log() { echo "[$(date -u +%H:%M:%S)] $*" | tee -a "$LOG"; }
+
+log "mode=$MODE  sister-cap=$SISTER_CAP  llm-cap=$LLM_CAP"
+
+"$PM2_BIN" jlist 2>/dev/null > "/tmp/pm2-jlist-${TS}.json" || {
+  log "ERROR: pm2 jlist failed"
+  exit 3
+}
+
+/usr/bin/python3 <<PYEOF
+import json, os, sys, subprocess, time
+
+MODE = "$MODE"
+SISTER_CAP = "$SISTER_CAP"
+LLM_CAP = "$LLM_CAP"
+LOG = "$LOG"
+PM2 = "$PM2_BIN"
+
+def logmsg(s):
+    line = f"[{time.strftime('%H:%M:%S', time.gmtime())}] {s}"
+    print(line)
+    with open(LOG, 'a') as f: f.write(line + "\n")
+
+procs = json.load(open("/tmp/pm2-jlist-${TS}.json"))
+
+# Identify candidates: no max_memory_restart, has a valid pm_cwd + pm_exec_path
+todo = []
+for p in procs:
+    env = p.get('pm2_env', {})
+    name = p.get('name', '?')
+    if env.get('max_memory_restart'): continue
+    pm_cwd = env.get('pm_cwd', '') or ''
+    pm_exec_path = env.get('pm_exec_path', '') or ''
+    if not pm_cwd or not pm_exec_path:
+        logmsg(f"SKIP {name}: missing cwd or script path")
+        continue
+    if not os.path.isfile(pm_exec_path):
+        logmsg(f"SKIP {name}: script path doesn't exist: {pm_exec_path}")
+        continue
+    # Try to extract PORT from env
+    port = env.get('PORT') or env.get('port') or ''
+    # Decide cap: LLM agents get more, sister-sites less
+    name_low = name.lower()
+    is_llm = any(k in name_low for k in ('agent', 'gpt', 'llm', 'codex', 'claude', 'orchestrator', 'factory', 'debrief'))
+    cap = LLM_CAP if is_llm else SISTER_CAP
+    todo.append({'name': name, 'cwd': pm_cwd, 'script': pm_exec_path, 'port': port, 'cap': cap})
+
+logmsg(f"candidates: {len(todo)}")
+for t in todo:
+    logmsg(f"  {t['name']:<32} cwd={t['cwd']} script={os.path.basename(t['script'])} cap={t['cap']}")
+
+if MODE == "dry":
+    logmsg("DRY-RUN — no files written, no pm2 changes. Re-run with --write or --cycle.")
+    sys.exit(0)
+
+# --write : create ecosystem.config.cjs in each cwd if missing
+created = 0
+already = 0
+for t in todo:
+    eco_cjs = os.path.join(t['cwd'], 'ecosystem.config.cjs')
+    eco_js = os.path.join(t['cwd'], 'ecosystem.config.js')
+    if os.path.isfile(eco_cjs) or os.path.isfile(eco_js):
+        logmsg(f"already-has-ecosystem {t['name']}")
+        already += 1
+        continue
+    # Build the script body
+    script_basename = os.path.basename(t['script'])
+    env_block = f"      PORT: {t['port']},\n" if t['port'] else ""
+    content = f"""module.exports = {{
+  apps: [{{
+    name: "{t['name']}",
+    script: "{script_basename}",
+    cwd: __dirname,
+    env: {{
+{env_block}    }},
+    autorestart: true,
+    max_memory_restart: "{t['cap']}",
+  }}],
+}};
+"""
+    try:
+        with open(eco_cjs, 'w') as f: f.write(content)
+        logmsg(f"WROTE {eco_cjs}")
+        created += 1
+    except Exception as e:
+        logmsg(f"WRITE-FAIL {t['name']}: {e}")
+
+logmsg(f"--write done: {created} created, {already} already had one.")
+
+if MODE == "cycle":
+    logmsg("--cycle: delete + restart each, 2s gap")
+    for t in todo:
+        eco = os.path.join(t['cwd'], 'ecosystem.config.cjs')
+        if not os.path.isfile(eco):
+            logmsg(f"  skip {t['name']}: no ecosystem.config.cjs")
+            continue
+        logmsg(f"  cycle {t['name']}")
+        subprocess.run([PM2, "delete", t['name']], capture_output=True)
+        r = subprocess.run([PM2, "start", eco], capture_output=True, text=True)
+        if r.returncode != 0:
+            logmsg(f"    START FAIL: {r.stderr[:200]}")
+        else:
+            logmsg(f"    started")
+        time.sleep(2)
+    logmsg("running pm2 save")
+    subprocess.run([PM2, "save"], capture_output=True)
+    logmsg("--cycle done.")
+PYEOF
+
+echo ""
+echo "Log: $LOG"

← 0239c2e load-spreading: run-on-mac1 wrapper + pm2-cap audit script  ·  back to Cncp Mcp  ·  load-aware dispatch: run-on-mac1 routes Mac2↔Mac1 by load + e6e3182 →