name: Build and Deploy CPRNIMS on: push: branches: [ main ] jobs: build-and-deploy: runs-on: windows steps: - name: Checkout uses: actions/checkout@v4 - name: Clean previous publish output shell: pwsh run: | Remove-Item -Recurse -Force "C:\ci-output\webapi" -ErrorAction SilentlyContinue Remove-Item -Recurse -Force "C:\ci-output\webapps" -ErrorAction SilentlyContinue - name: Publish WebApi shell: pwsh run: dotnet publish .\CPRNIMS.WebApi\CPRNIMS.WebApi.csproj -c Release -o C:\ci-output\webapi - name: Publish WebApps shell: pwsh run: dotnet publish .\CPRNIMS.WebApps\CPRNIMS.WebApps.csproj -c Release -o C:\ci-output\webapps # ---- Generate production config from Gitea Actions secrets (never committed to git) ---- - name: Write production appsettings - WebApi shell: pwsh env: TAVILY_KEY: ${{ secrets.LLI_NON_INVENTORY_PROD_TAVILY_API_KEY }} GROQ_KEY: ${{ secrets.LLI_NON_INVENTORY_PROD_GROQ_API_KEY }} JWT_SECRET: ${{ secrets.LLI_NON_INVENTORY_PROD_JWT_SECRET }} DB_CONN: ${{ secrets.LLI_NON_INVENTORY_PROD_DB_CONNECTION }} LOCALPURCH_CONN: ${{ secrets.LLI_NON_INVENTORY_PROD_DB_LOCALPURCH_CONNECTION }} run: | $config = @{ Tavily = @{ ApiKey = $env:TAVILY_KEY SearchUrl = "https://api.tavily.com/search" } Groq = @{ ApiKey = $env:GROQ_KEY ApiUrl = "https://api.groq.com/openai/v1/chat/completions" Model = "llama-3.1-8b-instant" } JWT = @{ ValidAudience = "https://lloydwebapi.lloydlab.com:2021" ValidIssuer = "https://lloydwebapi.lloydlab.com:2021" Secret = $env:JWT_SECRET } WebEndPoint = @{ ForgotPassword = "https://llipurchasingnoninventory.com:8080/" SupplierForm = "https://llipurchasingnoninventory.com:8083/" } ConnectionStrings = @{ DefaultConnection = $env:DB_CONN LocalPurchConn = $env:LOCALPURCH_CONN } } $json = $config | ConvertTo-Json -Depth 5 $json | Out-File -FilePath "C:\ci-output\webapi\appsettings.Production.json" -Encoding utf8 Write-Host "Wrote appsettings.Production.json to webapi output (values masked in this log automatically)" exit 0 # ---- Generate production config for WebApps (uses Variables, not Secrets, since BaseUrl isn't sensitive) ---- - name: Write production appsettings - WebApps shell: pwsh env: API_BASE_URL: ${{ vars.LLI_NON_INVENTORY_PROD_API_BASE_URL }} run: | $config = @{ CommonEndpoints = @{ ApiDefaultHeaders = @{ BaseUrl = $env:API_BASE_URL ESignaturePath = "https://llipurchasingnoninventory.com:8080/Content/Images/Signatures/" ItemImages = "https://llipurchasingnoninventory.com:8080/content/images/" ContentTypeMedia = "application/json" Authorization = "token" ErrorMessage = "api/ErrorLogs/ErrorMessage/" } } } $json = $config | ConvertTo-Json -Depth 5 $json | Out-File -FilePath "C:\ci-output\webapps\appsettings.Production.json" -Encoding utf8 Write-Host "Wrote appsettings.Production.json to webapps output" exit 0 # ---- Backup current live deployment before touching anything ---- - name: Backup current live files shell: pwsh run: | $stamp = Get-Date -Format "yyyyMMdd-HHmmss" New-Item -ItemType Directory -Force -Path "C:\backups\$stamp" | Out-Null # Mirror current live folders into the backup location (only if they exist / aren't empty) if (Test-Path "C:\inetpub\cprnims-api") { robocopy "C:\inetpub\cprnims-api" "C:\backups\$stamp\webapi" /MIR /R:2 /W:3 | Out-Null } if (Test-Path "C:\inetpub\cprnims-web") { robocopy "C:\inetpub\cprnims-web" "C:\backups\$stamp\webapps" /MIR /R:2 /W:3 | Out-Null } # Record this backup's timestamp so later steps know where it lives $stamp | Out-File -FilePath "C:\backups\latest.txt" -Encoding ascii -NoNewline # Keep only the last 5 backups to avoid filling the disk $all = Get-ChildItem "C:\backups" -Directory | Sort-Object Name -Descending if ($all.Count -gt 5) { $all | Select-Object -Skip 5 | Remove-Item -Recurse -Force } Write-Host "Backed up current deployment to C:\backups\$stamp" exit 0 - name: Stop app pools shell: pwsh run: | Import-Module WebAdministration Stop-WebAppPool -Name "CPRNIMS-Api" -ErrorAction SilentlyContinue Stop-WebAppPool -Name "CPRNIMS-Web" -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 - name: Deploy WebApi files id: deploy_api shell: pwsh run: | robocopy "C:\ci-output\webapi" "C:\inetpub\cprnims-api" /MIR /R:3 /W:5 $rc = $LASTEXITCODE Write-Host "ROBOCOPY EXIT CODE: $rc" if ($rc -ge 8) { throw "robocopy failed for WebApi with exit code $rc" } exit 0 - name: Deploy WebApps files id: deploy_web shell: pwsh run: | robocopy "C:\ci-output\webapps" "C:\inetpub\cprnims-web" /MIR /R:3 /W:5 $rc = $LASTEXITCODE Write-Host "ROBOCOPY EXIT CODE: $rc" if ($rc -ge 8) { throw "robocopy failed for WebApps with exit code $rc" } exit 0 - name: Start app pools shell: pwsh run: | Import-Module WebAdministration Start-WebAppPool -Name "CPRNIMS-Api" Start-WebAppPool -Name "CPRNIMS-Web" - name: Verify app pools are running shell: pwsh run: | Start-Sleep -Seconds 3 Import-Module WebAdministration $api = Get-WebAppPoolState -Name "CPRNIMS-Api" $web = Get-WebAppPoolState -Name "CPRNIMS-Web" Write-Host "CPRNIMS-Api: $($api.Value)" Write-Host "CPRNIMS-Web: $($web.Value)" if ($api.Value -ne "Started" -or $web.Value -ne "Started") { throw "One or more app pools failed to start" } # ---- Rollback path: only runs if any prior step in this job failed ---- - name: ROLLBACK - restore previous backup if: failure() shell: pwsh run: | $stamp = Get-Content "C:\backups\latest.txt" -Raw $backupPath = "C:\backups\$stamp" Write-Host "Deployment failed - rolling back to backup: $backupPath" Import-Module WebAdministration Stop-WebAppPool -Name "CPRNIMS-Api" -ErrorAction SilentlyContinue Stop-WebAppPool -Name "CPRNIMS-Web" -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 if (Test-Path "$backupPath\webapi") { robocopy "$backupPath\webapi" "C:\inetpub\cprnims-api" /MIR /R:3 /W:5 | Out-Null } if (Test-Path "$backupPath\webapps") { robocopy "$backupPath\webapps" "C:\inetpub\cprnims-web" /MIR /R:3 /W:5 | Out-Null } Start-WebAppPool -Name "CPRNIMS-Api" Start-WebAppPool -Name "CPRNIMS-Web" Write-Host "Rollback complete. Restored from $backupPath" exit 0 - name: ROLLBACK - verify pools after restore if: failure() shell: pwsh run: | Start-Sleep -Seconds 3 Import-Module WebAdministration $api = Get-WebAppPoolState -Name "CPRNIMS-Api" $web = Get-WebAppPoolState -Name "CPRNIMS-Web" Write-Host "After rollback - CPRNIMS-Api: $($api.Value)" Write-Host "After rollback - CPRNIMS-Web: $($web.Value)" if ($api.Value -ne "Started" -or $web.Value -ne "Started") { Write-Host "WARNING: app pools still not running after rollback. Manual intervention needed." }