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>
323 lines
9.4 KiB
Markdown
323 lines
9.4 KiB
Markdown
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# Create large dataset first
|
|
# Monitor migration duration and memory usage
|
|
docker stats container_id
|
|
```
|
|
|
|
## API Testing
|
|
|
|
### Migration Status Endpoint
|
|
```bash
|
|
# 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
|
|
```bash
|
|
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:
|
|
1. **Locate backup file**: `db.sqlite.migration-backup-{timestamp}`
|
|
2. **Restore original**: `cp backup-file db.sqlite`
|
|
3. **Check logs**: Look for specific error details
|
|
4. **Fix issue**: Address the root cause (permissions, disk space, etc.)
|
|
5. **Retry**: Restart container to trigger migration again
|
|
|
|
### If Both Databases Exist:
|
|
1. **Check dates**: Determine which file is newer
|
|
2. **Backup both**: Make copies before proceeding
|
|
3. **Remove older**: Delete the outdated database file
|
|
4. **Restart**: Container will detect single database
|
|
|
|
### Emergency Data Recovery:
|
|
1. **Backup files are SQLite**: Can be opened with any SQLite client
|
|
2. **Manual export**: Use SQLite tools to export data
|
|
3. **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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
#!/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. |