this repo has no description
0
fork

Configure Feed

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

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