param( [string]$SourceHost = "", [int]$SourcePort = 3306, [string]$SourceDatabase = "", [string]$SourceUser = "", [string]$SourcePassword = "", [string]$TargetService = "mysql-debug", [string]$TargetDatabase = "", [string]$TargetUser = "", [string]$TargetPassword = "", [switch]$Force ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Read-DotEnv { param([string]$Path) $values = @{} if (-not (Test-Path $Path)) { return $values } foreach ($line in Get-Content $Path) { $trimmed = $line.Trim() if ($trimmed -eq "" -or $trimmed.StartsWith("#")) { continue } $idx = $trimmed.IndexOf("=") if ($idx -lt 1) { continue } $key = $trimmed.Substring(0, $idx).Trim() $value = $trimmed.Substring($idx + 1).Trim() if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) { $value = $value.Substring(1, $value.Length - 2) } $values[$key] = $value } return $values } function Get-EnvOrDotEnv { param( [hashtable]$DotEnv, [string]$Key ) $envValue = [Environment]::GetEnvironmentVariable($Key) if (-not [string]::IsNullOrWhiteSpace($envValue)) { return $envValue } if ($DotEnv.ContainsKey($Key) -and -not [string]::IsNullOrWhiteSpace([string]$DotEnv[$Key])) { return [string]$DotEnv[$Key] } return "" } $RootDir = Split-Path -Parent $PSScriptRoot Set-Location $RootDir if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { throw "Docker is required but was not found in PATH." } $dotEnv = Read-DotEnv -Path (Join-Path $RootDir ".env") if ([string]::IsNullOrWhiteSpace($SourceHost)) { $SourceHost = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_HOST" } if ([string]::IsNullOrWhiteSpace($SourceDatabase)) { $SourceDatabase = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_DATABASE" } if ([string]::IsNullOrWhiteSpace($SourceUser)) { $SourceUser = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_USER" } if ([string]::IsNullOrWhiteSpace($SourcePassword)) { $SourcePassword = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_PASSWORD" } if ([string]::IsNullOrWhiteSpace($TargetDatabase)) { $TargetDatabase = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_DEBUG_DATABASE" } if ([string]::IsNullOrWhiteSpace($TargetUser)) { $TargetUser = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_DEBUG_USER" } if ([string]::IsNullOrWhiteSpace($TargetPassword)) { $TargetPassword = Get-EnvOrDotEnv -DotEnv $dotEnv -Key "CONFIG_DB_DEBUG_PASSWORD" } if ([string]::IsNullOrWhiteSpace($SourceHost) -or [string]::IsNullOrWhiteSpace($SourceDatabase) -or [string]::IsNullOrWhiteSpace($SourceUser) -or [string]::IsNullOrWhiteSpace($SourcePassword)) { throw "Missing live DB credentials. Expected CONFIG_DB_HOST/CONFIG_DB_DATABASE/CONFIG_DB_USER/CONFIG_DB_PASSWORD." } if ([string]::IsNullOrWhiteSpace($TargetDatabase) -or [string]::IsNullOrWhiteSpace($TargetUser) -or [string]::IsNullOrWhiteSpace($TargetPassword)) { throw "Missing debug DB credentials. Expected CONFIG_DB_DEBUG_DATABASE/CONFIG_DB_DEBUG_USER/CONFIG_DB_DEBUG_PASSWORD." } if (-not $Force) { Write-Host "This will overwrite debug DB '$TargetDatabase' in service '$TargetService' with live DB '$SourceDatabase' from '$SourceHost'." -ForegroundColor Yellow $confirmation = Read-Host "Type CLONE to continue" if ($confirmation -ne "CLONE") { throw "Cancelled." } } Write-Host "Starting debug DB service ($TargetService)..." -ForegroundColor Cyan & docker compose up -d $TargetService Write-Host "Waiting for debug DB to accept connections..." -ForegroundColor Cyan $maxAttempts = 60 for ($i = 1; $i -le $maxAttempts; $i++) { & docker compose exec -T $TargetService sh -lc "MYSQL_PWD='$TargetPassword' mysqladmin -u '$TargetUser' ping --silent" *> $null if ($LASTEXITCODE -eq 0) { break } if ($i -eq $maxAttempts) { throw "Timed out waiting for $TargetService to become ready." } Start-Sleep -Seconds 2 } $dumpDir = Join-Path $RootDir ".tmp-db-clone" $dumpFile = Join-Path $dumpDir "live-to-debug.sql" $resetFile = Join-Path $dumpDir "reset-debug.sql" if (-not (Test-Path $dumpDir)) { New-Item -Path $dumpDir -ItemType Directory | Out-Null } if (Test-Path $dumpFile) { Remove-Item -Path $dumpFile -Force } if (Test-Path $resetFile) { Remove-Item -Path $resetFile -Force } Write-Host "Creating live dump..." -ForegroundColor Cyan $dumpMount = $dumpDir.Replace('\', '/') $dumpCmd = "exec mysqldump --compression-algorithms=zstd --single-transaction --quick --set-gtid-purged=OFF -h '$SourceHost' -P '$SourcePort' -u '$SourceUser' '$SourceDatabase' > /dump/live-to-debug.sql" & docker run --rm -e "MYSQL_PWD=$SourcePassword" -v "${dumpMount}:/dump" mysql:8.4 sh -lc $dumpCmd if ($LASTEXITCODE -ne 0 -or -not (Test-Path $dumpFile)) { throw "Database dump failed." } Write-Host "Resolving docker network for target service..." -ForegroundColor Cyan $targetContainerId = (& docker compose ps -q $TargetService | Select-Object -First 1) if ([string]::IsNullOrWhiteSpace($targetContainerId)) { throw "Could not resolve container id for service '$TargetService'." } $targetNetwork = (& docker inspect $targetContainerId --format "{{range `$k,`$v := .NetworkSettings.Networks}}{{`$k}}{{end}}" | Out-String).Trim() if ([string]::IsNullOrWhiteSpace($targetNetwork)) { throw "Could not resolve docker network for service '$TargetService'." } Write-Host "Resetting debug database '$TargetDatabase'..." -ForegroundColor Cyan Set-Content -Path $resetFile -Value "DROP DATABASE IF EXISTS $TargetDatabase; CREATE DATABASE $TargetDatabase;" -NoNewline $resetCmd = "MYSQL_PWD='$TargetPassword' mysql -h '$TargetService' -u '$TargetUser' < /dump/reset-debug.sql" & docker run --rm ` --network $targetNetwork ` -v "${dumpMount}:/dump" ` mysql:8.4 sh -lc $resetCmd if ($LASTEXITCODE -ne 0) { throw "Failed to reset debug database." } Write-Host "Importing live dump into debug DB..." -ForegroundColor Cyan & docker run --rm ` --network $targetNetwork ` -v "${dumpMount}:/dump" ` mysql:8.4 sh -lc "MYSQL_PWD='$TargetPassword' mysql -h '$TargetService' -u '$TargetUser' '$TargetDatabase' < /dump/live-to-debug.sql" if ($LASTEXITCODE -ne 0) { throw "Import into debug database failed." } Remove-Item -Path $dumpFile -Force -ErrorAction SilentlyContinue Remove-Item -Path $resetFile -Force -ErrorAction SilentlyContinue Write-Host "Done. Debug DB '$TargetDatabase' now mirrors live '$SourceDatabase'." -ForegroundColor Green