this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(infra): add data migration script

Migrates local development data to production:
- SQLite database (assistant.db)
- Letta agent state (exports to .af file and imports on server)

Usage: ./infra/migrate-data.sh

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice 62cb78f1 55c9a9ad

+224
+3
.gitignore
··· 49 49 50 50 # Environment backups (created by deploy.sh) 51 51 .env.backup.* 52 + 53 + # Agent export files (created by migrate-data.sh) 54 + infra/agent_export.af
+221
infra/migrate-data.sh
··· 1 + #!/usr/bin/env bash 2 + # Migrate local data to production server 3 + # 4 + # Syncs: 5 + # - SQLite database (assistant.db) 6 + # - Letta agent state (.af file) 7 + # 8 + # Usage: 9 + # ./migrate-data.sh 10 + 11 + set -euo pipefail 12 + IFS=$'\n\t' 13 + 14 + # Colors 15 + GREEN='\033[0;32m' 16 + BLUE='\033[0;34m' 17 + RED='\033[0;31m' 18 + YELLOW='\033[0;33m' 19 + NC='\033[0m' 20 + 21 + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 22 + PROJECT_DIR="$(dirname "$SCRIPT_DIR")" 23 + SERVER_HOST="assistant-vps" 24 + 25 + log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } 26 + log_success() { echo -e "${GREEN}[OK]${NC} $1"; } 27 + log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } 28 + log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } 29 + die() { log_error "$1"; exit 1; } 30 + 31 + ####################################### 32 + # Sync SQLite database 33 + ####################################### 34 + sync_sqlite() { 35 + log_info "Syncing SQLite database..." 36 + 37 + local db_file="$PROJECT_DIR/data/assistant.db" 38 + 39 + if [[ ! -f "$db_file" ]]; then 40 + log_warn "No local database found: $db_file" 41 + return 42 + fi 43 + 44 + # Checkpoint WAL to ensure all data is in main db file 45 + if command -v sqlite3 &> /dev/null; then 46 + sqlite3 "$db_file" "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || true 47 + fi 48 + 49 + # Copy to server 50 + scp "$db_file" "$SERVER_HOST:/opt/assistant/data/assistant.db" 51 + ssh "$SERVER_HOST" "chown 1000:1000 /opt/assistant/data/assistant.db" 52 + 53 + log_success "SQLite database synced" 54 + } 55 + 56 + ####################################### 57 + # Export local Letta agent 58 + ####################################### 59 + export_letta_agent() { 60 + log_info "Exporting local Letta agent..." 61 + 62 + local export_file="$SCRIPT_DIR/agent_export.af" 63 + 64 + # Run TypeScript export script 65 + cd "$PROJECT_DIR" 66 + bun run --silent - << 'TYPESCRIPT' 67 + import { Letta } from '@letta-ai/letta-client'; 68 + 69 + const AGENT_NAME = 'adhd-support-agent'; 70 + 71 + async function main() { 72 + // Connect to local Letta (dev mode) 73 + const client = new Letta({ 74 + baseURL: process.env.LETTA_BASE_URL || 'http://localhost:8283', 75 + }); 76 + 77 + // Find agent by name 78 + let agentId: string | null = null; 79 + for await (const agent of client.agents.list()) { 80 + if (agent.name === AGENT_NAME) { 81 + agentId = agent.id; 82 + break; 83 + } 84 + } 85 + 86 + if (!agentId) { 87 + console.error(`Agent '${AGENT_NAME}' not found locally`); 88 + process.exit(1); 89 + } 90 + 91 + console.log(`Found agent: ${agentId}`); 92 + 93 + // Export agent to .af file 94 + const exported = await client.agents.exportAgentSerialized(agentId); 95 + 96 + // Write to file 97 + const fs = await import('fs'); 98 + fs.writeFileSync('infra/agent_export.af', Buffer.from(exported)); 99 + 100 + console.log('Agent exported to infra/agent_export.af'); 101 + } 102 + 103 + main().catch(err => { 104 + console.error('Export failed:', err); 105 + process.exit(1); 106 + }); 107 + TYPESCRIPT 108 + 109 + if [[ ! -f "$export_file" ]]; then 110 + log_warn "Agent export failed or no local agent exists" 111 + return 1 112 + fi 113 + 114 + log_success "Agent exported to $export_file" 115 + return 0 116 + } 117 + 118 + ####################################### 119 + # Import Letta agent to production 120 + ####################################### 121 + import_letta_agent() { 122 + log_info "Importing agent to production Letta..." 123 + 124 + local export_file="$SCRIPT_DIR/agent_export.af" 125 + 126 + if [[ ! -f "$export_file" ]]; then 127 + log_warn "No agent export file found: $export_file" 128 + return 129 + fi 130 + 131 + # Copy export file to server 132 + scp "$export_file" "$SERVER_HOST:/tmp/agent_export.af" 133 + 134 + # Run import on server 135 + ssh "$SERVER_HOST" << 'EOF' 136 + cd /opt/assistant 137 + 138 + # Load env for Letta password 139 + source .env 140 + 141 + # Run import script inside app container 142 + docker compose -f docker-compose.yml -f docker-compose.prod.yml exec -T app bun run --silent - << 'TYPESCRIPT' 143 + import { Letta } from '@letta-ai/letta-client'; 144 + import { readFileSync } from 'fs'; 145 + 146 + const AGENT_NAME = 'adhd-support-agent'; 147 + 148 + async function main() { 149 + const client = new Letta({ 150 + baseURL: process.env.LETTA_BASE_URL || 'http://letta:8283', 151 + apiKey: process.env.LETTA_SERVER_PASSWORD || undefined, 152 + }); 153 + 154 + // Check if agent already exists 155 + for await (const agent of client.agents.list()) { 156 + if (agent.name === AGENT_NAME) { 157 + console.log(`Agent '${AGENT_NAME}' already exists (${agent.id}), deleting...`); 158 + await client.agents.delete(agent.id); 159 + break; 160 + } 161 + } 162 + 163 + // Import agent from .af file 164 + const fileData = readFileSync('/tmp/agent_export.af'); 165 + const blob = new Blob([fileData]); 166 + 167 + const imported = await client.agents.importAgentSerialized(blob, {}); 168 + console.log(`Agent imported: ${imported.id}`); 169 + console.log(`Name: ${imported.name}`); 170 + } 171 + 172 + main().catch(err => { 173 + console.error('Import failed:', err); 174 + process.exit(1); 175 + }); 176 + TYPESCRIPT 177 + EOF 178 + 179 + # Cleanup 180 + ssh "$SERVER_HOST" "rm -f /tmp/agent_export.af" 181 + rm -f "$export_file" 182 + 183 + log_success "Agent imported to production" 184 + } 185 + 186 + ####################################### 187 + # Restart app to pick up changes 188 + ####################################### 189 + restart_app() { 190 + log_info "Restarting app container..." 191 + ssh "$SERVER_HOST" "cd /opt/assistant && docker compose -f docker-compose.yml -f docker-compose.prod.yml restart app" 192 + log_success "App restarted" 193 + } 194 + 195 + ####################################### 196 + # Main 197 + ####################################### 198 + main() { 199 + echo -e "${BLUE}========================================${NC}" 200 + echo -e "${BLUE} Data Migration Script${NC}" 201 + echo -e "${BLUE}========================================${NC}" 202 + echo 203 + 204 + # Check SSH connectivity 205 + if ! ssh -o ConnectTimeout=5 "$SERVER_HOST" "echo ok" &> /dev/null; then 206 + die "Cannot connect to $SERVER_HOST" 207 + fi 208 + 209 + sync_sqlite 210 + 211 + if export_letta_agent; then 212 + import_letta_agent 213 + fi 214 + 215 + restart_app 216 + 217 + echo 218 + log_success "Migration complete!" 219 + } 220 + 221 + main "$@"