← back to Tools Dw Hub

start-fleet-persistent.sh

45 lines

#!/usr/bin/env bash
# start-fleet-persistent.sh — bring the FULL Mac2 tool set up under pm2, reboot-persistent,
# WITHOUT leaking secrets into ~/.pm2/dump.pm2.
#
# THE SAFE PATTERN: each tool self-sources secrets-manager/.env at RUNTIME *inside its own
# bash child*, so pm2's saved env holds only the command string (which references the .env
# PATH), never the secret VALUES. Verified: a self-sourced proc's dump.pm2 env has no
# SHOPIFY_ADMIN_TOKEN / GEMINI_API_KEY, yet the running process has them at runtime.
#
# Supersedes start-fleet.sh's `export SECRET=…; pm2 start; pm2 save` pattern, which baked
# plaintext tokens into ~/.pm2/dump.pm2 (a live leak during the 2026-07-29 breach). TK-10070.
#
# This script never reads a secret value into its own shell — the child does the sourcing.
set -u
cd "$(dirname "$0")"
SECRETS="$HOME/Projects/secrets-manager/.env"

# Mac2 startable set = has port + start command + not CLI/missing-source + start is not a
# remote ssh (scraper-api runs on Kamatera, not here).
# (bash 3.2 compatible — no mapfile). Optional args = only start those slugs.
SLUGS=()
if [ "$#" -gt 0 ]; then
  SLUGS=("$@")
else
  while IFS= read -r s; do [ -n "$s" ] && SLUGS+=("$s"); done < <(node -e '
const t=JSON.parse(require("fs").readFileSync("tools.json","utf8"));
for(const x of t.tools){ if(x.port && x.start && !x.missingSource && !x.cli && !/^\s*ssh\b/.test(x.start)) console.log(x.slug); }')
fi

echo "Mac2 startable tools: ${#SLUGS[@]}"
for slug in "${SLUGS[@]}"; do
  START=$(node -e "process.stdout.write((JSON.parse(require('fs').readFileSync('tools.json','utf8')).tools.find(x=>x.slug==='$slug')||{}).start||'')")
  PORT=$(node -e "const t=JSON.parse(require('fs').readFileSync('tools.json','utf8')).tools.find(x=>x.slug==='$slug');process.stdout.write(t&&t.port?String(t.port):'')")
  [ -z "$START" ] && { echo "SKIP $slug (no start)"; continue; }
  NAME="dwtool-$slug"
  # child self-sources ALL secrets at runtime (matches the hub's own loadSecretsEnv), sets
  # DRY_RUN=1 so taggers/updaters never push, then runs the tool's own start command.
  WRAP="set -a; [ -f '$SECRETS' ] && . '$SECRETS'; set +a; export DRY_RUN=1 VISION_SCAN_CAP=25 NEXTAUTH_URL='http://localhost:$PORT'; $START"
  pm2 delete "$NAME" >/dev/null 2>&1
  if pm2 start bash --name "$NAME" -- -lc "$WRAP" >/dev/null 2>&1; then echo "up   $NAME (:$PORT)"; else echo "FAIL $NAME"; fi
done

pm2 save >/dev/null 2>&1 && echo "pm2 save done"
echo "--- roster ---"; pm2 ls | grep dwtool- | wc -l | sed 's/^/dwtool procs: /'