Skip to content

Commit 652a7bd

Browse files
Enhance deployment workflow and add smoke test script for API health verification
- Updated the deployment workflow to include a new smoke test script that checks the health of the API after deployment. - Added a `web.config` file for IIS configuration to support the application. - Improved documentation to clarify the use of `SITE_URL` for smoke testing and updated secret requirements. - Refactored the smoke test logic to handle multiple health check paths and provide better error reporting.
1 parent 397d183 commit 652a7bd

5 files changed

Lines changed: 176 additions & 42 deletions

File tree

.github/workflows/deploy-monsterasp-ftp.yml

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Build, publish, and deploy the API to MonsterASP.NET via FTPS.
22
# Required secrets: FTP_SERVER, FTP_USERNAME, FTP_PASSWORD,
33
# PRODUCTION_CONNECTION_STRING, JWT_KEY, ADMIN_PASSWORD
4-
# Optional secrets: JWT_ISSUER, JWT_AUDIENCE, FTP_SERVER_DIR, ALLOWED_ORIGINS, ALLOWED_HOSTS
4+
# Optional secrets: JWT_ISSUER, JWT_AUDIENCE, FTP_SERVER_DIR, ALLOWED_ORIGINS, ALLOWED_HOSTS,
5+
# SITE_URL (public https URL, e.g. https://site1234.monsterasp.net)
56

67
name: Deploy to MonsterASP (FTPS)
78

@@ -126,6 +127,12 @@ jobs:
126127
$json = $settings | ConvertTo-Json -Depth 6
127128
Set-Content -Path "./publish/appsettings.Production.json" -Value $json -Encoding utf8
128129
130+
- name: Apply IIS web.config for MonsterASP
131+
shell: pwsh
132+
run: |
133+
Copy-Item deploy/web.config ./publish/web.config -Force
134+
New-Item -ItemType Directory -Force -Path ./publish/logs | Out-Null
135+
129136
# IIS locks Api.dll while the site runs. app_offline.htm stops the app before file replace.
130137
- name: Stage app_offline.htm
131138
shell: pwsh
@@ -179,43 +186,11 @@ jobs:
179186
shell: pwsh
180187
env:
181188
FTP_SERVER: ${{ secrets.FTP_SERVER }}
189+
SITE_URL: ${{ secrets.SITE_URL }}
182190
run: |
183-
# MonsterASP/IIS routes the ASP.NET app under /api/*; root /health often 404.
184-
$candidates = @(
185-
"https://$env:FTP_SERVER/api/v1/health",
186-
"https://$env:FTP_SERVER/health"
187-
)
188-
$maxAttempts = 6
189-
$attempt = 0
190-
$success = $false
191-
while ($attempt -lt $maxAttempts -and -not $success) {
192-
$attempt++
193-
foreach ($url in $candidates) {
194-
Write-Host "Smoke testing $url (attempt $attempt) ..."
195-
try {
196-
$response = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 15
197-
if ($response.StatusCode -ne 200) {
198-
Write-Warning "Attempt $attempt – $url returned $($response.StatusCode)"
199-
continue
200-
}
201-
if ($url -like '*/api/v1/health') {
202-
$body = $response.Content | ConvertFrom-Json
203-
if ($body.data.status -ne 'healthy') {
204-
Write-Warning "Attempt $attempt – API envelope status is not healthy: $($response.Content)"
205-
continue
206-
}
207-
}
208-
Write-Host "Health check passed (attempt $attempt): $($response.Content)"
209-
$success = $true
210-
break
211-
} catch {
212-
Write-Warning "Attempt $attempt – $url – $($_.Exception.Message)"
213-
}
214-
}
215-
if (-not $success) {
216-
Start-Sleep -Seconds 10
217-
}
218-
}
219-
if (-not $success) {
220-
throw "Health check failed after $maxAttempts attempts. Tried: $($candidates -join ', ')"
221-
}
191+
./deploy/smoke-test.ps1 `
192+
-FtpServer $env:FTP_SERVER `
193+
-SiteUrl $env:SITE_URL `
194+
-MaxAttempts 8 `
195+
-InitialDelaySeconds 30 `
196+
-RetryDelaySeconds 15

deploy/smoke-test.ps1

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
param(
2+
[Parameter(Mandatory = $true)]
3+
[string]$FtpServer,
4+
5+
[string]$SiteUrl = "",
6+
7+
[int]$MaxAttempts = 8,
8+
9+
[int]$InitialDelaySeconds = 30,
10+
11+
[int]$RetryDelaySeconds = 15
12+
)
13+
14+
Set-StrictMode -Version Latest
15+
$ErrorActionPreference = "Stop"
16+
17+
function Normalize-HostName {
18+
param([string]$Value)
19+
20+
if ([string]::IsNullOrWhiteSpace($Value)) {
21+
return $null
22+
}
23+
24+
$hostName = $Value.Trim().TrimEnd('/')
25+
$hostName = $hostName -replace '^https?://', ''
26+
return $hostName
27+
}
28+
29+
function Add-UniqueBaseUrl {
30+
param(
31+
[System.Collections.Generic.List[string]]$List,
32+
[string]$HostName
33+
)
34+
35+
if ([string]::IsNullOrWhiteSpace($HostName)) {
36+
return
37+
}
38+
39+
foreach ($scheme in @("https", "http")) {
40+
$base = "$scheme://$HostName"
41+
if (-not $List.Contains($base)) {
42+
[void]$List.Add($base)
43+
}
44+
}
45+
}
46+
47+
$baseUrls = [System.Collections.Generic.List[string]]::new()
48+
49+
$siteHost = Normalize-HostName $SiteUrl
50+
if ($siteHost) {
51+
Add-UniqueBaseUrl -List $baseUrls -HostName $siteHost
52+
}
53+
54+
$ftpHost = Normalize-HostName $FtpServer
55+
if ($ftpHost) {
56+
Add-UniqueBaseUrl -List $baseUrls -HostName $ftpHost
57+
58+
if ($ftpHost -match '\.siteasp\.net$') {
59+
$publicHost = $ftpHost -replace '\.siteasp\.net$', '.monsterasp.net'
60+
Add-UniqueBaseUrl -List $baseUrls -HostName $publicHost
61+
}
62+
}
63+
64+
if ($baseUrls.Count -eq 0) {
65+
throw "No smoke-test base URLs. Set FTP_SERVER and/or SITE_URL."
66+
}
67+
68+
$healthPaths = @(
69+
"/api/v1/health",
70+
"/health",
71+
"/",
72+
"/index.html"
73+
)
74+
75+
Write-Host "Smoke-test base URLs: $($baseUrls -join ', ')"
76+
Write-Host "Waiting $InitialDelaySeconds s for IIS/app startup (migrations may run on first boot) ..."
77+
Start-Sleep -Seconds $InitialDelaySeconds
78+
79+
$success = $false
80+
$lastErrors = [System.Collections.Generic.List[string]]::new()
81+
82+
:attemptLoop for ($attempt = 1; $attempt -le $MaxAttempts -and -not $success; $attempt++) {
83+
foreach ($base in $baseUrls) {
84+
foreach ($path in $healthPaths) {
85+
$url = "$base$path"
86+
Write-Host "Attempt $attempt – GET $url"
87+
88+
try {
89+
$response = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 30
90+
91+
if ($path -eq "/api/v1/health") {
92+
if ($response.StatusCode -ne 200) {
93+
[void]$lastErrors.Add("$url -> HTTP $($response.StatusCode)")
94+
continue
95+
}
96+
97+
$body = $response.Content | ConvertFrom-Json
98+
if ($body.data.status -ne "healthy") {
99+
[void]$lastErrors.Add("$url -> API status not healthy: $($response.Content)")
100+
continue
101+
}
102+
103+
Write-Host "Health check passed: $url"
104+
Write-Host $response.Content
105+
$success = $true
106+
break attemptLoop
107+
}
108+
109+
if ($path -in @("/", "/index.html") -and $response.StatusCode -eq 200) {
110+
Write-Host "Site is up at $url (HTTP 200). API route not verified yet."
111+
}
112+
}
113+
catch {
114+
[void]$lastErrors.Add("$url -> $($_.Exception.Message)")
115+
}
116+
}
117+
}
118+
119+
if (-not $success -and $attempt -lt $MaxAttempts) {
120+
Write-Host "Retrying in $RetryDelaySeconds s ..."
121+
Start-Sleep -Seconds $RetryDelaySeconds
122+
}
123+
}
124+
125+
if (-not $success) {
126+
Write-Host ""
127+
Write-Host "Recent failures:"
128+
$lastErrors | Select-Object -Last 12 | ForEach-Object { Write-Host " - $_" }
129+
Write-Host ""
130+
Write-Host "Tips:"
131+
Write-Host " - Set SITE_URL to your public URL (e.g. https://site1234.monsterasp.net), not the FTP host."
132+
Write-Host " - FTP host is often siteXXXX.siteasp.net; the website is usually siteXXXX.monsterasp.net."
133+
Write-Host " - In MonsterASP control panel: Websites -> .NET version = .NET 10, restart site."
134+
Write-Host " - Check wwwroot/logs/stdout*.log on FTP if the app fails to start."
135+
136+
throw "Health check failed after $MaxAttempts attempts."
137+
}

deploy/web.config

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<location path="." inheritInChildApplications="false">
4+
<system.webServer>
5+
<handlers>
6+
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
7+
</handlers>
8+
<aspNetCore processPath=".\Api.exe"
9+
stdoutLogEnabled="true"
10+
stdoutLogFile=".\logs\stdout"
11+
hostingModel="inprocess">
12+
<environmentVariables>
13+
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
14+
</environmentVariables>
15+
</aspNetCore>
16+
</system.webServer>
17+
</location>
18+
</configuration>

docs/DEPLOYMENT.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ Open **Settings → Secrets and variables → Actions → New repository secret*
4444

4545
| Secret | Required | Example / notes |
4646
|--------|----------|-----------------|
47-
| `FTP_SERVER` | Yes | `site1234.siteasp.net` |
47+
| `FTP_SERVER` | Yes | FTP host only: `site1234.siteasp.net` (no `https://`) |
4848
| `FTP_USERNAME` | Yes | `site1234` |
4949
| `FTP_PASSWORD` | Yes | FTP password from control panel |
5050
| `FTP_SERVER_DIR` | No | Default `/wwwroot/` |
51+
| `SITE_URL` | No | **Recommended.** Public site URL for smoke test, e.g. `https://site1234.monsterasp.net`. FTP host (`*.siteasp.net`) is not the browser URL and often returns 404. |
5152
| `FTP_PORT` | No | Default `21` |
5253
| `PRODUCTION_CONNECTION_STRING` | Yes | MonsterASP MSSQL connection string |
5354
| `JWT_KEY` | Yes | Random string, **≥ 32 characters** |
@@ -89,7 +90,9 @@ dotnet run --project src/Api
8990

9091
Monitor the workflow log. On success, browse `https://<your-site>.monsterasp.net/` (or your assigned subdomain).
9192

92-
The deploy workflow smoke test calls **`GET /api/v1/health`** (same as the site landing page). Root **`/health`** is an optional EF/database probe and may return 404 on MonsterASP depending on IIS routing.
93+
The deploy smoke test calls **`GET /api/v1/health`** on your public site URL. It auto-tries `https://<ftp-host-with-siteasp-replaced-by-monsterasp.net>` when `SITE_URL` is not set. Set **`SITE_URL`** if you use a custom domain.
94+
95+
If the smoke test fails but FTP deploy succeeded, open `https://<your-site>/api/v1/health` in a browser and check `wwwroot/logs/stdout*.log` on FTP for startup errors (migrations, connection string, .NET version in control panel).
9396

9497
---
9598

src/Api/Api.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<TargetFramework>net10.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
7+
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
78
</PropertyGroup>
89

910
<ItemGroup>

0 commit comments

Comments
 (0)