|
|
@@ -0,0 +1,103 @@ |
|
|
|
|
|
<# |
|
|
|
|
|
Applies pending db\migrations\*.asp to the production database WITHOUT redeploying the |
|
|
|
|
|
site - useful when the app code on the server is already current but the schema has |
|
|
|
|
|
drifted (e.g. a migration was added after the last full deploy, or - as happened once - |
|
|
|
|
|
build-release.ps1 was silently dropping db\migrations\ from every release). |
|
|
|
|
|
|
|
|
|
|
|
Flow: |
|
|
|
|
|
1. scp scripts\runMigrations.vbs and db\migrations\*.asp to a throwaway work folder in |
|
|
|
|
|
C:\Windows\Temp on the remote host (never touches the live site's own deploy directory). |
|
|
|
|
|
2. scp scripts\run-migrations-remote-apply.ps1 and run it over ssh - it copies the site's |
|
|
|
|
|
already-deployed public\web.config into the work folder (so it uses the real production |
|
|
|
|
|
connection string), runs `runMigrations.vbs status` then `up`, and deletes the work |
|
|
|
|
|
folder afterward. |
|
|
|
|
|
|
|
|
|
|
|
For a normal deploy (which also ships db\migrations and runs migrations automatically), |
|
|
|
|
|
use scripts\deploy-iis.ps1 instead. |
|
|
|
|
|
|
|
|
|
|
|
Usage: |
|
|
|
|
|
powershell -File scripts\run-migrations-remote.ps1 |
|
|
|
|
|
|
|
|
|
|
|
Remote target defaults to the "ssh user@host" line in scripts\deployinfo.txt (gitignored), |
|
|
|
|
|
same as deploy-iis.ps1. |
|
|
|
|
|
#> |
|
|
|
|
|
|
|
|
|
|
|
param( |
|
|
|
|
|
[string]$RemoteTarget = '', |
|
|
|
|
|
[int]$RemotePort = 22, |
|
|
|
|
|
[string]$RemoteDir = 'C:\inetpub\wwwroot\Purple_Envelop_Order_Site', |
|
|
|
|
|
[string]$SshExe = 'ssh', |
|
|
|
|
|
[string]$ScpExe = 'scp' |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
$ErrorActionPreference = 'Stop' |
|
|
|
|
|
$repoRoot = Split-Path $PSScriptRoot -Parent |
|
|
|
|
|
|
|
|
|
|
|
function Ensure-Command { |
|
|
|
|
|
param([string]$Name) |
|
|
|
|
|
if(!(Get-Command $Name -ErrorAction SilentlyContinue)){ |
|
|
|
|
|
throw "$Name not found on PATH" |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function Get-DefaultRemoteTarget { |
|
|
|
|
|
$infoPath = Join-Path $PSScriptRoot 'deployinfo.txt' |
|
|
|
|
|
if(!(Test-Path $infoPath)){ return '' } |
|
|
|
|
|
$sshLine = Get-Content $infoPath | Where-Object { $_ -match '^\s*ssh\s+' } | Select-Object -First 1 |
|
|
|
|
|
if([string]::IsNullOrWhiteSpace($sshLine)){ return '' } |
|
|
|
|
|
return ($sshLine -replace '^\s*ssh\s+', '').Trim() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if([string]::IsNullOrWhiteSpace($RemoteTarget)){ |
|
|
|
|
|
$RemoteTarget = Get-DefaultRemoteTarget |
|
|
|
|
|
} |
|
|
|
|
|
if([string]::IsNullOrWhiteSpace($RemoteTarget)){ |
|
|
|
|
|
throw 'No -RemoteTarget given and scripts\deployinfo.txt not found. Create scripts\deployinfo.txt with a line like: ssh user@host' |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Ensure-Command $SshExe |
|
|
|
|
|
Ensure-Command $ScpExe |
|
|
|
|
|
|
|
|
|
|
|
$AuthOpts = @( |
|
|
|
|
|
'-o', 'PreferredAuthentications=publickey,keyboard-interactive,password', |
|
|
|
|
|
'-o', 'NumberOfPasswordPrompts=3' |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# --- 1. Stage a local bundle: scripts\runMigrations.vbs + db\migrations\*.asp --- |
|
|
|
|
|
$stageDir = Join-Path $repoRoot ('dist\migrations_stage_' + (Get-Date -Format 'yyyyMMdd_HHmmss')) |
|
|
|
|
|
New-Item -ItemType Directory -Force -Path (Join-Path $stageDir 'scripts') | Out-Null |
|
|
|
|
|
New-Item -ItemType Directory -Force -Path (Join-Path $stageDir 'db\migrations') | Out-Null |
|
|
|
|
|
Copy-Item (Join-Path $repoRoot 'scripts\runMigrations.vbs') (Join-Path $stageDir 'scripts\runMigrations.vbs') -Force |
|
|
|
|
|
Copy-Item (Join-Path $repoRoot 'db\migrations\*.asp') (Join-Path $stageDir 'db\migrations') -Force |
|
|
|
|
|
|
|
|
|
|
|
$remoteWorkDir = 'C:\Windows\Temp\pe-migrations-' + (Get-Date -Format 'yyyyMMdd_HHmmss') |
|
|
|
|
|
$remoteApplyDest = 'C:\Windows\Temp\run-migrations-remote-apply.ps1' |
|
|
|
|
|
|
|
|
|
|
|
# --- 2. Ship the bundle and the remote-apply script --- |
|
|
|
|
|
# A single recursive source -> a destination that doesn't exist yet makes scp create that |
|
|
|
|
|
# destination as a copy of the source, so this also creates $remoteWorkDir - no separate |
|
|
|
|
|
# "ssh mkdir" step needed (the remote shell is cmd.exe, not PowerShell, so raw PowerShell |
|
|
|
|
|
# commands passed straight through ssh don't run there without an explicit powershell.exe |
|
|
|
|
|
# wrapper; simplest to just avoid needing one here). |
|
|
|
|
|
Write-Host "Copying migrations bundle to $RemoteTarget" |
|
|
|
|
|
& $ScpExe -P $RemotePort @AuthOpts -r $stageDir "${RemoteTarget}:$remoteWorkDir" |
|
|
|
|
|
if($LASTEXITCODE -ne 0){ throw 'scp of migrations bundle failed - see scp output above for the actual reason' } |
|
|
|
|
|
|
|
|
|
|
|
& $ScpExe -P $RemotePort @AuthOpts (Join-Path $PSScriptRoot 'run-migrations-remote-apply.ps1') "${RemoteTarget}:$remoteApplyDest" |
|
|
|
|
|
if($LASTEXITCODE -ne 0){ throw 'scp of remote-apply script failed - see scp output above for the actual reason' } |
|
|
|
|
|
|
|
|
|
|
|
Remove-Item -Recurse -Force $stageDir -ErrorAction SilentlyContinue |
|
|
|
|
|
|
|
|
|
|
|
# --- 3. Apply on the remote host (also deletes $remoteWorkDir when done) --- |
|
|
|
|
|
$remoteCommandParts = @( |
|
|
|
|
|
'powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', |
|
|
|
|
|
'-File', ('"' + $remoteApplyDest + '"'), |
|
|
|
|
|
'-RemoteDir', ('"' + $RemoteDir + '"'), |
|
|
|
|
|
'-WorkDir', ('"' + $remoteWorkDir + '"') |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
Write-Host "Applying pending migrations on $RemoteTarget" |
|
|
|
|
|
& $SshExe -p $RemotePort @AuthOpts $RemoteTarget ($remoteCommandParts -join ' ') |
|
|
|
|
|
if($LASTEXITCODE -ne 0){ throw 'remote migration apply failed - see ssh output above' } |
|
|
|
|
|
|
|
|
|
|
|
Write-Host 'Done.' |