feat: add migrate-v2.sh — standalone v1 → v2 migration script
New entry point: `bash migrate-v2.sh` from the v2 checkout. Replaces the old setup-embedded migration flow with a standalone 4-phase script + rewritten Claude skill for the interactive parts. Phase 0: Bootstrap (Node/pnpm/deps via setup.sh) + find v1 Phase 1: Core state (env, DB, groups, sessions, tasks) Phase 2: Channels (clack multiselect, auth copy, code install) Phase 3: Infrastructure (OneCLI, auth, Docker, skills, container build) Service switchover: stop v1 → start v2 → test → keep or revert Phase 4: Handoff → exec claude "/migrate-from-v1" The skill handles: owner seeding, access policy, CLAUDE.local.md cleanup, container config validation, fork customization porting. Key fixes found during testing: - triggerToEngage: requires_trigger=0 must override non-empty pattern - unknown_sender_policy defaults to 'public' (strict drops all msgs before owner is seeded) - Service revert must stop v2 (parse unit name from step log, not early tsx one-liner that can fail) - Session continuity: copy JSONL from -workspace-group/ to -workspace-agent/ and write continuation:claude into outbound.db - container_config.additionalMounts written directly to container.json (same shape in v1 and v2) - EXIT trap writes handoff.json; explicit write_handoff before exec Includes migrate-v2-reset.sh for dev iteration and docs/migration-dev.md for testing/debugging reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
139
docs/migration-dev.md
Normal file
139
docs/migration-dev.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# v1 → v2 Migration — Development Guide
|
||||
|
||||
How to test, develop, and debug the migration flow.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# Full cycle: reset → migrate → Claude finishes
|
||||
bash migrate-v2-reset.sh && bash migrate-v2.sh
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Two-part migration:
|
||||
|
||||
1. **`migrate-v2.sh`** — deterministic bash script. Handles prerequisites, DB seeding, file copies, channel install, container build, service switchover. Writes `logs/setup-migration/handoff.json` then `exec`s into Claude.
|
||||
|
||||
2. **`/migrate-from-v1` skill** — Claude-driven. Reads the handoff, seeds owner/roles, cleans up CLAUDE.local.md, validates container configs, ports fork customizations.
|
||||
|
||||
## File layout
|
||||
|
||||
```
|
||||
migrate-v2.sh # Entry point
|
||||
migrate-v2-reset.sh # Wipe v2 state for re-testing
|
||||
setup/migrate-v2/
|
||||
env.ts # Phase 1a: merge .env
|
||||
db.ts # Phase 1b: seed v2 DB
|
||||
groups.ts # Phase 1c: copy group folders + container.json
|
||||
sessions.ts # Phase 1d: copy sessions + set continuation
|
||||
tasks.ts # Phase 1e: port scheduled tasks
|
||||
channel-auth.ts # Phase 2b: copy channel auth state
|
||||
select-channels.ts # Phase 2a: clack multiselect
|
||||
switchover-prompt.ts # Service switch prompts
|
||||
setup/migrate-v1/shared.ts # Shared helpers (JID parsing, trigger mapping, etc.)
|
||||
.claude/skills/migrate-from-v1/ # The Claude skill
|
||||
logs/setup-migration/handoff.json # Written by migrate-v2.sh, read by skill
|
||||
logs/migrate-steps/*.log # Per-step raw output
|
||||
```
|
||||
|
||||
## Development loop
|
||||
|
||||
```bash
|
||||
# Reset v2 to clean state (keeps node_modules)
|
||||
bash migrate-v2-reset.sh
|
||||
|
||||
# Run migration with non-interactive channel selection
|
||||
NANOCLAW_CHANNELS="telegram" bash migrate-v2.sh
|
||||
|
||||
# Or run interactively (clack multiselect)
|
||||
bash migrate-v2.sh
|
||||
```
|
||||
|
||||
`migrate-v2-reset.sh` wipes: `data/`, `logs/`, `.env`, `groups/` (restores git-tracked), `container/skills/` (restores git-tracked), `src/channels/` (restores git-tracked).
|
||||
|
||||
It does NOT wipe `node_modules/` (expensive to reinstall).
|
||||
|
||||
## Testing individual steps
|
||||
|
||||
Each step is a standalone TypeScript file:
|
||||
|
||||
```bash
|
||||
# Run a single step (after pnpm install)
|
||||
pnpm exec tsx setup/migrate-v2/env.ts /path/to/v1
|
||||
pnpm exec tsx setup/migrate-v2/db.ts /path/to/v1
|
||||
pnpm exec tsx setup/migrate-v2/groups.ts /path/to/v1
|
||||
pnpm exec tsx setup/migrate-v2/sessions.ts /path/to/v1
|
||||
pnpm exec tsx setup/migrate-v2/tasks.ts /path/to/v1
|
||||
pnpm exec tsx setup/migrate-v2/channel-auth.ts /path/to/v1 telegram discord
|
||||
```
|
||||
|
||||
Each prints `OK:<details>`, `SKIPPED:<reason>`, or errors to stdout. Exit 0 on success/skip, non-zero on failure.
|
||||
|
||||
## Debugging
|
||||
|
||||
### Check what was migrated
|
||||
|
||||
```bash
|
||||
# Agent groups
|
||||
sqlite3 data/v2.db "SELECT * FROM agent_groups"
|
||||
|
||||
# Messaging groups + wiring
|
||||
sqlite3 data/v2.db "SELECT mg.id, mg.channel_type, mg.platform_id, mg.unknown_sender_policy, mga.engage_mode, mga.engage_pattern FROM messaging_groups mg JOIN messaging_group_agents mga ON mga.messaging_group_id = mg.id"
|
||||
|
||||
# Sessions
|
||||
sqlite3 data/v2.db "SELECT * FROM sessions"
|
||||
|
||||
# Users and roles
|
||||
sqlite3 data/v2.db "SELECT * FROM users"
|
||||
sqlite3 data/v2.db "SELECT * FROM user_roles"
|
||||
|
||||
# Session continuation (which Claude Code session will be resumed)
|
||||
AG_ID=$(sqlite3 data/v2.db "SELECT id FROM agent_groups LIMIT 1")
|
||||
SESS_ID=$(sqlite3 data/v2.db "SELECT id FROM sessions LIMIT 1")
|
||||
sqlite3 data/v2-sessions/$AG_ID/$SESS_ID/outbound.db "SELECT * FROM session_state"
|
||||
|
||||
# Scheduled tasks
|
||||
sqlite3 data/v2-sessions/$AG_ID/$SESS_ID/inbound.db "SELECT id, kind, recurrence, status FROM messages_in WHERE kind='task'"
|
||||
```
|
||||
|
||||
### Check handoff
|
||||
|
||||
```bash
|
||||
python3 -m json.tool logs/setup-migration/handoff.json
|
||||
```
|
||||
|
||||
### Common issues
|
||||
|
||||
**Bot doesn't respond after switchover:**
|
||||
1. Check both services aren't running: `systemctl --user list-units 'nanoclaw*'`
|
||||
2. Check error log: `tail logs/nanoclaw.error.log`
|
||||
3. Check sender policy: `sqlite3 data/v2.db "SELECT unknown_sender_policy FROM messaging_groups"` — must be `public` before owner is seeded
|
||||
4. Check engage pattern: `sqlite3 data/v2.db "SELECT engage_mode, engage_pattern FROM messaging_group_agents"` — should be `pattern` / `.` for respond-to-everything
|
||||
|
||||
**Session not continuing from v1:**
|
||||
1. Check continuation is set: see "Session continuation" query above
|
||||
2. Check JSONL exists at the right path: `ls data/v2-sessions/<ag_id>/.claude-shared/projects/-workspace-agent/`
|
||||
3. The v1 session JSONL should be copied from `-workspace-group/` to `-workspace-agent/` (v2 container CWD is `/workspace/agent`)
|
||||
|
||||
**Service switchover revert didn't work:**
|
||||
1. The v2 service name is `nanoclaw-v2-<hash>` — find it: `systemctl --user list-units 'nanoclaw*'`
|
||||
2. Manually stop: `systemctl --user stop <unit> && systemctl --user disable <unit>`
|
||||
3. Restart v1: `systemctl --user start nanoclaw`
|
||||
|
||||
### Step logs
|
||||
|
||||
Each step writes raw output to `logs/migrate-steps/<step>.log`. Read these when a step fails:
|
||||
|
||||
```bash
|
||||
cat logs/migrate-steps/1b-db.log
|
||||
cat logs/migrate-steps/1d-sessions.log
|
||||
```
|
||||
|
||||
## Key decisions
|
||||
|
||||
- `unknown_sender_policy` is set to `public` during migration so the bot responds immediately. The `/migrate-from-v1` skill tightens it after seeding the owner.
|
||||
- `requires_trigger=0` in v1 takes priority over a non-empty `trigger_pattern` — it means "respond to everything."
|
||||
- v1 `container_config.additionalMounts` is written directly to v2 `container.json` (same shape).
|
||||
- v1 Claude Code sessions are copied from `-workspace-group/` to `-workspace-agent/` and the session ID is written to `outbound.db` as `continuation:claude` so the agent-runner resumes the same conversation.
|
||||
- `exec claude "/migrate-from-v1"` at the end replaces the bash process — `write_handoff` is called explicitly before `exec` since EXIT traps don't fire on `exec`.
|
||||
Reference in New Issue
Block a user