From d5d313375cfb7b4ba3e26c6e4833be9eb151bd8b Mon Sep 17 00:00:00 2001 From: gti Date: Thu, 24 Sep 2026 10:59:25 -0300 Subject: [PATCH] fix: deploy test gate uses a per-run pytest basetemp Co-Authored-By: Claude Sonnet 5 --- scripts/deploy_hub.ps1 | 158 +++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/scripts/deploy_hub.ps1 b/scripts/deploy_hub.ps1 index b830444..ba7174e 100644 --- a/scripts/deploy_hub.ps1 +++ b/scripts/deploy_hub.ps1 @@ -1,78 +1,80 @@ -# Official production deploy of the GTI Fiscal Hub (10.0.10.117). Run by the OPERATOR, outside Claude Code: -# powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 [-PublishAgent] [-Version 1.1.5] [-SkipTests] [-DryRun] -# SSH password: env GTI_SSH_PASSWORD, else GTI_SSH_PASSWORD=... in .env.local (gitignored). Never printed or logged. -[CmdletBinding()] -param( - [string]$Version = "", - [switch]$PublishAgent, - [switch]$SkipTests, - [switch]$DryRun, - [string]$Python = "python" -) -$ErrorActionPreference = "Stop" -$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path -Set-Location $Root -$LogDir = Join-Path $Root "logs"; New-Item -ItemType Directory -Force $LogDir | Out-Null -$Log = Join-Path $LogDir ("deploy-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss")) -Start-Transcript -Path $Log | Out-Null -try { - if (-not $Version) { $Version = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() } - - # 3. credential: process env wins, then .env.local - $src = "env" - if (-not $env:GTI_SSH_PASSWORD) { - $envFile = Join-Path $Root ".env.local" - if (Test-Path $envFile) { - $line = Get-Content $envFile | Where-Object { $_ -match '^\s*GTI_SSH_PASSWORD\s*=' } | Select-Object -First 1 - if ($line) { $env:GTI_SSH_PASSWORD = ($line -replace '^\s*GTI_SSH_PASSWORD\s*=\s*', '').Trim().Trim('"').Trim("'"); $src = ".env.local" } - } - } - if (-not $env:GTI_SSH_PASSWORD) { throw "GTI_SSH_PASSWORD not found in the environment or .env.local" } - Write-Host "SSH_AUTH_SOURCE=$src PASSWORD_LOGGED=false" - - # 2. local pre-checks - git check-ignore -q .env.local - if ($LASTEXITCODE -ne 0 -and (Test-Path ".env.local")) { throw ".env.local is not gitignored" } - $dirty = git status --porcelain - if ($dirty) { throw "working tree is not clean - commit or stash first:`n$dirty" } - $branch = git rev-parse --abbrev-ref HEAD; $head = git rev-parse --short HEAD - Write-Host "BRANCH=$branch HEAD=$head VERSION=$Version" - $fileVersion = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() - if ($fileVersion -ne $Version) { throw "release/VERSION is $fileVersion but -Version is $Version" } - if ($PublishAgent) { - $exe = Join-Path $Root "dist\installer\GTIFiscalAgent-Setup-$Version.exe" - if (-not (Test-Path $exe)) { throw "installer not built: $exe (run scripts\build_windows_installer.ps1)" } - } - if (-not $SkipTests) { - & $Python -m pytest tests central/tests agent/tests -q -p no:cacheprovider - if ($LASTEXITCODE -ne 0) { throw "tests failed - deploy aborted" } - } - - # 4. route/SSH recovery: reconnect the existing Windows VPN profile "GTI" (saved credentials) and retry - $reachable = $false - for ($i = 1; $i -le 3 -and -not $reachable; $i++) { - $reachable = (Test-NetConnection 10.0.10.117 -Port 22 -WarningAction SilentlyContinue).TcpTestSucceeded - if (-not $reachable) { - Write-Host "SSH_UNREACHABLE attempt=$i - reconnecting VPN profile GTI" - rasdial GTI | Out-Null - Start-Sleep -Seconds (10 * $i) - } - } - if (-not $reachable) { throw "FAILED_STAGE=network REMOTE_ERROR=10.0.10.117:22 unreachable after 3 attempts (VPN profile GTI)" } - Write-Host "SSH_ROUTE_OK" - Remove-Item (Join-Path $LogDir "last_deploy_result.json") -ErrorAction SilentlyContinue - - $deployArgs = @("scripts\deploy_hub_remote.py", "--version", $Version) - if ($DryRun) { $deployArgs += "--dry-run" } - if ($PublishAgent) { $deployArgs += "--publish-agent" } - & $Python @deployArgs - if ($LASTEXITCODE -ne 0) { - $res = Join-Path $LogDir "last_deploy_result.json" - $detail = if (Test-Path $res) { $j = Get-Content $res -Raw | ConvertFrom-Json; "FAILED_STAGE/ERROR=$($j.error) ROLLBACK_REQUIRED=$($j.rollback_required)" } else { "no result file (see $Log)" } - throw "remote deploy failed: $detail" - } - Write-Host "DEPLOY_SCRIPT_OK log=$Log" -} finally { - Remove-Item Env:GTI_SSH_PASSWORD -ErrorAction SilentlyContinue - Stop-Transcript | Out-Null -} +# Official production deploy of the GTI Fiscal Hub (10.0.10.117). Run by the OPERATOR, outside Claude Code: +# powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1 [-PublishAgent] [-Version 1.1.5] [-SkipTests] [-DryRun] +# SSH password: env GTI_SSH_PASSWORD, else GTI_SSH_PASSWORD=... in .env.local (gitignored). Never printed or logged. +[CmdletBinding()] +param( + [string]$Version = "", + [switch]$PublishAgent, + [switch]$SkipTests, + [switch]$DryRun, + [string]$Python = "python" +) +$ErrorActionPreference = "Stop" +$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +Set-Location $Root +$LogDir = Join-Path $Root "logs"; New-Item -ItemType Directory -Force $LogDir | Out-Null +$Log = Join-Path $LogDir ("deploy-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss")) +Start-Transcript -Path $Log | Out-Null +try { + if (-not $Version) { $Version = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() } + + # 3. credential: process env wins, then .env.local + $src = "env" + if (-not $env:GTI_SSH_PASSWORD) { + $envFile = Join-Path $Root ".env.local" + if (Test-Path $envFile) { + $line = Get-Content $envFile | Where-Object { $_ -match '^\s*GTI_SSH_PASSWORD\s*=' } | Select-Object -First 1 + if ($line) { $env:GTI_SSH_PASSWORD = ($line -replace '^\s*GTI_SSH_PASSWORD\s*=\s*', '').Trim().Trim('"').Trim("'"); $src = ".env.local" } + } + } + if (-not $env:GTI_SSH_PASSWORD) { throw "GTI_SSH_PASSWORD not found in the environment or .env.local" } + Write-Host "SSH_AUTH_SOURCE=$src PASSWORD_LOGGED=false" + + # 2. local pre-checks + git check-ignore -q .env.local + if ($LASTEXITCODE -ne 0 -and (Test-Path ".env.local")) { throw ".env.local is not gitignored" } + $dirty = git status --porcelain + if ($dirty) { throw "working tree is not clean - commit or stash first:`n$dirty" } + $branch = git rev-parse --abbrev-ref HEAD; $head = git rev-parse --short HEAD + Write-Host "BRANCH=$branch HEAD=$head VERSION=$Version" + $fileVersion = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() + if ($fileVersion -ne $Version) { throw "release/VERSION is $fileVersion but -Version is $Version" } + if ($PublishAgent) { + $exe = Join-Path $Root "dist\installer\GTIFiscalAgent-Setup-$Version.exe" + if (-not (Test-Path $exe)) { throw "installer not built: $exe (run scripts\build_windows_installer.ps1)" } + } + if (-not $SkipTests) { + # per-run basetemp: some tests ACL-lock their tmp dirs, which breaks pytest's shared default temp root + $base = Join-Path $Root (".pytmp un-" + (Get-Date -Format "yyyyMMddHHmmss")) + & $Python -m pytest tests central/tests agent/tests -q -p no:cacheprovider --basetemp=$base + if ($LASTEXITCODE -ne 0) { throw "tests failed - deploy aborted" } + } + + # 4. route/SSH recovery: reconnect the existing Windows VPN profile "GTI" (saved credentials) and retry + $reachable = $false + for ($i = 1; $i -le 3 -and -not $reachable; $i++) { + $reachable = (Test-NetConnection 10.0.10.117 -Port 22 -WarningAction SilentlyContinue).TcpTestSucceeded + if (-not $reachable) { + Write-Host "SSH_UNREACHABLE attempt=$i - reconnecting VPN profile GTI" + rasdial GTI | Out-Null + Start-Sleep -Seconds (10 * $i) + } + } + if (-not $reachable) { throw "FAILED_STAGE=network REMOTE_ERROR=10.0.10.117:22 unreachable after 3 attempts (VPN profile GTI)" } + Write-Host "SSH_ROUTE_OK" + Remove-Item (Join-Path $LogDir "last_deploy_result.json") -ErrorAction SilentlyContinue + + $deployArgs = @("scripts\deploy_hub_remote.py", "--version", $Version) + if ($DryRun) { $deployArgs += "--dry-run" } + if ($PublishAgent) { $deployArgs += "--publish-agent" } + & $Python @deployArgs + if ($LASTEXITCODE -ne 0) { + $res = Join-Path $LogDir "last_deploy_result.json" + $detail = if (Test-Path $res) { $j = Get-Content $res -Raw | ConvertFrom-Json; "FAILED_STAGE/ERROR=$($j.error) ROLLBACK_REQUIRED=$($j.rollback_required)" } else { "no result file (see $Log)" } + throw "remote deploy failed: $detail" + } + Write-Host "DEPLOY_SCRIPT_OK log=$Log" +} finally { + Remove-Item Env:GTI_SSH_PASSWORD -ErrorAction SilentlyContinue + Stop-Transcript | Out-Null +}