| @@ -1,324 +1,265 @@ | |||||
| <# | <# | ||||
| Deploy asp-territory to an existing IIS site, locally or over SSH. | |||||
| Remote mode: | |||||
| - Copies this script to the remote Windows host with scp | |||||
| - Executes it remotely via ssh in -RunRemoteCore mode | |||||
| - Preserves the remote site's current DB path unless -DbPath is passed | |||||
| - Can run standard migrations and an optional legacy migration script | |||||
| Local / remote core behavior: | |||||
| - Infers IIS site/app pool/work dir from the existing site when possible | |||||
| - Stops the site/app pool while deploying | |||||
| - Clones/pulls and hard-resets to origin/<Branch> | |||||
| - Points IIS at <WorkDir>\public | |||||
| - Reapplies the effective DB path in public\web.config | |||||
| - Grants IIS AppPool rights to the DB folder | |||||
| - Runs migrations | |||||
| - Restarts the site/app pool and smoke tests key routes | |||||
| .SYNOPSIS | |||||
| Packages this repository and deploys it to IIS over Tailscale/OpenSSH. | |||||
| .DESCRIPTION | |||||
| This is the controller/CI entry point. It creates a ZIP containing the full | |||||
| repository (excluding VCS and local deployment artifacts), computes SHA-256, | |||||
| copies the package and host installer with scp, then invokes the installer on | |||||
| the Windows host through ssh. | |||||
| -DryRun performs local validation and prints the remote operations without | |||||
| connecting. Gitea 1.11.4 does not run this itself; use a trusted external CI | |||||
| worker, a scheduled task, or an operator workstation. | |||||
| #> | #> | ||||
| [CmdletBinding()] | |||||
| param( | param( | ||||
| [string]$Repo = 'git@onefortheroadgit.sytes.net:dcovington/asp-classic-unified-framework.git', | |||||
| [string]$Branch = 'main', | |||||
| [string]$SiteName = 'ttasp', | |||||
| [string]$AppPool = '', | |||||
| [string]$WorkDir = '', | |||||
| [string]$PublicDir = '', | |||||
| [string]$BaseUrl = '', | |||||
| [string]$DbPath = '', | |||||
| [Parameter(Mandatory = $true)] | |||||
| [ValidatePattern('^[A-Za-z0-9_. -]+$')] | |||||
| [string]$SiteName, | |||||
| [switch]$RunMigrations = $true, | |||||
| [switch]$SkipLegacyIsBusinessMigration, | |||||
| [string]$LegacyMigrationScript = 'scripts\migrate_isbusiness_to_households.vbs', | |||||
| [switch]$UseRemoteSsh, | |||||
| [string]$RemoteTarget = '', | |||||
| [string]$RemoteTarget = 'webserver-1', | |||||
| [ValidateRange(1, 65535)] | |||||
| [int]$RemotePort = 22, | [int]$RemotePort = 22, | ||||
| [string]$SourcePath = (Split-Path $PSScriptRoot -Parent), | |||||
| [string]$DeployRoot = '', | |||||
| [string]$BaseUrl = '', | |||||
| [string]$ReleaseId = '', | |||||
| [string]$ExpectedBranch = 'master', | |||||
| [ValidateRange(2, 100)] | |||||
| [int]$KeepReleases = 5, | |||||
| [string]$SshExe = 'ssh', | [string]$SshExe = 'ssh', | ||||
| [string]$ScpExe = 'scp', | [string]$ScpExe = 'scp', | ||||
| [switch]$RunRemoteCore | |||||
| [switch]$AllowAnyBranch, | |||||
| [switch]$AllowDirty, | |||||
| [switch]$RunMigrations, | |||||
| [switch]$SkipSmokeTest, | |||||
| [switch]$RemotePreflightOnly, | |||||
| [switch]$Rollback, | |||||
| [string]$RollbackTo = '', | |||||
| [switch]$DryRun, | |||||
| [switch]$KeepPackage | |||||
| ) | ) | ||||
| Set-StrictMode -Version 2.0 | |||||
| $ErrorActionPreference = 'Stop' | $ErrorActionPreference = 'Stop' | ||||
| function Ensure-Dir { | |||||
| param([string]$Path) | |||||
| if([string]::IsNullOrWhiteSpace($Path)){ return } | |||||
| if(!(Test-Path $Path)){ | |||||
| New-Item -ItemType Directory -Force -Path $Path | Out-Null | |||||
| } | |||||
| function Write-Step { | |||||
| param([string]$Message) | |||||
| Write-Host ('==> ' + $Message) | |||||
| } | } | ||||
| function Ensure-Command { | |||||
| function Assert-Command { | |||||
| param([string]$Name) | param([string]$Name) | ||||
| if(!(Get-Command $Name -ErrorAction SilentlyContinue)){ | |||||
| throw "$Name not found on PATH" | |||||
| if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { | |||||
| throw "$Name was not found on PATH." | |||||
| } | } | ||||
| } | } | ||||
| function Get-DefaultRemoteTargetFromInfo { | |||||
| $infoPath = Join-Path $PSScriptRoot 'depolyinfo.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() | |||||
| } | |||||
| function ConvertTo-PowerShellLiteral { | |||||
| param([AllowNull()][string]$Value) | |||||
| if($null -eq $Value){ return "''" } | |||||
| return "'" + ($Value -replace "'", "''") + "'" | |||||
| } | |||||
| function ConvertTo-CmdDoubleQuoted { | |||||
| param([AllowNull()][string]$Value) | |||||
| if($null -eq $Value){ return '""' } | |||||
| return '"' + ($Value -replace '"', '""') + '"' | |||||
| function ConvertTo-SingleQuotedPowerShell { | |||||
| param([string]$Value) | |||||
| return "'" + $Value.Replace("'", "''") + "'" | |||||
| } | } | ||||
| function Get-DataSourceFromConfig { | |||||
| param([string]$ConfigPath) | |||||
| if(!(Test-Path $ConfigPath)){ return '' } | |||||
| $raw = Get-Content $ConfigPath -Raw | |||||
| $match = [regex]::Match($raw, 'Data Source=([^;]+);', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) | |||||
| if($match.Success){ | |||||
| return $match.Groups[1].Value.Trim() | |||||
| } | |||||
| return '' | |||||
| } | |||||
| function Set-DataSourceInConfig { | |||||
| function Add-RemoteArgument { | |||||
| param( | param( | ||||
| [string]$ConfigPath, | |||||
| [string]$EffectiveDbPath | |||||
| [System.Collections.Generic.List[string]]$Arguments, | |||||
| [string]$Name, | |||||
| [string]$Value | |||||
| ) | ) | ||||
| $Arguments.Add($Name) | |||||
| $Arguments.Add((ConvertTo-SingleQuotedPowerShell $Value)) | |||||
| } | |||||
| if(!(Test-Path $ConfigPath)){ return } | |||||
| $raw = Get-Content $ConfigPath -Raw | |||||
| $updated = [regex]::Replace( | |||||
| $raw, | |||||
| 'Data Source=[^;]*;', | |||||
| ('Data Source=' + $EffectiveDbPath + ';'), | |||||
| [System.Text.RegularExpressions.RegexOptions]::IgnoreCase | |||||
| function Copy-ReleaseSource { | |||||
| param( | |||||
| [string]$From, | |||||
| [string]$To | |||||
| ) | ) | ||||
| if($updated -ne $raw){ | |||||
| Set-Content -Path $ConfigPath -Value $updated -Encoding UTF8 | |||||
| Write-Host "Updated ConnectionString Data Source to $EffectiveDbPath" | |||||
| New-Item -ItemType Directory -Force -Path $To | Out-Null | |||||
| $excludedNames = @('.git', '.deployment', 'releases') | |||||
| Get-ChildItem -LiteralPath $From -Force | ForEach-Object { | |||||
| if ($excludedNames -notcontains $_.Name) { | |||||
| Copy-Item -LiteralPath $_.FullName -Destination $To -Recurse -Force | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| function Get-BaseUrlFromSite { | |||||
| param($Site) | |||||
| $SourcePath = [System.IO.Path]::GetFullPath($SourcePath) | |||||
| $installerPath = Join-Path $PSScriptRoot 'install-iis-release.ps1' | |||||
| if (-not (Test-Path -LiteralPath $installerPath -PathType Leaf)) { | |||||
| throw "Host installer is missing: $installerPath" | |||||
| } | |||||
| $httpBind = $Site.Bindings.Collection | Where-Object { $_.protocol -eq 'http' } | Select-Object -First 1 | |||||
| if($httpBind){ | |||||
| $parts = $httpBind.bindingInformation.Split(':') | |||||
| $port = $parts[1] | |||||
| if([string]::IsNullOrWhiteSpace($port)){ $port = '80' } | |||||
| return ('http://127.0.0.1:' + $port) | |||||
| $requiredSourceFiles = @( | |||||
| 'public\Default.asp', | |||||
| 'public\web.config', | |||||
| 'core\autoload_core.asp', | |||||
| 'app\controllers\autoload_controllers.asp' | |||||
| ) | |||||
| foreach ($relativePath in $requiredSourceFiles) { | |||||
| if (-not (Test-Path -LiteralPath (Join-Path $SourcePath $relativePath) -PathType Leaf)) { | |||||
| throw "Source tree is incomplete; missing $relativePath" | |||||
| } | } | ||||
| return 'http://127.0.0.1' | |||||
| } | } | ||||
| function Invoke-DeployCore { | |||||
| Ensure-Command git | |||||
| Import-Module WebAdministration | |||||
| $site = Get-Website -Name $SiteName | |||||
| if(!$site){ throw "IIS site not found: $SiteName" } | |||||
| try { | |||||
| [xml](Get-Content -LiteralPath (Join-Path $SourcePath 'public\web.config') -Raw) | Out-Null | |||||
| } catch { | |||||
| throw "Source public\web.config is not valid XML: $($_.Exception.Message)" | |||||
| } | |||||
| if([string]::IsNullOrWhiteSpace($AppPool)){ | |||||
| $AppPool = $site.applicationPool | |||||
| $directGitRoot = Test-Path -LiteralPath (Join-Path $SourcePath '.git') | |||||
| $commit = 'nogit' | |||||
| if ($directGitRoot) { | |||||
| Assert-Command 'git' | |||||
| $branch = (& git -C $SourcePath branch --show-current).Trim() | |||||
| if ($LASTEXITCODE -ne 0) { | |||||
| throw 'Could not determine the Git branch.' | |||||
| } | } | ||||
| if([string]::IsNullOrWhiteSpace($PublicDir)){ | |||||
| $PublicDir = $site.physicalPath | |||||
| if ((-not $AllowAnyBranch) -and $branch -ne $ExpectedBranch) { | |||||
| throw "Refusing to deploy branch '$branch'; expected '$ExpectedBranch'. Use -AllowAnyBranch only for an intentional exception." | |||||
| } | } | ||||
| if([string]::IsNullOrWhiteSpace($WorkDir)){ | |||||
| $pd = [Environment]::ExpandEnvironmentVariables($PublicDir) | |||||
| $pd = $pd.Trim().Trim('"') | |||||
| $pd = $pd.TrimEnd('\','/') | |||||
| if((Split-Path $pd -Leaf).ToLower() -eq 'public'){ | |||||
| $WorkDir = Split-Path $pd -Parent | |||||
| } else { | |||||
| $WorkDir = $pd | |||||
| } | |||||
| $dirty = & git -C $SourcePath status --porcelain | |||||
| if ($LASTEXITCODE -ne 0) { | |||||
| throw 'Could not inspect the Git worktree.' | |||||
| } | } | ||||
| if([string]::IsNullOrWhiteSpace($BaseUrl)){ | |||||
| $BaseUrl = Get-BaseUrlFromSite -Site $site | |||||
| if ((-not $AllowDirty) -and $null -ne $dirty -and @($dirty).Count -gt 0) { | |||||
| throw 'Refusing to deploy a dirty worktree. Commit/stash changes or use -AllowDirty for an intentional, auditable exception.' | |||||
| } | } | ||||
| $currentPublicDir = $PublicDir | |||||
| $currentConfigPath = Join-Path $currentPublicDir 'web.config' | |||||
| $effectiveDbPath = $DbPath | |||||
| if([string]::IsNullOrWhiteSpace($effectiveDbPath)){ | |||||
| $effectiveDbPath = Get-DataSourceFromConfig -ConfigPath $currentConfigPath | |||||
| $commit = (& git -C $SourcePath rev-parse --short=12 HEAD).Trim() | |||||
| if ($LASTEXITCODE -ne 0) { | |||||
| throw 'Could not determine the Git commit.' | |||||
| } | } | ||||
| if([string]::IsNullOrWhiteSpace($effectiveDbPath)){ | |||||
| throw 'No database path was provided and no existing Data Source could be read from the current web.config' | |||||
| Write-Host "Source branch: $branch" | |||||
| Write-Host "Source commit: $commit" | |||||
| } else { | |||||
| Write-Warning 'SourcePath is not a standalone Git checkout; branch and dirty-worktree checks cannot be enforced.' | |||||
| if (-not $AllowAnyBranch) { | |||||
| throw 'Use a standalone CI checkout, or pass -AllowAnyBranch explicitly for a reviewed non-Git source tree.' | |||||
| } | } | ||||
| } | |||||
| Write-Host "Stopping IIS site $SiteName and app pool $AppPool" | |||||
| try { Stop-Website -Name $SiteName } catch { } | |||||
| try { Stop-WebAppPool -Name $AppPool } catch { } | |||||
| Ensure-Dir (Split-Path $WorkDir -Parent) | |||||
| if((Test-Path $WorkDir) -and !(Test-Path (Join-Path $WorkDir '.git'))){ | |||||
| $bak = ($WorkDir.TrimEnd('\') + '_pre_git_' + (Get-Date -Format 'yyyyMMdd_HHmmss')) | |||||
| Write-Host "Existing non-git folder detected. Moving to $bak" | |||||
| Move-Item -Force $WorkDir $bak | |||||
| } | |||||
| if ([string]::IsNullOrWhiteSpace($ReleaseId)) { | |||||
| $ReleaseId = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + $commit | |||||
| } | |||||
| if ($ReleaseId -notmatch '^[A-Za-z0-9._-]+$') { | |||||
| throw 'ReleaseId may contain only letters, numbers, dot, underscore, and hyphen.' | |||||
| } | |||||
| if ($Rollback -and [string]::IsNullOrWhiteSpace($RollbackTo)) { | |||||
| throw '-Rollback requires -RollbackTo <release-id>.' | |||||
| } | |||||
| if ((-not $Rollback) -and -not [string]::IsNullOrWhiteSpace($RollbackTo)) { | |||||
| throw '-RollbackTo is only valid with -Rollback.' | |||||
| } | |||||
| if ($RunMigrations -and $Rollback) { | |||||
| throw '-RunMigrations is not valid during rollback.' | |||||
| } | |||||
| if(!(Test-Path $WorkDir)){ | |||||
| Write-Host "Cloning $Repo -> $WorkDir" | |||||
| git clone $Repo $WorkDir | |||||
| $workRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('asp-iis-deploy-' + [Guid]::NewGuid().ToString('N')) | |||||
| $packageStage = Join-Path $workRoot 'package' | |||||
| $packagePath = Join-Path $workRoot ($ReleaseId + '.zip') | |||||
| $remoteDirectory = 'C:\Windows\Temp\asp-iis-deploy-' + $ReleaseId | |||||
| $remotePackage = $remoteDirectory + '\' + $ReleaseId + '.zip' | |||||
| $remoteInstaller = $remoteDirectory + '\install-iis-release.ps1' | |||||
| try { | |||||
| if (-not $Rollback -and -not $RemotePreflightOnly) { | |||||
| Write-Step 'Staging the full repository for packaging' | |||||
| Copy-ReleaseSource -From $SourcePath -To $packageStage | |||||
| Add-Type -AssemblyName System.IO.Compression.FileSystem | |||||
| [System.IO.Compression.ZipFile]::CreateFromDirectory( | |||||
| $packageStage, | |||||
| $packagePath, | |||||
| [System.IO.Compression.CompressionLevel]::Optimal, | |||||
| $false | |||||
| ) | |||||
| $sha256 = (Get-FileHash -LiteralPath $packagePath -Algorithm SHA256).Hash | |||||
| Write-Host "Package: $packagePath" | |||||
| Write-Host "SHA-256: $sha256" | |||||
| } else { | |||||
| $sha256 = '' | |||||
| } | } | ||||
| Push-Location $WorkDir | |||||
| try { | |||||
| Write-Host "Updating to origin/$Branch" | |||||
| git fetch origin | |||||
| git checkout $Branch | |||||
| & git reset --hard ("origin/" + $Branch) | |||||
| } finally { | |||||
| Pop-Location | |||||
| $remoteArguments = New-Object 'System.Collections.Generic.List[string]' | |||||
| $remoteArguments.Add('&') | |||||
| $remoteArguments.Add((ConvertTo-SingleQuotedPowerShell $remoteInstaller)) | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-SiteName' -Value $SiteName | |||||
| if ($Rollback) { | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-RollbackTo' -Value $RollbackTo | |||||
| } elseif (-not $RemotePreflightOnly) { | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-PackagePath' -Value $remotePackage | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-ReleaseId' -Value $ReleaseId | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-ExpectedSha256' -Value $sha256 | |||||
| } else { | |||||
| $remoteArguments.Add('-PreflightOnly') | |||||
| } | } | ||||
| if((Split-Path $WorkDir -Leaf).ToLower() -eq 'public'){ | |||||
| $WorkDir = Split-Path $WorkDir -Parent | |||||
| if (-not [string]::IsNullOrWhiteSpace($DeployRoot)) { | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-DeployRoot' -Value $DeployRoot | |||||
| } | } | ||||
| $PublicDir = Join-Path $WorkDir 'public' | |||||
| $cfg = Join-Path $PublicDir 'web.config' | |||||
| Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name physicalPath -Value $PublicDir | |||||
| Set-ItemProperty ('IIS:\Sites\' + $SiteName) -Name applicationPool -Value $AppPool | |||||
| Set-ItemProperty ('IIS:\AppPools\' + $AppPool) -Name processModel.identityType -Value NetworkService | |||||
| Set-DataSourceInConfig -ConfigPath $cfg -EffectiveDbPath $effectiveDbPath | |||||
| $dbFolder = Split-Path $effectiveDbPath -Parent | |||||
| if(!(Test-Path $dbFolder)){ | |||||
| Ensure-Dir $dbFolder | |||||
| if (-not [string]::IsNullOrWhiteSpace($BaseUrl)) { | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-BaseUrl' -Value $BaseUrl | |||||
| } | } | ||||
| icacls $dbFolder /grant ("IIS AppPool\" + $AppPool + ":(OI)(CI)(M)") /T | Out-Null | |||||
| Push-Location $WorkDir | |||||
| try { | |||||
| if($RunMigrations){ | |||||
| Write-Host 'Running standard migrations' | |||||
| cscript //nologo scripts\runMigrations.vbs up | |||||
| } | |||||
| if(-not $SkipLegacyIsBusinessMigration){ | |||||
| $legacyPath = Join-Path $WorkDir $LegacyMigrationScript | |||||
| if(!(Test-Path $legacyPath)){ | |||||
| throw "Legacy migration script not found: $legacyPath" | |||||
| } | |||||
| Write-Host 'Running legacy IsBusiness migration' | |||||
| cscript //nologo $legacyPath $effectiveDbPath | |||||
| Add-RemoteArgument -Arguments $remoteArguments -Name '-KeepReleases' -Value $KeepReleases.ToString() | |||||
| if ($RunMigrations) { $remoteArguments.Add('-RunMigrations') } | |||||
| if ($SkipSmokeTest) { $remoteArguments.Add('-SkipSmokeTest') } | |||||
| $remoteScript = $remoteArguments -join ' ' | |||||
| $remoteBytes = [Text.Encoding]::Unicode.GetBytes($remoteScript) | |||||
| $remoteEncodedCommand = [Convert]::ToBase64String($remoteBytes) | |||||
| $remoteCommand = 'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -EncodedCommand ' + $remoteEncodedCommand | |||||
| Write-Host "Remote target: $RemoteTarget (Tailscale/OpenSSH port $RemotePort)" | |||||
| if ($DryRun) { | |||||
| Write-Step 'Dry-run complete; no network connection was made' | |||||
| Write-Host "Would create remote directory: $remoteDirectory" | |||||
| Write-Host "Would copy installer: $installerPath" | |||||
| if (-not $Rollback -and -not $RemotePreflightOnly) { | |||||
| Write-Host "Would copy package: $packagePath" | |||||
| } | } | ||||
| } finally { | |||||
| Pop-Location | |||||
| Write-Host ('Would execute host script: ' + $remoteScript) | |||||
| Write-Host ('Transport command uses PowerShell -EncodedCommand to avoid remote-shell quoting ambiguity.') | |||||
| exit 0 | |||||
| } | } | ||||
| if((Get-WebAppPoolState -Name $AppPool).Value -eq 'Started'){ | |||||
| Restart-WebAppPool -Name $AppPool | |||||
| } else { | |||||
| Start-WebAppPool -Name $AppPool | |||||
| } | |||||
| Start-Website $SiteName | |||||
| Assert-Command $SshExe | |||||
| Assert-Command $ScpExe | |||||
| Start-Sleep -Seconds 1 | |||||
| Write-Step 'Creating remote staging directory over Tailscale/OpenSSH' | |||||
| $mkdirScript = "New-Item -ItemType Directory -Force -Path $(ConvertTo-SingleQuotedPowerShell $remoteDirectory) | Out-Null" | |||||
| $mkdirBytes = [Text.Encoding]::Unicode.GetBytes($mkdirScript) | |||||
| $mkdirEncodedCommand = [Convert]::ToBase64String($mkdirBytes) | |||||
| & $SshExe -p $RemotePort $RemoteTarget ('powershell.exe -NoProfile -NonInteractive -EncodedCommand ' + $mkdirEncodedCommand) | |||||
| if ($LASTEXITCODE -ne 0) { throw 'Remote directory creation failed.' } | |||||
| $paths = @('/','/territories','/households','/householder-names') | |||||
| foreach($path in $paths){ | |||||
| $url = $BaseUrl + $path | |||||
| $response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 30 | |||||
| Write-Host ("OK " + $path + ' -> ' + $response.StatusCode) | |||||
| } | |||||
| Write-Step 'Copying the host installer' | |||||
| & $ScpExe -P $RemotePort $installerPath ($RemoteTarget + ':' + $remoteDirectory.Replace('\', '/') + '/install-iis-release.ps1') | |||||
| if ($LASTEXITCODE -ne 0) { throw 'Installer copy failed.' } | |||||
| Write-Host 'Deploy complete.' | |||||
| } | |||||
| if($UseRemoteSsh -and !$RunRemoteCore -and [string]::IsNullOrWhiteSpace($RemoteTarget)){ | |||||
| $RemoteTarget = Get-DefaultRemoteTargetFromInfo | |||||
| } | |||||
| if($UseRemoteSsh -and !$RunRemoteCore -and -not [string]::IsNullOrWhiteSpace($RemoteTarget)){ | |||||
| Ensure-Command $SshExe | |||||
| Ensure-Command $ScpExe | |||||
| $remoteScriptPath = 'C:\Windows\Temp\deploy-test-territory-git.ps1' | |||||
| $scpDestination = "${RemoteTarget}:C:/Windows/Temp/deploy-test-territory-git.ps1" | |||||
| Write-Host "Copying deploy script to $RemoteTarget" | |||||
| & $ScpExe -P $RemotePort $PSCommandPath $scpDestination | |||||
| if($LASTEXITCODE -ne 0){ throw 'scp failed' } | |||||
| $remoteCommand = New-Object System.Collections.Generic.List[string] | |||||
| @( | |||||
| 'powershell', | |||||
| '-NoProfile', | |||||
| '-ExecutionPolicy', 'Bypass', | |||||
| '-File', (ConvertTo-CmdDoubleQuoted $remoteScriptPath), | |||||
| '-RunRemoteCore', | |||||
| '-Repo', (ConvertTo-CmdDoubleQuoted $Repo), | |||||
| '-Branch', (ConvertTo-CmdDoubleQuoted $Branch), | |||||
| '-SiteName', (ConvertTo-CmdDoubleQuoted $SiteName) | |||||
| ) | ForEach-Object { [void]$remoteCommand.Add($_) } | |||||
| if(-not [string]::IsNullOrWhiteSpace($AppPool)){ | |||||
| [void]$remoteCommand.Add('-AppPool') | |||||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $AppPool)) | |||||
| if (-not $Rollback -and -not $RemotePreflightOnly) { | |||||
| Write-Step 'Copying the release package' | |||||
| & $ScpExe -P $RemotePort $packagePath ($RemoteTarget + ':' + $remoteDirectory.Replace('\', '/') + '/' + $ReleaseId + '.zip') | |||||
| if ($LASTEXITCODE -ne 0) { throw 'Package copy failed.' } | |||||
| } | } | ||||
| if(-not [string]::IsNullOrWhiteSpace($WorkDir)){ | |||||
| [void]$remoteCommand.Add('-WorkDir') | |||||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $WorkDir)) | |||||
| } | |||||
| Write-Step 'Invoking the host-side installer' | |||||
| & $SshExe -p $RemotePort $RemoteTarget $remoteCommand | |||||
| if ($LASTEXITCODE -ne 0) { throw "Remote installer failed with exit code $LASTEXITCODE." } | |||||
| if(-not [string]::IsNullOrWhiteSpace($PublicDir)){ | |||||
| [void]$remoteCommand.Add('-PublicDir') | |||||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $PublicDir)) | |||||
| Write-Step 'Remote operation completed successfully' | |||||
| } finally { | |||||
| if ($KeepPackage -and (Test-Path -LiteralPath $packagePath)) { | |||||
| $keptPath = Join-Path (Get-Location) ([System.IO.Path]::GetFileName($packagePath)) | |||||
| Copy-Item -LiteralPath $packagePath -Destination $keptPath -Force | |||||
| Write-Host "Package retained at $keptPath" | |||||
| } | } | ||||
| if(-not [string]::IsNullOrWhiteSpace($BaseUrl)){ | |||||
| [void]$remoteCommand.Add('-BaseUrl') | |||||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $BaseUrl)) | |||||
| if (Test-Path -LiteralPath $workRoot) { | |||||
| Remove-Item -LiteralPath $workRoot -Recurse -Force | |||||
| } | } | ||||
| if(-not [string]::IsNullOrWhiteSpace($DbPath)){ | |||||
| [void]$remoteCommand.Add('-DbPath') | |||||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $DbPath)) | |||||
| } | |||||
| if(-not [string]::IsNullOrWhiteSpace($LegacyMigrationScript)){ | |||||
| [void]$remoteCommand.Add('-LegacyMigrationScript') | |||||
| [void]$remoteCommand.Add((ConvertTo-CmdDoubleQuoted $LegacyMigrationScript)) | |||||
| } | |||||
| if($RunMigrations){ $remoteCommand += '-RunMigrations' } | |||||
| if($SkipLegacyIsBusinessMigration){ $remoteCommand += '-SkipLegacyIsBusinessMigration' } | |||||
| Write-Host "Executing remote deploy on $RemoteTarget" | |||||
| & $SshExe -p $RemotePort $RemoteTarget ($remoteCommand -join ' ') | |||||
| if($LASTEXITCODE -ne 0){ throw 'remote deploy failed' } | |||||
| exit 0 | |||||
| } | } | ||||
| Invoke-DeployCore | |||||
Powered by TurnKey Linux.