[CmdletBinding()] param( [ValidateSet('Plan', 'Apply')] [string]$Mode = 'Plan', [string]$ServerHost = '2.27.25.56', [string]$RemoteUser = 'root', [string]$WslDistribution = 'Ubuntu-24.04', [switch]$PrepareOnly ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest if ($ServerHost -notmatch '^[A-Za-z0-9.-]+$' -or $RemoteUser -notmatch '^[a-z_][a-z0-9_-]*$') { throw 'Invalid SSH target' } $projectRoot = $PSScriptRoot $releaseRoot = Join-Path $projectRoot 'build\capacity50-release' $payloadRoot = Join-Path $releaseRoot 'payload\app' $deploymentRoot = Join-Path $projectRoot 'deploy\capacity50' $sourceNames = @( 'capacity_model.py', 'scanner_db.py', 'worker_assignment.py', 'worker_api.py', 'jsonl_projector.py', 'runtime_document.py', 'lifecycle_authority.py', 'config.linux.yaml' ) if (Test-Path -LiteralPath $releaseRoot) { Remove-Item -LiteralPath $releaseRoot -Recurse -Force } New-Item -ItemType Directory -Path $payloadRoot -Force | Out-Null foreach ($name in $sourceNames) { $source = Join-Path $projectRoot "app\$name" if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { throw "Missing release source: $source" } Copy-Item -LiteralPath $source -Destination (Join-Path $payloadRoot $name) } foreach ($name in @( 'Dockerfile', 'deploy.sh', 'render_config.py', 'release_stopped_pipeline_leases.py' )) { Copy-Item -LiteralPath (Join-Path $deploymentRoot $name) -Destination (Join-Path $releaseRoot $name) } $checksumLines = Get-ChildItem -LiteralPath $releaseRoot -File -Recurse | Where-Object Name -ne 'checksums.sha256' | Sort-Object FullName | ForEach-Object { $relative = $_.FullName.Substring($releaseRoot.Length + 1).Replace('\', '/') $hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() "$hash $relative" } [IO.File]::WriteAllLines( (Join-Path $releaseRoot 'checksums.sha256'), [string[]]$checksumLines, [Text.UTF8Encoding]::new($false) ) Write-Output "Prepared release bundle: $releaseRoot" if ($PrepareOnly) { return } $wslPath = (& wsl.exe -d $WslDistribution -- wslpath -a ($releaseRoot -replace '\', '/')).Trim() if (-not $wslPath.StartsWith('/')) { throw 'Could not resolve the release path inside WSL' } $remoteMode = $Mode.ToLowerInvariant() $remoteCommand = "set -eu; install -d -m 0700 /var/lib/truf-deploy/stage; stage=`$(mktemp -d /var/lib/truf-deploy/stage/capacity50.XXXXXXXX); trap `"rm -rf `$stage`" EXIT; tar -xf - -C `"`$stage`"; cd `"`$stage`"; sha256sum -c checksums.sha256; bash deploy.sh $remoteMode `"`$stage`"" if ($wslPath.Contains("'")) { throw 'Release path cannot contain an apostrophe' } $bashPath = "'$wslPath'" $target = "$RemoteUser@$ServerHost" $bashCommand = "tar -C $bashPath -cf - . | ssh -o StrictHostKeyChecking=yes $target '$remoteCommand'" & wsl.exe -d $WslDistribution -- bash -lc $bashCommand if ($LASTEXITCODE -ne 0) { throw "Remote deployment failed with exit code $LASTEXITCODE" }