← back to Pm2 Migration
README.md
131 lines
# PM2 Migration Kit — Kamatera → Local
**Rule:** Kamatera is for outward-facing apps. Internal automation runs on Steve's Mac.
## Files
- `processes.json` — full classification (`keep_kamatera`, `move_local`, `investigate`, `delete_dead`, `high_priority`)
- `migrate.sh` — driver script (per-process or batch, dry-run by default)
- `append-ecosystem.js` — adds each migrated app to `ecosystem.local.cjs`
- `ecosystem.local.cjs` — generated PM2 config that runs locally
- `cleanup-stopped.sh` — drops already-dead processes (`hollywood-style-tagger`)
- `state/` — captured PM2 metadata for each migrated process (used for rollback)
## Prereqs
1. **SSH to Kamatera** working as `root@45.61.58.125` with key auth (no password prompt). Test:
```bash
ssh root@45.61.58.125 'pm2 -v'
```
2. **PM2 installed locally**: `npm i -g pm2 && pm2 startup`.
3. **`jq`** locally and on Kamatera: `brew install jq`.
4. **rsync** (preinstalled on macOS).
If SSH lives under a different alias, set:
```bash
export KAMATERA_USER=root KAMATERA_HOST=kamatera # or whatever your ~/.ssh/config calls it
```
## Run order
```bash
cd /Users/stevestudio2/Projects/pm2-migration
# 1. Reality check — what would happen for the whole bucket, no changes
./migrate.sh --dry-run --batch move_local
# 2. Pilot one (low-risk pick — george-gmail is already partially local)
./migrate.sh george-gmail
# 3. After 30s smoke test passes, leave it overnight, then finalize
./migrate.sh --finalize george-gmail
# 4. Batch the rest
./migrate.sh --batch move_local
# 5. After 24h, finalize each
for n in $(jq -r '.buckets.move_local.processes[]' processes.json); do
./migrate.sh --finalize "$n"
done
# 6. Drop the dead
./cleanup-stopped.sh
```
## How hardcoded `/root/*` paths are handled (the shim)
Most DW-Agents have hardcoded Kamatera paths like `/root/Projects/Designer-Wallcoverings/DW-MCP/.env` and `/root/DW-Agents/logs/...`. Rather than patching every agent, every migrated agent runs with `node --require root-shim.js`, which monkey-patches `fs.readFileSync` (and ~30 sibling fs methods) to rewrite any `/root/...` path to `~/kamatera-mirror/...`.
**The mirror layout** (`~/kamatera-mirror/`):
```
~/kamatera-mirror/
Projects/
Designer-Wallcoverings/
DW-MCP/
.env ← rsynced from Kamatera (creds; gitignored)
.env.example
DW-Agents/
logs/ ← agent log writes land here
shared/
```
Synced once via:
```bash
rsync -avz --include='.env*' --exclude='*' \
root@45.61.58.125:/root/Projects/Designer-Wallcoverings/DW-MCP/ \
~/kamatera-mirror/Projects/Designer-Wallcoverings/DW-MCP/
```
**Verified working** for `george-gmail` — boots all 4 Workspace accounts, no code changes:
```
[root-shim] /root/* → /Users/stevestudio2/kamatera-mirror/*
[George] Loaded credentials from /root/Projects/Designer-Wallcoverings/DW-MCP/.env
[George] Gmail + Tasks API client initialized (steve@)
[George] Info@ account initialized (full Workspace)
[George] Steve Office account upgraded to full Workspace
[George] Steve Personal account initialized (full Workspace)
```
**Limits of the shim**
- Only intercepts Node `fs` calls. Python agents and child-process invocations (`spawn('cat', ['/root/...'])`) are not covered. For Python agents, set `KAMATERA_MIRROR` env and patch the agent.
- Doesn't rewrite hardcoded URLs printed to logs (e.g., `Dashboard: http://45.61.58.125:9850/`) — cosmetic only.
- Doesn't refresh OAuth tokens. Tokens live inside `DW-MCP/.env`, so the rsync covers them. If an agent stores tokens elsewhere, sync those dirs too.
**Adding new mirror paths**
If a migrated agent fails reading `/root/<some/other/path>`, just rsync that path under `~/kamatera-mirror/<some/other/path>` and restart the agent. No code changes.
## Per-process flow (what migrate.sh does)
1. `pm2 jlist` over SSH → grab the process's `cwd`, `script`, `env`, `args` → save to `state/<name>.json`.
2. `rsync` the project directory from Kamatera to `~/Projects/<name>/` (skips `node_modules`, `.git`, `logs`).
3. `npm install` locally if a `package.json` is present.
4. Append/update an entry for `<name>` in `ecosystem.local.cjs`.
5. **Stop** (don't delete) on Kamatera and `pm2 save`.
6. **Start** locally via `pm2 start ecosystem.local.cjs --only <name>` and `pm2 save`.
7. 30-second smoke test — if `restart_time` > 3, automatically roll back (start on Kamatera, stop locally).
8. Print a finalize hint.
## Rollback
If something misbehaves on local after the smoke test passes:
```bash
./migrate.sh --rollback <name>
```
Restarts the original on Kamatera and stops the local copy. The Kamatera process was only stopped (not deleted) until you ran `--finalize`, so this is safe up to that point.
## Investigate bucket
These four need a 30-second decision before moving — see `processes.json → buckets.investigate`. After deciding, just call `./migrate.sh <name>` (or leave them on Kamatera).
## High-priority crash-loopers
`momentum-crawler` (879 restarts), `color-search` (18), `george-gmail` (14). Moving them local is also the debug session — once local you can `pm2 logs <name>` without SSH lag.
## Why not auto-batch all 40 right now?
Each migration is reversible-with-effort but not free: rsync size, `npm install` time, and a 30-second smoke test per process means ~5 min × 40 = 3 hours wall-clock if everything works. Pilot one, sleep on it, batch the rest in the morning.
## What's NOT touched
Customer-facing sites in `buckets.keep_kamatera` are explicitly skipped. The script will refuse if you try (TODO: add the guard if needed).