[CLAUDE] Phase5 prep: production infra + deploy scripts + 4 guides + FE refresh token
Backend production infra:
- Packages: Serilog.Sinks.File, HealthChecks.EntityFrameworkCore (RateLimiting built-in .NET 10)
- appsettings.Production.json MOI: placeholder __SET_VIA_SECRETS__, AllowedOrigins, Serilog File sink rolling daily retention 30d, RateLimit config
- appsettings.json + Development.json: them Serilog WriteTo Console
- Program.cs REWRITE:
- Serilog ReadFrom.Configuration (prod file / dev console)
- Rate limiter: policy auth-login 5/min/IP (AuthController.Login) + GlobalLimiter 300/min/IP
- Health checks: /health/live liveness (empty predicate) + /health/ready DB probe (AddDbContextCheck)
- HSTS production 1 year
- CORS origins from config AllowedOrigins (default dev 2 localhost)
- AuthController.Login gắn [EnableRateLimiting("auth-login")]
Deploy scripts:
- scripts/deploy-iis.ps1: stop pool → backup current → clean+extract artifact → start pool → health check loop 30s timeout → rollback instruction if fail
- scripts/backup-sql.ps1: BACKUP DATABASE voi INIT+COMPRESSION+CHECKSUM + retention 30d auto cleanup
- .gitea/workflows/deploy.yml MOI: 4 job build BE (Windows) + build 2 FE (Ubuntu, pin .nvmrc 20) + deploy-iis qua WinRM PSSession (secrets IIS_HOST/USER/PASSWORD/JWT_SECRET/DB_CONNECTION)
Docs guides MOI (4 file):
- deployment-iis.md: prereqs (IIS features, Hosting Bundle, SQL, WinRM) + setup lan dau (app pool, 3 site, HTTPS win-acme, user-secrets) + deploy hang ngay (CI/CD + manual) + rollback + monitoring + troubleshooting + SPA web.config sample
- cicd.md: pipeline overview 4 job, secrets setup, runner Windows+Ubuntu, branch strategy, build optimizations, common CI/CD issues
- security-checklist.md: OWASP top 10 2021 mapping voi status + pre go-live checklist + incident response
- runbook.md: daily ops (health/logs), restart/rollback, DB backup/restore/migration revert, user management (reset password, unlock, disable), monitoring (CPU/disk/connection pool), deployment checklist, common gotcha
Frontend refresh token (ca 2 app fe-admin + fe-user):
- lib/api.ts REWRITE: them REFRESH_KEY, axios response interceptor 401 → POST /auth/refresh → retry request goc. Queue pattern cho nhieu request song song chi 1 refresh call chay. Skip retry /auth/login + /auth/refresh tranh infinite loop. _retry flag tren original config.
- contexts/AuthContext.tsx: luu+xoa REFRESH_KEY trong login/logout
E2E verified:
- GET /health/live → 200 Healthy
- GET /health/ready → 200 Healthy (DB probe)
- Rate limit flood 7 POST /auth/login → #1-5 HTTP 400 (cred sai) + #6-7 HTTP 429 Too Many Requests ✅
- TS check fe-admin + fe-user → pass
- dotnet build → 0 errors
Docs updates:
- docs/STATUS.md: Phase 5 prep done, next Phase 5 deploy production + Phase 5.1 security hardening, cumulative stats 8 commits
- docs/HANDOFF.md: phase table them Phase 5 prep row, file tree update voi guides + scripts + workflows, git state commit 8
- docs/changelog/migration-todos.md: tick Phase 5 prep items (12 items done) + Phase 5 deploy items remaining + Phase 5.1 security hardening list
- docs/changelog/sessions/2026-04-21-1530-phase5-prep.md: session log chi tiet
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
216
docs/guides/deployment-iis.md
Normal file
216
docs/guides/deployment-iis.md
Normal file
@ -0,0 +1,216 @@
|
||||
# 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 <PreviousMigrationName> --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
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="SPA Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
<add input="{REQUEST_URI}" pattern="^/api" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="/" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user