Files
solution-erp/docs/guides/deployment-iis.md
pqhuy1987 f3fb3fd565 [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>
2026-04-21 12:57:12 +07:00

7.7 KiB

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

.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)

# 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

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ễ:

# 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)

# 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)

$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:

# 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

# 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

# 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

# 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.jsonAllowedOrigins 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)

<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