Fix migration tool params called from install.sh (#190)

This commit is contained in:
Leendert de Borst
2024-12-24 14:48:34 +01:00
parent 4d43acb53f
commit a7502d42e4
2 changed files with 64 additions and 9 deletions

View File

@@ -1659,7 +1659,7 @@ handle_migrate_db() {
SQLITE_DB_NAME=$(basename "$SQLITE_DB_ABS")
# Get PostgreSQL password from .env file
POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2)
POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d= -f2-)
if [ -z "$POSTGRES_PASSWORD" ]; then
printf "${RED}Error: POSTGRES_PASSWORD not found in .env file${NC}\n"
exit 1
@@ -1670,9 +1670,17 @@ handle_migrate_db() {
NETWORK_NAME=$(echo "$NETWORK_NAME" | tr '[:upper:]' '[:lower:]')
printf "\n${YELLOW}Warning: This will migrate data from your SQLite database to PostgreSQL.${NC}\n"
printf "\n"
printf "This is a one-time operation necessary when upgrading from <= 0.9.x to 0.10.0+ and only needs to be run once.\n"
printf "\n"
printf "Source database: ${CYAN}${SQLITE_DB_ABS}${NC}\n"
printf "Target: PostgreSQL database (using connection string from docker-compose.yml)\n"
printf "Make sure you have backed up your data before proceeding.\n"
printf "\n${RED}WARNING: This operation will DELETE ALL EXISTING DATA in the PostgreSQL database.${NC}\n"
printf "${RED}Only proceed if you understand that any current PostgreSQL data will be permanently lost.${NC}\n"
printf "\n"
read -p "Continue with migration? [y/N]: " confirm
if [[ ! $confirm =~ ^[Yy]$ ]]; then
printf "${YELLOW}Migration cancelled.${NC}\n"
@@ -1705,14 +1713,14 @@ handle_migrate_db() {
docker run --rm \
--network="${NETWORK_NAME}" \
-v "${SQLITE_DB_DIR}:/sqlite" \
${GITHUB_CONTAINER_REGISTRY}-installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}"
installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}"
else
# Run migration with volume mount using pre-built image
docker run --rm \
--network="${NETWORK_NAME}" \
-v "${SQLITE_DB_DIR}:/sqlite" \
installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}"
fi
${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.0 migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}"
fi
printf "${GREEN}> Check migration output above for details.${NC}\n"
}

View File

@@ -104,21 +104,40 @@ public partial class Program
var optionsBuilderSqlite = new DbContextOptionsBuilder<AliasServerDbContext>()
.UseSqlite(sqliteConnString);
var optionsBuilderPg = new DbContextOptionsBuilder<AliasServerDbContext>()
.UseNpgsql(pgConnString);
// Make sure sqlite is on latest version migration
Console.WriteLine("Update sqlite database to latest version...");
await using var sqliteContext = new AliasServerDbContextSqlite(optionsBuilderSqlite.Options);
await sqliteContext.Database.MigrateAsync();
Console.WriteLine("Updating finished.");
var optionsBuilderPg = new DbContextOptionsBuilder<AliasServerDbContext>()
.UseNpgsql(pgConnString);
// Make sure postgres is on latest version migration
Console.WriteLine("Update postgres database to latest version...");
await using var pgContext = new AliasServerDbContextPostgresql(optionsBuilderPg.Options);
await pgContext.Database.MigrateAsync();
Console.WriteLine("Updating finished.");
Console.WriteLine("Truncating existing tables in reverse dependency order...");
// Truncate tables in reverse order of dependencies
await TruncateTable(pgContext.EmailAttachments, "EmailAttachments");
await TruncateTable(pgContext.Emails, "Emails");
await TruncateTable(pgContext.UserTokens, "UserTokens");
await TruncateTable(pgContext.UserRoles, "UserRoles");
await TruncateTable(pgContext.UserLogin, "UserLogins");
await TruncateTable(pgContext.UserEmailClaims, "UserEmailClaims");
await TruncateTable(pgContext.Vaults, "Vaults");
await TruncateTable(pgContext.UserEncryptionKeys, "UserEncryptionKeys");
await TruncateTable(pgContext.AliasVaultUserRefreshTokens, "AliasVaultUserRefreshTokens");
await TruncateTable(pgContext.AuthLogs, "AuthLogs");
await TruncateTable(pgContext.DataProtectionKeys, "DataProtectionKeys");
await TruncateTable(pgContext.ServerSettings, "ServerSettings");
await TruncateTable(pgContext.AliasVaultUsers, "AliasVaultUsers");
await TruncateTable(pgContext.AliasVaultRoles, "AliasVaultRoles");
await TruncateTable(pgContext.AdminUsers, "AdminUsers");
Console.WriteLine("Starting content migration...");
// First, migrate tables without foreign key dependencies
@@ -155,6 +174,25 @@ public partial class Program
}
}
/// <summary>
/// Truncates a table in the PostgreSQL database.
/// </summary>
/// <typeparam name="T">The entity type of the table being truncated.</typeparam>
/// <param name="table">The database table to truncate.</param>
/// <param name="tableName">The name of the table being truncated (for logging purposes).</param>
/// <returns>A task representing the asynchronous truncation operation.</returns>
private static async Task TruncateTable<T>(DbSet<T> table, string tableName)
where T : class
{
Console.WriteLine($"Truncating table {tableName}...");
var count = await table.CountAsync();
if (count > 0)
{
await table.ExecuteDeleteAsync();
Console.WriteLine($"Removed {count} records from {tableName}");
}
}
/// <summary>
/// Migrates data from one database table to another, handling the transfer in batches.
/// </summary>
@@ -184,6 +222,15 @@ public partial class Program
if (items.Count > 0)
{
// Remove any existing entries in the destination table
var existingEntries = await destination.ToListAsync();
if (existingEntries.Any())
{
Console.WriteLine($"Removing {existingEntries.Count} existing entries from {tableName}...");
destination.RemoveRange(existingEntries);
await destinationContext.SaveChangesAsync();
}
const int batchSize = 30;
foreach (var batch in items.Chunk(batchSize))
{
@@ -220,9 +267,9 @@ public partial class Program
}
// Ensure that the amount of records in the source and destination tables match
if (await source.CountAsync() != await destination.CountAsync())
if (await source.CountAsync() > await destination.CountAsync())
{
throw new ArgumentException($"The amount of records in the source and destination tables do not match. Check if the migration is working correctly.");
throw new ArgumentException($"The amount of records in the source is greater than the destination. Check if the migration is working correctly.");
}
}
}