Phase 1: Database file migration (startup) - Add DatabaseMigration class for safe unencrypted → encrypted DB migration - Disable foreign key constraints during migration to prevent constraint failures - Create timestamped backups and verification checks - Rename original files instead of deletion for safety Phase 2: Lazy field encryption (user login) - Add LazyFieldEncryption utility for plaintext field detection - Implement gradual migration of sensitive fields using user KEK - Update DataCrypto to handle mixed plaintext/encrypted data - Integrate lazy encryption into AuthManager login flow Key improvements: - Non-destructive migration with comprehensive backup strategy - Automatic detection and handling of plaintext vs encrypted fields - User-transparent migration during normal login process - Complete migration logging and admin API endpoints - Foreign key constraint handling during database structure migration Resolves data decryption errors during Docker updates by providing seamless transition from plaintext to encrypted storage. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
9.4 KiB
Database Migration Testing Guide
Overview
This document outlines the testing procedures for the automatic database migration system that migrates unencrypted SQLite databases to encrypted format during Docker deployment updates.
Migration System Features
✅ Automatic Detection: Detects unencrypted databases on startup ✅ Safe Backup: Creates timestamped backups before migration ✅ Integrity Verification: Validates migration completeness ✅ Non-destructive: Original files are renamed, not deleted ✅ Cleanup: Removes old backup files (keeps latest 3) ✅ Admin API: Migration status and history endpoints ✅ Detailed Logging: Comprehensive migration logs
Test Scenarios
Scenario 1: Fresh Installation (No Migration Needed)
Setup: Clean Docker container with no existing database files Expected:
- New encrypted database created
- No migration messages in logs
- Status API shows "Fresh installation detected"
Test Commands:
# Clean start
docker run --rm termix:latest
# Check logs for "fresh installation"
# GET /database/migration/status should show needsMigration: false
Scenario 2: Standard Migration (Unencrypted → Encrypted)
Setup: Existing unencrypted db.sqlite file with user data
Expected:
- Automatic migration on startup
- Backup file created (
.migration-backup-{timestamp}) - Original file renamed (
.migrated-{timestamp}) - Encrypted database created successfully
- All data preserved and accessible
Test Commands:
# 1. Create test data in unencrypted format
docker run -v /host/data:/app/data termix:old-version
# Add some SSH hosts and credentials via UI
# 2. Stop container and update to new version
docker stop container_id
docker run -v /host/data:/app/data termix:latest
# 3. Check migration logs
docker logs container_id | grep -i migration
# 4. Verify data integrity via API
curl -H "Authorization: Bearer $TOKEN" http://localhost:8081/database/migration/status
Scenario 3: Already Encrypted (No Migration Needed)
Setup: Only encrypted database file exists Expected:
- No migration performed
- Database loads normally
- Status API shows "Only encrypted database exists"
Test Commands:
# Start with existing encrypted database
docker run -v /host/encrypted-data:/app/data termix:latest
# Verify no migration messages in logs
Scenario 4: Both Files Exist (Safety Mode)
Setup: Both encrypted and unencrypted databases present Expected:
- Migration skipped for safety
- Warning logged about manual intervention
- Both files preserved
- Uses encrypted database
Test Commands:
# Manually create both files
touch /host/data/db.sqlite
touch /host/data/db.sqlite.encrypted
docker run -v /host/data:/app/data termix:latest
# Check for safety warning in logs
Scenario 5: Migration Failure Recovery
Setup: Simulate migration failure (corrupted source file) Expected:
- Migration fails safely
- Backup file preserved
- Original unencrypted file untouched
- Clear error message with recovery instructions
Test Commands:
# Create corrupted database file
echo "corrupted" > /host/data/db.sqlite
docker run -v /host/data:/app/data termix:latest
# Verify error handling and backup preservation
Scenario 6: Large Database Migration
Setup: Large unencrypted database (>100MB with many records) Expected:
- Migration completes successfully
- Performance is acceptable (under 30 seconds)
- Memory usage stays reasonable
- All data integrity checks pass
Test Commands:
# Create large dataset first
# Monitor migration duration and memory usage
docker stats container_id
API Testing
Migration Status Endpoint
# Admin access required
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:8081/database/migration/status
# Expected response:
{
"migrationStatus": {
"needsMigration": false,
"hasUnencryptedDb": false,
"hasEncryptedDb": true,
"unencryptedDbSize": 0,
"reason": "Only encrypted database exists. No migration needed."
},
"files": {
"unencryptedDbSize": 0,
"encryptedDbSize": 524288,
"backupFiles": 2,
"migratedFiles": 1
},
"recommendations": [
"Database is properly encrypted",
"No action required"
]
}
Migration History Endpoint
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:8081/database/migration/history
# Expected response:
{
"files": [
{
"name": "db.sqlite.migration-backup-2024-09-24T10-30-00-000Z",
"size": 262144,
"created": "2024-09-24T10:30:00.000Z",
"modified": "2024-09-24T10:30:00.000Z",
"type": "backup"
}
],
"summary": {
"totalBackups": 1,
"totalMigrated": 1,
"oldestBackup": "2024-09-24T10:30:00.000Z",
"newestBackup": "2024-09-24T10:30:00.000Z"
}
}
Log Analysis
Successful Migration Logs
Look for these log entries:
[INFO] Migration status check completed - needsMigration: true
[INFO] Starting automatic database migration
[INFO] Creating migration backup
[SUCCESS] Migration backup created successfully
[INFO] Found tables to migrate - tableCount: 8
[SUCCESS] Migration integrity verification completed
[INFO] Creating encrypted database file
[SUCCESS] Database migration completed successfully
Migration Skipped (Safety) Logs
[INFO] Migration status check completed - needsMigration: false
[INFO] Both encrypted and unencrypted databases exist. Skipping migration for safety
[WARN] Manual intervention may be required
Migration Failure Logs
[ERROR] Database migration failed
[ERROR] Backup available at: /app/data/db.sqlite.migration-backup-{timestamp}
[ERROR] Manual intervention required to recover data
Manual Recovery Procedures
If Migration Fails:
- Locate backup file:
db.sqlite.migration-backup-{timestamp} - Restore original:
cp backup-file db.sqlite - Check logs: Look for specific error details
- Fix issue: Address the root cause (permissions, disk space, etc.)
- Retry: Restart container to trigger migration again
If Both Databases Exist:
- Check dates: Determine which file is newer
- Backup both: Make copies before proceeding
- Remove older: Delete the outdated database file
- Restart: Container will detect single database
Emergency Data Recovery:
- Backup files are SQLite: Can be opened with any SQLite client
- Manual export: Use SQLite tools to export data
- Re-import: Use Termix import functionality
Performance Expectations
| Database Size | Expected Migration Time | Memory Usage |
|---|---|---|
| < 10MB | < 5 seconds | < 50MB |
| 10-50MB | 5-15 seconds | < 100MB |
| 50-200MB | 15-45 seconds | < 200MB |
| 200MB+ | 45+ seconds | < 500MB |
Validation Checklist
After migration, verify:
- All SSH hosts are accessible
- SSH credentials work correctly
- File manager recent/pinned items preserved
- User settings maintained
- OIDC configuration intact
- Admin users still have admin privileges
- Backup file exists and is valid SQLite
- Original file renamed (not deleted)
- Encrypted file is properly encrypted
- Migration APIs respond correctly
Monitoring Commands
# Watch migration in real-time
docker logs -f container_id | grep -i migration
# Check file sizes before/after
ls -la /host/data/db.sqlite*
# Verify encrypted file
file /host/data/db.sqlite.encrypted
# Monitor system resources during migration
docker stats container_id
# Test database connectivity after migration
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:8081/hosts/list
Common Issues & Solutions
Issue: "Permission denied" during backup creation
Solution: Check container file permissions and volume mounts
Issue: "Insufficient disk space" during migration
Solution: Free up space, migration requires 2x database size temporarily
Issue: "Database locked" error
Solution: Ensure no other processes are accessing the database file
Issue: Migration hangs indefinitely
Solution: Check for very large BLOB data, increase timeout or migrate manually
Issue: Encrypted file fails validation
Solution: Check DATABASE_KEY environment variable, ensure it's stable
Security Considerations
- Backup files contain unencrypted data: Secure backup file access
- Migration logs may contain sensitive info: Review log retention policies
- Temporary files during migration: Ensure secure temp directory
- Original files are preserved: Plan for secure cleanup of old files
- Admin API access: Ensure proper authentication and authorization
Integration with CI/CD
For automated testing in CI/CD pipelines:
#!/bin/bash
# Migration integration test
set -e
# Start with unencrypted test data
docker run -d --name test-migration \
-v ./test-data:/app/data \
termix:latest
# Wait for startup
sleep 30
# Check migration status
RESPONSE=$(curl -s -H "Authorization: Bearer $TEST_TOKEN" \
http://localhost:8081/database/migration/status)
# Validate migration success
echo "$RESPONSE" | jq '.migrationStatus.needsMigration == false'
# Cleanup
docker stop test-migration
docker rm test-migration
This comprehensive testing approach ensures the migration system handles all edge cases safely and provides administrators with full visibility into the migration process.