experiments in a post-browser web
10
fork

Configure Feed

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

docs: add server migration safety analysis and improvements

Created SERVER-MIGRATION-SAFETY-IMPROVEMENTS.md documenting:
- Current risk assessment (server uses fs.renameSync without backup)
- Desktop migration is safe (non-destructive, metadata only)
- Recommended improvements in priority order:
1. HIGH: Add pre-migration backup with rollback
2. MEDIUM: Add dry-run mode for testing
3. MEDIUM: Add database integrity verification
4. LOW: Add automatic backup cleanup
- Deployment strategy and testing checklist

CRITICAL FINDING:
Server migration moves data without pre-migration backup. If deployed
to production, needs immediate attention. Desktop is safe.

+235
+235
SERVER-MIGRATION-SAFETY-IMPROVEMENTS.md
··· 1 + # Server Migration Safety Improvements 2 + 3 + ## Current Risk Assessment 4 + 5 + The server migration function `migrateUserDataToProfiles()` uses `fs.renameSync()` which is a MOVE operation, not a copy. This poses data loss risks if something goes wrong during migration. 6 + 7 + ## Recommended Improvements (Priority Order) 8 + 9 + ### 1. HIGH PRIORITY - Add Pre-Migration Backup 10 + 11 + Before moving any data, create a backup: 12 + 13 + ```javascript 14 + function migrateUserDataToProfiles() { 15 + if (!fs.existsSync(DATA_DIR)) { 16 + return; 17 + } 18 + 19 + const userDirs = fs.readdirSync(DATA_DIR, { withFileTypes: true }) 20 + .filter(dirent => dirent.isDirectory() && dirent.name !== 'system.db') 21 + .map(dirent => dirent.name); 22 + 23 + let migratedCount = 0; 24 + 25 + for (const userId of userDirs) { 26 + const oldDbPath = path.join(DATA_DIR, userId, "peek.db"); 27 + const newDbPath = path.join(DATA_DIR, userId, "profiles", "default", "datastore.sqlite"); 28 + 29 + if (!fs.existsSync(oldDbPath) || fs.existsSync(newDbPath)) { 30 + continue; 31 + } 32 + 33 + try { 34 + // NEW: Create backup before migration 35 + const backupPath = `${oldDbPath}.pre-migration-backup`; 36 + if (!fs.existsSync(backupPath)) { 37 + console.log(`Creating pre-migration backup for ${userId}`); 38 + fs.copyFileSync(oldDbPath, backupPath); 39 + } 40 + 41 + // Create profile directory 42 + const profileDir = path.dirname(newDbPath); 43 + if (!fs.existsSync(profileDir)) { 44 + fs.mkdirSync(profileDir, { recursive: true }); 45 + } 46 + 47 + // Move database file 48 + fs.renameSync(oldDbPath, newDbPath); 49 + console.log(`Migrated ${userId} data to profiles/default/datastore.sqlite`); 50 + 51 + // Verify the move succeeded 52 + if (!fs.existsSync(newDbPath)) { 53 + throw new Error('Migration verification failed: new DB not found'); 54 + } 55 + 56 + // Create profile record 57 + const existingProfile = users.getProfile(userId, "default"); 58 + if (!existingProfile) { 59 + users.createProfile(userId, "default", "Default"); 60 + console.log(`Created default profile for user ${userId}`); 61 + } 62 + 63 + // Move images directory if it exists 64 + const oldImagesDir = path.join(DATA_DIR, userId, "images"); 65 + const newImagesDir = path.join(DATA_DIR, userId, "profiles", "default", "images"); 66 + if (fs.existsSync(oldImagesDir) && !fs.existsSync(newImagesDir)) { 67 + fs.renameSync(oldImagesDir, newImagesDir); 68 + console.log(`Migrated ${userId} images to profiles/default/images`); 69 + } 70 + 71 + // NEW: Migration successful, can delete backup after grace period 72 + console.log(`Migration successful for ${userId}. Backup kept at: ${backupPath}`); 73 + console.log(`(Backup can be manually deleted after verifying data integrity)`); 74 + 75 + migratedCount++; 76 + } catch (error) { 77 + console.error(`Failed to migrate ${userId}:`, error.message); 78 + 79 + // NEW: Attempt rollback if backup exists 80 + const backupPath = `${oldDbPath}.pre-migration-backup`; 81 + if (fs.existsSync(backupPath) && !fs.existsSync(oldDbPath)) { 82 + console.error(`Attempting rollback for ${userId}...`); 83 + try { 84 + fs.copyFileSync(backupPath, oldDbPath); 85 + console.log(`Rollback successful for ${userId}`); 86 + } catch (rollbackError) { 87 + console.error(`CRITICAL: Rollback failed for ${userId}:`, rollbackError.message); 88 + console.error(`Manual recovery required. Backup at: ${backupPath}`); 89 + } 90 + } 91 + } 92 + } 93 + 94 + if (migratedCount > 0) { 95 + console.log(`Migration complete: ${migratedCount} user(s) migrated to profiles structure`); 96 + } 97 + } 98 + ``` 99 + 100 + ### 2. MEDIUM PRIORITY - Add Migration Dry-Run Mode 101 + 102 + Add an environment variable to test migration without moving data: 103 + 104 + ```javascript 105 + const DRY_RUN = process.env.MIGRATION_DRY_RUN === 'true'; 106 + 107 + function migrateUserDataToProfiles() { 108 + if (DRY_RUN) { 109 + console.log('[DRY RUN] Migration check - no data will be moved'); 110 + } 111 + 112 + // ... existing code ... 113 + 114 + for (const userId of userDirs) { 115 + // ... existing checks ... 116 + 117 + if (DRY_RUN) { 118 + console.log(`[DRY RUN] Would migrate ${userId}: ${oldDbPath} -> ${newDbPath}`); 119 + continue; 120 + } 121 + 122 + try { 123 + // ... actual migration ... 124 + } 125 + } 126 + } 127 + ``` 128 + 129 + Usage: `MIGRATION_DRY_RUN=true npm start` 130 + 131 + ### 3. MEDIUM PRIORITY - Add Data Integrity Verification 132 + 133 + After migration, verify the database is valid: 134 + 135 + ```javascript 136 + const Database = require('better-sqlite3'); 137 + 138 + function verifyDatabase(dbPath) { 139 + try { 140 + const db = new Database(dbPath, { readonly: true }); 141 + 142 + // Run integrity check 143 + const result = db.pragma('integrity_check'); 144 + db.close(); 145 + 146 + return result[0].integrity_check === 'ok'; 147 + } catch (error) { 148 + console.error(`Database verification failed: ${error.message}`); 149 + return false; 150 + } 151 + } 152 + 153 + // In migration function, after fs.renameSync(): 154 + if (!verifyDatabase(newDbPath)) { 155 + throw new Error('Database integrity check failed after migration'); 156 + } 157 + ``` 158 + 159 + ### 4. LOW PRIORITY - Add Automatic Backup Cleanup 160 + 161 + Clean up old migration backups after a grace period: 162 + 163 + ```javascript 164 + // After migration completes successfully: 165 + const backupPath = `${oldDbPath}.pre-migration-backup`; 166 + const GRACE_PERIOD_DAYS = 30; 167 + 168 + setTimeout(() => { 169 + if (fs.existsSync(backupPath)) { 170 + const stats = fs.statSync(backupPath); 171 + const ageMs = Date.now() - stats.mtimeMs; 172 + const ageDays = ageMs / (1000 * 60 * 60 * 24); 173 + 174 + if (ageDays > GRACE_PERIOD_DAYS) { 175 + fs.unlinkSync(backupPath); 176 + console.log(`Cleaned up old migration backup: ${backupPath}`); 177 + } 178 + } 179 + }, 0); // Run async, don't block startup 180 + ``` 181 + 182 + ## Desktop Migration (Already Safe) 183 + 184 + Desktop migration is non-destructive and doesn't need improvements: 185 + - Only creates records in `profiles.db` 186 + - Never moves or modifies data directories 187 + - Fully idempotent 188 + - Safe to run multiple times 189 + 190 + ## Mobile Migration (Not Implemented) 191 + 192 + When implementing mobile profile support: 193 + - Follow desktop pattern (non-destructive metadata only) 194 + - OR follow improved server pattern (with backups) 195 + - Consider iOS/Android app data backup constraints 196 + 197 + ## Deployment Strategy 198 + 199 + To deploy server improvements safely: 200 + 201 + 1. **Add pre-migration backup first** (highest priority) 202 + 2. **Deploy to staging**, test migration with real data 203 + 3. **Run dry-run on production** to verify what would be migrated 204 + 4. **Deploy to production** during low-traffic window 205 + 5. **Monitor logs** for migration errors 206 + 6. **Keep backups** for at least 30 days 207 + 208 + ## Testing Checklist 209 + 210 + Before deploying improved migration: 211 + 212 + - [ ] Test migration with small database (< 1MB) 213 + - [ ] Test migration with large database (> 100MB) 214 + - [ ] Test migration with disk nearly full 215 + - [ ] Test migration rollback on failure 216 + - [ ] Test dry-run mode 217 + - [ ] Test migration idempotency (run twice) 218 + - [ ] Test database integrity verification 219 + - [ ] Test backup cleanup after grace period 220 + - [ ] Verify backup doesn't exist when old DB missing 221 + - [ ] Verify migration skips if already migrated 222 + 223 + ## Current Status 224 + 225 + - ✅ Desktop migration: Safe (non-destructive) 226 + - ⚠️ Server migration: Needs improvements 227 + - ❌ Mobile migration: Not implemented 228 + 229 + ## Immediate Action 230 + 231 + If server is already deployed with current migration: 232 + 1. Check if any users have been migrated (`data/{userId}/profiles/default/` exists) 233 + 2. If yes, create manual backups immediately 234 + 3. Deploy improved migration code ASAP 235 + 4. Monitor for any data loss reports