Initial commit
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message)
|
||||
Write-Host "==> $Message" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Write-Note {
|
||||
param([string]$Message)
|
||||
Write-Host " -> $Message" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
function Assert-Command {
|
||||
param([string]$Name)
|
||||
|
||||
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
|
||||
throw "Required command '$Name' was not found in PATH."
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-AbsolutePath {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$BasePath,
|
||||
[Parameter(Mandatory = $true)][string]$Path
|
||||
)
|
||||
|
||||
if ([System.IO.Path]::IsPathRooted($Path)) {
|
||||
return [System.IO.Path]::GetFullPath($Path)
|
||||
}
|
||||
|
||||
return [System.IO.Path]::GetFullPath((Join-Path $BasePath $Path))
|
||||
}
|
||||
|
||||
function Invoke-Checked {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$FilePath,
|
||||
[Parameter(Mandatory = $true)][string[]]$Arguments,
|
||||
[string]$WorkingDirectory
|
||||
)
|
||||
|
||||
if ($WorkingDirectory) {
|
||||
Push-Location $WorkingDirectory
|
||||
}
|
||||
|
||||
try {
|
||||
& $FilePath @Arguments
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Command failed: $FilePath $($Arguments -join ' ')"
|
||||
}
|
||||
} finally {
|
||||
if ($WorkingDirectory) {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-DockerCompose {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ComposeFile,
|
||||
[Parameter(Mandatory = $true)][string[]]$Arguments,
|
||||
[string]$ProjectName,
|
||||
[string]$WorkingDirectory
|
||||
)
|
||||
|
||||
$dockerArgs = @('compose', '-f', $ComposeFile)
|
||||
|
||||
if ($ProjectName) {
|
||||
$dockerArgs += @('-p', $ProjectName)
|
||||
}
|
||||
|
||||
$dockerArgs += $Arguments
|
||||
|
||||
Invoke-Checked -FilePath 'docker' -Arguments $dockerArgs -WorkingDirectory $WorkingDirectory
|
||||
}
|
||||
|
||||
function Wait-ForHttpOk {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Url,
|
||||
[int]$TimeoutSeconds = 120
|
||||
)
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 10
|
||||
|
||||
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300) {
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
Start-Sleep -Seconds 2
|
||||
continue
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
throw "Timed out waiting for a healthy response from $Url."
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ComposeFile = 'docker-compose.yml',
|
||||
[string]$ProjectName = 'goodwalk',
|
||||
[switch]$RunMigration,
|
||||
[string]$LegacyComposeFile,
|
||||
[string]$LegacyProjectName = 'legacy-goodwalk',
|
||||
[string]$LegacyWordPressContainer,
|
||||
[string]$LegacyDatabaseContainer,
|
||||
[string]$LegacyUploadsPath = '/var/www/html/wp-content/uploads',
|
||||
[string]$MySqlDatabase,
|
||||
[string]$MySqlUser,
|
||||
[string]$MySqlPassword,
|
||||
[switch]$SkipLegacyShutdown,
|
||||
[switch]$SkipBuild,
|
||||
[int]$HealthTimeoutSeconds = 120,
|
||||
[string]$HealthUrl = 'http://localhost/api/health'
|
||||
)
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $scriptDir 'common.ps1')
|
||||
|
||||
$repoRoot = Split-Path -Parent $scriptDir
|
||||
$composeFilePath = Resolve-AbsolutePath -BasePath $repoRoot -Path $ComposeFile
|
||||
|
||||
Write-Step 'Validating deployment prerequisites'
|
||||
Assert-Command docker
|
||||
Invoke-DockerCompose -ComposeFile $composeFilePath -ProjectName $ProjectName -WorkingDirectory $repoRoot -Arguments @('config')
|
||||
|
||||
if ($RunMigration) {
|
||||
Write-Step 'Running the WordPress migration step'
|
||||
$migrationScript = Join-Path $scriptDir 'migrate-wordpress.ps1'
|
||||
$migrationArgs = @(
|
||||
'-LegacyWordPressContainer', $LegacyWordPressContainer,
|
||||
'-LegacyUploadsPath', $LegacyUploadsPath
|
||||
)
|
||||
|
||||
if ($LegacyDatabaseContainer) {
|
||||
$migrationArgs += @('-LegacyDatabaseContainer', $LegacyDatabaseContainer)
|
||||
} else {
|
||||
$migrationArgs += '-SkipDatabaseDump'
|
||||
}
|
||||
|
||||
if ($MySqlDatabase) { $migrationArgs += @('-MySqlDatabase', $MySqlDatabase) }
|
||||
if ($MySqlUser) { $migrationArgs += @('-MySqlUser', $MySqlUser) }
|
||||
if ($MySqlPassword) { $migrationArgs += @('-MySqlPassword', $MySqlPassword) }
|
||||
|
||||
& $migrationScript @migrationArgs
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'The WordPress migration step failed.'
|
||||
}
|
||||
}
|
||||
|
||||
if ($LegacyComposeFile -and -not $SkipLegacyShutdown) {
|
||||
$legacyComposeFilePath = Resolve-AbsolutePath -BasePath $repoRoot -Path $LegacyComposeFile
|
||||
Write-Step 'Stopping the legacy WordPress stack'
|
||||
Invoke-DockerCompose -ComposeFile $legacyComposeFilePath -ProjectName $LegacyProjectName -WorkingDirectory $repoRoot -Arguments @('down')
|
||||
}
|
||||
|
||||
Write-Step 'Building and starting the new stack'
|
||||
$upArgs = @('up', '-d', '--remove-orphans')
|
||||
if (-not $SkipBuild) {
|
||||
$upArgs += '--build'
|
||||
}
|
||||
|
||||
Invoke-DockerCompose -ComposeFile $composeFilePath -ProjectName $ProjectName -WorkingDirectory $repoRoot -Arguments $upArgs
|
||||
|
||||
Write-Step 'Waiting for the new site to become healthy'
|
||||
Wait-ForHttpOk -Url $HealthUrl -TimeoutSeconds $HealthTimeoutSeconds
|
||||
|
||||
Write-Step 'Current container status'
|
||||
Invoke-DockerCompose -ComposeFile $composeFilePath -ProjectName $ProjectName -WorkingDirectory $repoRoot -Arguments @('ps')
|
||||
|
||||
Write-Step 'Deployment completed'
|
||||
Write-Host "Health check passed at $HealthUrl"
|
||||
@@ -0,0 +1,78 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$LegacyWordPressContainer,
|
||||
[string]$LegacyDatabaseContainer,
|
||||
[string]$LegacyUploadsPath = '/var/www/html/wp-content/uploads',
|
||||
[string]$StaticUploadsTarget = 'static/wp-content/uploads',
|
||||
[string]$BackupRoot = 'migration-backups',
|
||||
[string]$MySqlDatabase,
|
||||
[string]$MySqlUser,
|
||||
[string]$MySqlPassword,
|
||||
[switch]$SkipUploads,
|
||||
[switch]$SkipDatabaseDump
|
||||
)
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $scriptDir 'common.ps1')
|
||||
|
||||
$repoRoot = Split-Path -Parent $scriptDir
|
||||
$backupRootPath = Resolve-AbsolutePath -BasePath $repoRoot -Path $BackupRoot
|
||||
$staticUploadsPath = Resolve-AbsolutePath -BasePath $repoRoot -Path $StaticUploadsTarget
|
||||
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$runRoot = Join-Path $backupRootPath $timestamp
|
||||
$stagingRoot = Join-Path $runRoot 'uploads-staging'
|
||||
$uploadsArchivePath = Join-Path $runRoot 'uploads'
|
||||
$sqlDumpPath = Join-Path $runRoot 'wordpress.sql'
|
||||
|
||||
Write-Step 'Preparing WordPress migration workspace'
|
||||
Assert-Command docker
|
||||
New-Item -ItemType Directory -Force -Path $runRoot | Out-Null
|
||||
|
||||
if (-not $SkipDatabaseDump) {
|
||||
if (-not $LegacyDatabaseContainer) {
|
||||
throw 'LegacyDatabaseContainer is required unless -SkipDatabaseDump is used.'
|
||||
}
|
||||
|
||||
if (-not $MySqlDatabase -or -not $MySqlUser -or -not $MySqlPassword) {
|
||||
throw 'MySqlDatabase, MySqlUser, and MySqlPassword are required unless -SkipDatabaseDump is used.'
|
||||
}
|
||||
|
||||
Write-Step 'Dumping the legacy WordPress database'
|
||||
$dumpCommand = "exec mysqldump --single-transaction --quick --lock-tables=false -u$MySqlUser -p`"$MySqlPassword`" $MySqlDatabase"
|
||||
$dumpOutput = & docker exec $LegacyDatabaseContainer sh -lc $dumpCommand
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw 'mysqldump failed.'
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllText($sqlDumpPath, ($dumpOutput -join [Environment]::NewLine))
|
||||
Write-Note "Database dump saved to $sqlDumpPath"
|
||||
}
|
||||
|
||||
if (-not $SkipUploads) {
|
||||
Write-Step 'Copying wp-content/uploads from the legacy WordPress container'
|
||||
New-Item -ItemType Directory -Force -Path $stagingRoot | Out-Null
|
||||
|
||||
$sourceSpec = '{0}:{1}' -f $LegacyWordPressContainer, $LegacyUploadsPath
|
||||
Invoke-Checked -FilePath 'docker' -Arguments @('cp', $sourceSpec, $stagingRoot)
|
||||
|
||||
$copiedUploads = Join-Path $stagingRoot 'uploads'
|
||||
|
||||
if (-not (Test-Path $copiedUploads)) {
|
||||
throw "Expected copied uploads at $copiedUploads but it was not found."
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $staticUploadsPath) | Out-Null
|
||||
if (Test-Path $staticUploadsPath) {
|
||||
Remove-Item -LiteralPath $staticUploadsPath -Recurse -Force
|
||||
}
|
||||
|
||||
Move-Item -LiteralPath $copiedUploads -Destination $uploadsArchivePath
|
||||
Copy-Item -LiteralPath $uploadsArchivePath -Destination $staticUploadsPath -Recurse -Force
|
||||
|
||||
Write-Note "Uploads copied into $staticUploadsPath"
|
||||
Write-Note "Archive copy saved to $uploadsArchivePath"
|
||||
}
|
||||
|
||||
Write-Step 'Migration artifacts prepared'
|
||||
Write-Host "Backup folder: $runRoot"
|
||||
Reference in New Issue
Block a user