| @@ -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( | |||
| [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, | |||
| [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]$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' | |||
| 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) | |||
| 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( | |||
| [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.