← back to Tools Dw Hub
Add start-fleet-persistent.sh: full Mac2 tool set reboot-persistent, secrets never in dump.pm2
a0205190d0f16e69f406bb34d790414958176c4b · 2026-07-30 18:08:11 -0700 · steve
Each tool self-sources secrets-manager/.env at RUNTIME in its bash child, so pm2's dump.pm2
holds only the command, never token VALUES (verified 0, was 26). Supersedes start-fleet.sh's
export-then-save leak. Boot hook com.steve.pm2-resurrect resurrects from dump.pm2 -> reboot-safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A start-fleet-persistent.sh
Diff
commit a0205190d0f16e69f406bb34d790414958176c4b
Author: steve <steve@designerwallcoverings.com>
Date: Thu Jul 30 18:08:11 2026 -0700
Add start-fleet-persistent.sh: full Mac2 tool set reboot-persistent, secrets never in dump.pm2
Each tool self-sources secrets-manager/.env at RUNTIME in its bash child, so pm2's dump.pm2
holds only the command, never token VALUES (verified 0, was 26). Supersedes start-fleet.sh's
export-then-save leak. Boot hook com.steve.pm2-resurrect resurrects from dump.pm2 -> reboot-safe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
start-fleet-persistent.sh | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/start-fleet-persistent.sh b/start-fleet-persistent.sh
new file mode 100755
index 0000000..12b35a8
--- /dev/null
+++ b/start-fleet-persistent.sh
@@ -0,0 +1,40 @@
+#!/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)
+SLUGS=()
+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); }')
+
+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 ADMIN_PASSWORD='DW2024!' DW_BASIC_AUTH='admin:DW2024!' 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: /'
← b514256 mood-board-studio: fix mobile horizontal-scroll (public-read
·
back to Tools Dw Hub
·
start-fleet-persistent.sh: accept optional slug args to (re) 0a30fa2 →