[CLAUDE] CICD: rewrite workflow for local deploy on self-hosted runner
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 5s
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:
@ -1,13 +1,13 @@
|
|||||||
# Gitea Actions CI/CD — build + deploy SOLUTION_ERP lên IIS Windows Server.
|
# Gitea Actions CI/CD - build + deploy SOLUTION_ERP to IIS on same VPS.
|
||||||
# Trigger: push vào branch main, hoặc manual.
|
# 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).
|
# Self-hosted Windows runner on VPS (shared with VIETREPORT). Runner has:
|
||||||
# Secrets cần set trong Gitea repo settings:
|
# - git, .NET 10 SDK, Node 20, IIS
|
||||||
# - IIS_HOST (hostname hoặc IP)
|
# - Can deploy locally (no WinRM needed)
|
||||||
# - IIS_USER (Windows user có admin + WinRM)
|
#
|
||||||
# - IIS_PASSWORD
|
# Secrets required in Gitea repo settings:
|
||||||
# - JWT_SECRET (64+ chars random — dùng trong appsettings.Production.json)
|
# - JWT_SECRET (64+ chars random)
|
||||||
# - DB_CONNECTION (connection string production)
|
# - DB_CONNECTION (full connection string with vrapp password)
|
||||||
|
|
||||||
name: Deploy SOLUTION_ERP
|
name: Deploy SOLUTION_ERP
|
||||||
|
|
||||||
@ -17,102 +17,105 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-backend:
|
build-deploy:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup .NET 10
|
- name: Add dotnet + node to PATH
|
||||||
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
|
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
Compress-Archive -Path artifacts/backend-api/* -DestinationPath api.zip -Force
|
echo "C:\Program Files\dotnet" >> $env:GITHUB_PATH
|
||||||
Compress-Archive -Path artifacts/fe-admin-dist/* -DestinationPath fe-admin.zip -Force
|
echo "C:\Program Files\nodejs" >> $env:GITHUB_PATH
|
||||||
Compress-Archive -Path artifacts/fe-user-dist/* -DestinationPath fe-user.zip -Force
|
|
||||||
|
|
||||||
- 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
|
shell: pwsh
|
||||||
env:
|
env:
|
||||||
IIS_HOST: ${{ secrets.IIS_HOST }}
|
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
||||||
IIS_USER: ${{ secrets.IIS_USER }}
|
DB_CONNECTION: ${{ secrets.DB_CONNECTION }}
|
||||||
IIS_PASSWORD: ${{ secrets.IIS_PASSWORD }}
|
|
||||||
run: |
|
run: |
|
||||||
$secure = ConvertTo-SecureString $env:IIS_PASSWORD -AsPlainText -Force
|
Import-Module WebAdministration
|
||||||
$cred = New-Object PSCredential($env:IIS_USER, $secure)
|
|
||||||
$session = New-PSSession -ComputerName $env:IIS_HOST -Credential $cred
|
|
||||||
|
|
||||||
Copy-Item -Path api.zip, fe-admin.zip, fe-user.zip -Destination "C:\Deploy\" -ToSession $session
|
# Stop app pool so DLLs are writable
|
||||||
|
if (Get-WebAppPoolState -Name SolutionErp-Api -ErrorAction SilentlyContinue) {
|
||||||
Invoke-Command -Session $session -ScriptBlock {
|
Stop-WebAppPool -Name SolutionErp-Api
|
||||||
& C:\Deploy\scripts\deploy-iis.ps1 -Artifact "C:\Deploy\api.zip" -Site "SolutionErpApi"
|
Start-Sleep -Seconds 3
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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: $_"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user