mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-18 18:29:35 +08:00
1d73b2986a
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>
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
/**
|
|
* migrate-v2 step: env
|
|
*
|
|
* Copy every key from v1 .env into v2 .env. Never overwrites existing v2
|
|
* keys. Idempotent — re-running skips keys already present.
|
|
*
|
|
* Usage: pnpm exec tsx setup/migrate-v2/env.ts <v1-path>
|
|
*/
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
function parseEnv(text: string): Map<string, string> {
|
|
const out = new Map<string, string>();
|
|
for (const raw of text.split('\n')) {
|
|
const line = raw.trimEnd();
|
|
if (!line || line.startsWith('#')) continue;
|
|
const eq = line.indexOf('=');
|
|
if (eq <= 0) continue;
|
|
const key = line.slice(0, eq).trim();
|
|
if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) continue;
|
|
out.set(key, line);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function main(): void {
|
|
const v1Path = process.argv[2];
|
|
if (!v1Path) {
|
|
console.error('Usage: tsx setup/migrate-v2/env.ts <v1-path>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const v1EnvPath = path.join(v1Path, '.env');
|
|
if (!fs.existsSync(v1EnvPath)) {
|
|
console.log('SKIPPED:no v1 .env');
|
|
process.exit(0);
|
|
}
|
|
|
|
const v2EnvPath = path.join(process.cwd(), '.env');
|
|
const v1Lines = parseEnv(fs.readFileSync(v1EnvPath, 'utf-8'));
|
|
const v2Text = fs.existsSync(v2EnvPath) ? fs.readFileSync(v2EnvPath, 'utf-8') : '';
|
|
const v2Lines = parseEnv(v2Text);
|
|
|
|
const copied: string[] = [];
|
|
const skipped: string[] = [];
|
|
const appended: string[] = [];
|
|
|
|
const BLOCK_START = '# ── migrated from v1 ──';
|
|
const alreadyMigrated = v2Text.includes(BLOCK_START);
|
|
|
|
for (const [key, raw] of v1Lines) {
|
|
if (v2Lines.has(key)) {
|
|
skipped.push(key);
|
|
continue;
|
|
}
|
|
copied.push(key);
|
|
appended.push(raw);
|
|
}
|
|
|
|
if (appended.length > 0) {
|
|
let result = v2Text;
|
|
if (result && !result.endsWith('\n')) result += '\n';
|
|
if (!alreadyMigrated) result += `\n${BLOCK_START}\n`;
|
|
result += appended.join('\n') + '\n';
|
|
fs.writeFileSync(v2EnvPath, result);
|
|
}
|
|
|
|
// Sync to data/env/env (container reads from here)
|
|
const containerEnvDir = path.join(process.cwd(), 'data', 'env');
|
|
try {
|
|
fs.mkdirSync(containerEnvDir, { recursive: true });
|
|
fs.copyFileSync(v2EnvPath, path.join(containerEnvDir, 'env'));
|
|
} catch {
|
|
// Non-fatal
|
|
}
|
|
|
|
console.log(`OK:copied=${copied.length},skipped=${skipped.length}`);
|
|
if (copied.length > 0) console.log(`COPIED:${copied.join(',')}`);
|
|
}
|
|
|
|
main();
|