[CLAUDE] CICD: rewrite workflow for local deploy on self-hosted runner
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 5s

VPS runner has only windows-latest label (no ubuntu) and lives on the
same VPS as IIS, so WinRM + multi-job artifact handoff is unnecessary.

Changes:
- Single build-deploy job on windows-latest self-hosted
- Uses pre-installed Node 20 and .NET 10 SDK (avoids 500MB setup-*
  downloads per run)
- Deploys directly to C:\inetpub\solution-erp\ — no WinRM
- appsettings.Production.json rendered from template via secrets
  (ConvertFrom-Json + ConvertTo-Json keeps JSON structure intact)
- Fixed site name mismatch: SolutionErp-Api (was SolutionErpApi)
- Removed IIS_HOST/USER/PASSWORD secrets — no longer needed
- Added smoke test step hitting https://api.huypham.vn/health/live

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 14:25:02 +07:00
parent 45452765e3
commit ccfcfb4907

View File

@ -1,13 +1,13 @@
# Gitea Actions CI/CD build + deploy SOLUTION_ERP lên IIS Windows Server.
# Trigger: push vào branch main, hoặc manual.
# Gitea Actions CI/CD - build + deploy SOLUTION_ERP to IIS on same VPS.
# Trigger: push to main, or manual dispatch.
#
# Chạy trên Windows self-hosted runner (vì cần IIS + Word COM cho .doc convert optional).
# Secrets cần set trong Gitea repo settings:
# - IIS_HOST (hostname hoặc IP)
# - IIS_USER (Windows user có admin + WinRM)
# - IIS_PASSWORD
# - JWT_SECRET (64+ chars random — dùng trong appsettings.Production.json)
# - DB_CONNECTION (connection string production)
# Self-hosted Windows runner on VPS (shared with VIETREPORT). Runner has:
# - git, .NET 10 SDK, Node 20, IIS
# - Can deploy locally (no WinRM needed)
#
# Secrets required in Gitea repo settings:
# - JWT_SECRET (64+ chars random)
# - DB_CONNECTION (full connection string with vrapp password)
name: Deploy SOLUTION_ERP
@ -17,102 +17,105 @@ on:
workflow_dispatch:
jobs:
build-backend:
build-deploy:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore + Build BE
run: |
dotnet restore SolutionErp.slnx
dotnet build SolutionErp.slnx --no-restore --configuration Release
- name: Publish Api
run: >
dotnet publish src/Backend/SolutionErp.Api/SolutionErp.Api.csproj
--no-build --configuration Release
--output artifacts/api
--runtime win-x64 --self-contained false
- uses: actions/upload-artifact@v4
with:
name: backend-api
path: artifacts/api/
build-fe-admin:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: 'fe-admin/.nvmrc' # pin 20.x (gotcha NamGroup)
cache: 'npm'
cache-dependency-path: 'fe-admin/package-lock.json'
- run: npm ci
working-directory: fe-admin
- run: npm run build
working-directory: fe-admin
- uses: actions/upload-artifact@v4
with:
name: fe-admin-dist
path: fe-admin/dist/
build-fe-user:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: 'fe-user/.nvmrc'
cache: 'npm'
cache-dependency-path: 'fe-user/package-lock.json'
- run: npm ci
working-directory: fe-user
- run: npm run build
working-directory: fe-user
- uses: actions/upload-artifact@v4
with:
name: fe-user-dist
path: fe-user/dist/
deploy-iis:
needs: [build-backend, build-fe-admin, build-fe-user]
runs-on: windows-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Zip artifacts
- name: Add dotnet + node to PATH
shell: pwsh
run: |
Compress-Archive -Path artifacts/backend-api/* -DestinationPath api.zip -Force
Compress-Archive -Path artifacts/fe-admin-dist/* -DestinationPath fe-admin.zip -Force
Compress-Archive -Path artifacts/fe-user-dist/* -DestinationPath fe-user.zip -Force
echo "C:\Program Files\dotnet" >> $env:GITHUB_PATH
echo "C:\Program Files\nodejs" >> $env:GITHUB_PATH
- name: Copy + deploy via WinRM
- name: Show tool versions
shell: pwsh
run: |
& 'C:\Program Files\dotnet\dotnet.exe' --version
& 'C:\Program Files\nodejs\node.exe' --version
& 'C:\Program Files\nodejs\npm.cmd' --version
- name: Build backend
shell: pwsh
run: |
& 'C:\Program Files\dotnet\dotnet.exe' restore SolutionErp.slnx
& 'C:\Program Files\dotnet\dotnet.exe' publish src/Backend/SolutionErp.Api/SolutionErp.Api.csproj `
--configuration Release `
--output out/api `
--runtime win-x64 `
--self-contained false
- name: Build fe-admin
shell: pwsh
working-directory: fe-admin
run: |
& 'C:\Program Files\nodejs\npm.cmd' ci
& 'C:\Program Files\nodejs\npm.cmd' run build
- name: Build fe-user
shell: pwsh
working-directory: fe-user
run: |
& 'C:\Program Files\nodejs\npm.cmd' ci
& 'C:\Program Files\nodejs\npm.cmd' run build
- name: Deploy to IIS (local)
if: github.ref == 'refs/heads/main'
shell: pwsh
env:
IIS_HOST: ${{ secrets.IIS_HOST }}
IIS_USER: ${{ secrets.IIS_USER }}
IIS_PASSWORD: ${{ secrets.IIS_PASSWORD }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
DB_CONNECTION: ${{ secrets.DB_CONNECTION }}
run: |
$secure = ConvertTo-SecureString $env:IIS_PASSWORD -AsPlainText -Force
$cred = New-Object PSCredential($env:IIS_USER, $secure)
$session = New-PSSession -ComputerName $env:IIS_HOST -Credential $cred
Import-Module WebAdministration
Copy-Item -Path api.zip, fe-admin.zip, fe-user.zip -Destination "C:\Deploy\" -ToSession $session
Invoke-Command -Session $session -ScriptBlock {
& C:\Deploy\scripts\deploy-iis.ps1 -Artifact "C:\Deploy\api.zip" -Site "SolutionErpApi"
Expand-Archive "C:\Deploy\fe-admin.zip" "C:\inetpub\solution-erp\fe-admin" -Force
Expand-Archive "C:\Deploy\fe-user.zip" "C:\inetpub\solution-erp\fe-user" -Force
# Stop app pool so DLLs are writable
if (Get-WebAppPoolState -Name SolutionErp-Api -ErrorAction SilentlyContinue) {
Stop-WebAppPool -Name SolutionErp-Api
Start-Sleep -Seconds 3
}
Remove-PSSession $session
# Deploy API
Remove-Item -Path 'C:\inetpub\solution-erp\api\*' -Recurse -Force -Exclude 'appsettings.Production.json','logs','uploads','wwwroot' -ErrorAction SilentlyContinue
Copy-Item -Path 'out\api\*' -Destination 'C:\inetpub\solution-erp\api\' -Recurse -Force
# Write appsettings.Production.json from template + secrets
$example = 'C:\inetpub\solution-erp\api\appsettings.Production.json.example'
$prod = 'C:\inetpub\solution-erp\api\appsettings.Production.json'
if (Test-Path $example) {
$settings = Get-Content $example -Raw | ConvertFrom-Json
$settings.ConnectionStrings.Default = $env:DB_CONNECTION
$settings.Jwt.Secret = $env:JWT_SECRET
$settings | ConvertTo-Json -Depth 10 | Set-Content -Path $prod -Encoding UTF8
Write-Host "Wrote appsettings.Production.json"
# Restrict ACL
icacls $prod /inheritance:r | Out-Null
icacls $prod /grant:r 'Administrators:(R,W)' 'IIS AppPool\SolutionErp-Api:(R)' | Out-Null
} else {
Write-Error "Template $example not found"
exit 1
}
# Deploy fe-admin
Remove-Item -Path 'C:\inetpub\solution-erp\fe-admin\*' -Recurse -Force -Exclude 'web.config' -ErrorAction SilentlyContinue
Copy-Item -Path 'fe-admin\dist\*' -Destination 'C:\inetpub\solution-erp\fe-admin\' -Recurse -Force
# Deploy fe-user
Remove-Item -Path 'C:\inetpub\solution-erp\fe-user\*' -Recurse -Force -Exclude 'web.config' -ErrorAction SilentlyContinue
Copy-Item -Path 'fe-user\dist\*' -Destination 'C:\inetpub\solution-erp\fe-user\' -Recurse -Force
# Restart app pool
Start-WebAppPool -Name SolutionErp-Api
Write-Host "Deploy done. App pool started."
- name: Smoke test
if: github.ref == 'refs/heads/main'
shell: pwsh
run: |
Start-Sleep -Seconds 10
try {
$r = Invoke-WebRequest -Uri 'https://api.huypham.vn/health/live' -TimeoutSec 30 -UseBasicParsing
Write-Host "API /health/live -> $($r.StatusCode)"
} catch {
Write-Warning "API smoke test: $_"
}