diff --git a/Documentation/deploy-dev-to-prod.md b/Documentation/deploy-dev-to-prod.md index 739f258..9d45500 100644 --- a/Documentation/deploy-dev-to-prod.md +++ b/Documentation/deploy-dev-to-prod.md @@ -36,7 +36,7 @@ For a typical weekly release, use the orchestrator instead of running each step - `smb.user` / `smb.password` — optional; maps `\\host\share` before image sync - `backupFile` — optional fixed path; otherwise uses the newest `gallery_dev_data_*.txt` after backup -3. Prerequisites still apply: Docker Desktop running, `docker login gitea.mysuperlab.netcraze.pro`, [`infra/docker/.env.prod`](../infra/docker/.env.prod) present. +3. Prerequisites still apply: **`npm run dev:web` running** (Vite `:5173` + API `:3451`), Docker Desktop running, `docker login gitea.mysuperlab.netcraze.pro`, [`infra/docker/.env.prod`](../infra/docker/.env.prod) present. ### Run @@ -56,7 +56,7 @@ npm run devtoprod:release -- -DryRun | Config step | Maps to runbook | |-------------|-----------------| -| `validateBuild` | Step 0 — `prod:build` + dev `/api/bounds` check | +| `validateBuild` | Step 0 — `prod:build` + dev `/api/bounds` check (API `:3451` and/or Vite `:5173` proxy) | | `gitCommitPush` | Step 1 — `git add`, commit, push | | `thumbnails` | Step 2 — `devtoprod:thumbnails` | | `backupDev` | Step 3 — `dev:db:backup` | diff --git a/infra/deploy/devtoprod.config.example.json b/infra/deploy/devtoprod.config.example.json index 90011b4..08239e9 100644 --- a/infra/deploy/devtoprod.config.example.json +++ b/infra/deploy/devtoprod.config.example.json @@ -29,6 +29,7 @@ "password": "" }, "verify": { + "devApiUrl": "http://127.0.0.1:3451/api/bounds", "devLanUrl": "http://192.168.10.70:5173/api/bounds", "devUrl": "https://devgallery.mysuperlab.netcraze.pro/api/bounds", "lanUrl": "http://192.168.10.122:5173/api/bounds", diff --git a/infra/scripts/deploy-dev-to-prod.ps1 b/infra/scripts/deploy-dev-to-prod.ps1 index a2d9f3f..447abde 100644 --- a/infra/scripts/deploy-dev-to-prod.ps1 +++ b/infra/scripts/deploy-dev-to-prod.ps1 @@ -203,29 +203,57 @@ function Get-LatestDevBackup { return $latest.FullName } +function Get-DevApiPort { + $envFile = Join-Path $RepoRoot '.env' + $port = '3451' + if (Test-Path $envFile) { + foreach ($line in Get-Content -LiteralPath $envFile) { + if ($line -match '^\s*PORT\s*=\s*(\d+)\s*$') { + $port = $Matches[1] + break + } + } + } + return $port +} + function Test-BoundsJson { param( [string]$Url, [switch]$Insecure, [int]$MaxTimeSeconds = 10, - [int]$Retries = 3 + [int]$Retries = 3, + [ref]$Detail ) + $lastDetail = 'no response' for ($attempt = 1; $attempt -le $Retries; $attempt++) { - $curlArgs = @('-s', '-f', '--max-time', $MaxTimeSeconds) + $curlArgs = @('-s', '-w', "`nHTTP_CODE:%{http_code}", '--max-time', $MaxTimeSeconds) if ($Insecure) { $curlArgs += '-k' } $curlArgs += $Url - $body = & curl.exe @curlArgs 2>$null - if ($LASTEXITCODE -eq 0 -and $body) { + $raw = (& curl.exe @curlArgs 2>&1 | Out-String).TrimEnd() + $httpCode = $null + $body = $raw + if ($raw -match '(?s)^(.*)HTTP_CODE:(\d+)\s*$') { + $body = $Matches[1].TrimEnd() + $httpCode = $Matches[2] + } + + if ($httpCode -eq '200' -and $body) { try { $json = $body | ConvertFrom-Json if ($null -ne $json.min_year -and $null -ne $json.max_year) { return $true } + $lastDetail = 'HTTP 200 but JSON missing min_year/max_year' } catch { - # Continue retries below. + $lastDetail = 'HTTP 200 but response is not valid JSON' } + } elseif ($httpCode) { + $lastDetail = "HTTP $httpCode" + } else { + $lastDetail = "curl exit $LASTEXITCODE" } if ($attempt -lt $Retries) { @@ -233,6 +261,7 @@ function Test-BoundsJson { } } + if ($Detail) { $Detail.Value = $lastDetail } return $false } @@ -282,9 +311,13 @@ if (Get-StepEnabled $steps 'validateBuild') { if ($code -ne 0) { return $code } # Dev-only endpoints. Do not use verify.lanUrl here (that is prod on TrueNAS). + $devApiPort = Get-DevApiPort $devCandidates = @( + "http://127.0.0.1:$devApiPort/api/bounds" + 'http://localhost:5173/api/bounds' 'http://192.168.10.70:5173/api/bounds' ) + if ($cfg.verify.devApiUrl) { $devCandidates += $cfg.verify.devApiUrl } if ($cfg.verify.devLanUrl) { $devCandidates += $cfg.verify.devLanUrl } if ($cfg.verify.devUrl) { $devCandidates += $cfg.verify.devUrl } $devCandidates = $devCandidates | Select-Object -Unique @@ -293,16 +326,19 @@ if (Get-StepEnabled $steps 'validateBuild') { foreach ($u in $devCandidates) { Write-Host "Checking dev /api/bounds: $u" $useInsecure = $u.StartsWith('https://', [System.StringComparison]::OrdinalIgnoreCase) - if (Test-BoundsJson -Url $u -Insecure:$useInsecure -MaxTimeSeconds 10 -Retries 3) { + $detail = '' + if (Test-BoundsJson -Url $u -Insecure:$useInsecure -MaxTimeSeconds 10 -Retries 3 -Detail ([ref]$detail)) { Write-Host "Dev API OK: $u" $devOk = $true break } - Write-Host "Dev API check failed for: $u" -ForegroundColor DarkYellow + Write-Host "Dev API check failed for: $u ($detail)" -ForegroundColor DarkYellow } if (-not $devOk) { Write-Host "Dev API check failed on all URLs." -ForegroundColor Red + Write-Host "Start the full dev stack before release: npm run dev:web" -ForegroundColor Yellow + Write-Host "Vite on :5173 alone is not enough - the API must listen on :$devApiPort (PORT in .env)." -ForegroundColor Yellow return 1 } return 0