# Deployment — Windows Server + IIS > Step-by-step setup lần đầu + deploy hàng ngày. Test với Windows Server 2019/2022 + IIS 10. ## 1. Prerequisites trên target server ### OS + IIS - Windows Server 2019 / 2022 (hoặc Windows 10/11 Pro cho staging) - IIS với features: **Static Content**, **HTTP Redirection**, **Application Initialization**, **WebSocket Protocol**, **Management Console**, **Windows Auth** (optional) - **URL Rewrite Module 2.1+**: https://www.iis.net/downloads/microsoft/url-rewrite - **Application Request Routing (ARR) 3.0+** (nếu dùng reverse proxy): https://www.iis.net/downloads/microsoft/application-request-routing ### .NET 10 Hosting Bundle ``` https://dotnet.microsoft.com/en-us/download/dotnet/10.0 → .NET 10 Hosting Bundle (không phải SDK, runtime + ASP.NET Core Module) ``` Sau khi cài → restart IIS: `iisreset` trong cmd elevated. ### SQL Server - SQL Server 2019+ Express / Standard / Enterprise - Tạo DB `SolutionErp` + SQL user `solutionerp_app` với db_owner - Bật **Named Pipes** hoặc **TCP/IP** protocol (SQL Server Configuration Manager) ### WinRM (cho CI/CD deploy từ xa) ```powershell # Run as admin trên target server Enable-PSRemoting -Force Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force winrm quickconfig -Transport:HTTPS ``` ## 2. Setup lần đầu ### 2.1 Tạo app pool ```powershell Import-Module WebAdministration New-WebAppPool -Name "SolutionErpApi" Set-ItemProperty IIS:\AppPools\SolutionErpApi -Name managedRuntimeVersion -Value "" # .NET CORE (no CLR) Set-ItemProperty IIS:\AppPools\SolutionErpApi -Name startMode -Value "AlwaysRunning" Set-ItemProperty IIS:\AppPools\SolutionErpApi -Name processModel.identityType -Value "ApplicationPoolIdentity" ``` ### 2.2 Tạo site (3 site hoặc 1 site với 3 path) **Recommended:** 3 site riêng để quản lý binding dễ: ```powershell # Api New-WebSite -Name "SolutionErp-Api" -Port 443 -HostHeader "api.solutionerp.local" ` -PhysicalPath "C:\inetpub\solution-erp\api" -ApplicationPool "SolutionErpApi" -Ssl # Admin FE New-WebSite -Name "SolutionErp-Admin" -Port 443 -HostHeader "admin.solutionerp.local" ` -PhysicalPath "C:\inetpub\solution-erp\fe-admin" -Ssl # User FE New-WebSite -Name "SolutionErp-User" -Port 443 -HostHeader "app.solutionerp.local" ` -PhysicalPath "C:\inetpub\solution-erp\fe-user" -Ssl ``` ### 2.3 HTTPS certificate #### Option A — win-acme (Let's Encrypt free) ```powershell # Download từ https://www.win-acme.com/ wacs.exe # Menu: N (new cert) → chọn sites → auto-renew via scheduled task ``` #### Option B — Self-signed (dev/staging only) ```powershell $cert = New-SelfSignedCertificate -DnsName "api.solutionerp.local", "admin.solutionerp.local", "app.solutionerp.local" ` -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(5) $bytes = $cert.GetCertHash() ``` ### 2.4 Secrets cho Api **KHÔNG commit `appsettings.Production.json` với secret thật.** Dùng user-secrets: ```powershell # Trên target server, với identity app pool cd C:\inetpub\solution-erp\api dotnet user-secrets set "Jwt:Secret" "__RANDOM_64_CHAR_STRING__" dotnet user-secrets set "ConnectionStrings:Default" "Server=.;Database=SolutionErp;User Id=solutionerp_app;Password=..." ``` Hoặc dùng env vars (set qua IIS app pool advanced settings): - `Jwt__Secret` = `...` - `ConnectionStrings__Default` = `...` ### 2.5 FE build — adjust Vite base URL + API proxy `fe-admin/vite.config.ts` production build không proxy, FE gọi trực tiếp `https://api.solutionerp.local`. Thêm env: ``` # fe-admin/.env.production VITE_API_BASE_URL=https://api.solutionerp.local ``` Update `src/lib/api.ts` sử dụng `import.meta.env.VITE_API_BASE_URL ?? '/api'`. ### 2.6 Init DB ```powershell # Run trên app server với .NET SDK installed (KHÔNG chỉ runtime) cd C:\Deploy\staging dotnet ef database update --project src\Backend\SolutionErp.Infrastructure --startup-project src\Backend\SolutionErp.Api ``` Hoặc tự động khi app khởi động lần đầu (đã có trong `DbInitializer`). ## 3. Deploy hàng ngày ### 3.1 Qua CI/CD (Gitea Actions) Push code vào `main` → `.gitea/workflows/deploy.yml` auto: 1. Build BE + 2 FE 2. Upload artifact 3. WinRM tới IIS host 4. Run `scripts/deploy-iis.ps1` ### 3.2 Manual deploy ```powershell # Trên dev machine dotnet publish src/Backend/SolutionErp.Api --configuration Release -o .\publish\api cd fe-admin && npm ci && npm run build # dist/ cd fe-user && npm ci && npm run build # dist/ Compress-Archive .\publish\api\* api.zip Compress-Archive .\fe-admin\dist\* fe-admin.zip Compress-Archive .\fe-user\dist\* fe-user.zip # Copy lên target server Copy-Item api.zip, fe-admin.zip, fe-user.zip -Destination \\server\C$\Deploy\ -Force # Trên target server .\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 ``` ### 3.3 Rollback ```powershell # Deploy script tự backup vào C:\inetpub\solution-erp\backups\api-{timestamp} Stop-WebAppPool SolutionErpApi Copy-Item C:\inetpub\solution-erp\backups\api-20260421-0930\* ` C:\inetpub\solution-erp\api\ -Recurse -Force Start-WebAppPool SolutionErpApi # Nếu migration hỏng → revert DB: dotnet ef database update --project ... ``` ## 4. Monitoring ### 4.1 Health check - `/health/live` — liveness probe (IIS ping) - `/health/ready` — readiness probe (DB reachable) ### 4.2 Logs - Serilog → `C:\inetpub\solution-erp\api\logs\solution-erp-{yyyyMMdd}.log` - Retention 30 ngày auto - IIS request log: `C:\inetpub\logs\LogFiles\` - Application event log: `eventvwr` → Windows Logs → Application → Source: IIS Express / .NET Runtime ### 4.3 SQL backup Task Scheduler trigger `scripts\backup-sql.ps1` daily 2:00 AM. Retention 30 ngày `.bak` files. ## 5. Troubleshooting | Triệu chứng | Check | |---|---| | HTTP 500.30 ANCM startup | event log `Application`, logs folder có được tạo không, permission app pool identity | | HTTP 502.5 ANCM Process failure | .NET 10 Hosting Bundle đã cài chưa, app pool CLR = "" | | 500 khi login | secrets config đúng chưa (Jwt__Secret + ConnectionStrings__Default) | | CORS fail | `appsettings.Production.json` → `AllowedOrigins` match domain FE | | Slow query | check index (xem `database/schema-diagram.md §4`) | | App pool crash loop | disable rapid fail protection tạm thời khi debug: `Set-ItemProperty IIS:\AppPools\SolutionErpApi -Name failure.rapidFailProtection -Value false` | | FE 404 routes (vd /contracts/123) | IIS URL Rewrite config SPA fallback — tạo `web.config` FE folder với rule rewrite `.*` → `index.html` | ### web.config cho SPA FE (sample) ```xml ``` ## 6. Liên quan - [`cicd.md`](cicd.md) — Gitea Actions workflow chi tiết - [`security-checklist.md`](security-checklist.md) — OWASP top 10 - [`runbook.md`](runbook.md) — operations (restart, restore, common tasks) - [`../database/database-guide.md`](../database/database-guide.md) — DB backup/restore chi tiết