# 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
