[CLAUDE] Skill · Docs: fix iis-deploy-runbook stale paths + S88 web.config closeout

iis-deploy-runbook SKILL corrected (discovered during the index.html-hardening task):
- physical paths apps\SolutionErp\{Api,Admin,User} were STALE (empty dir) -> real
  solution-erp\{api,fe-admin,fe-user} (verified via appcmd list site).
- web.config rule list fixed to the ACTUAL live config (HTTP->HTTPS + SPA-Routes excl
  /api + .webmanifest + 3 security headers; NO /api|/hubs ARR proxy -- FE calls api via CORS)
  + documented the S88 cache hardening (index.html no-cache, assets immutable).
- log/appsettings/scp/backup paths swept to the real base.
Session log: index.html no-cache hardening DONE (ca136d8) + lesson reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-25 11:42:23 +07:00
parent ca136d8e2e
commit d085436e49
2 changed files with 21 additions and 18 deletions

View File

@ -46,15 +46,17 @@ Internet
| Site | Binding | Physical path | Apool | Purpose |
|---|---|---|---|---|
| `SolutionErp-Api` | `*:443:api.solutions.com.vn` HTTPS | `C:\inetpub\apps\SolutionErp\Api\` | out-of-process Kestrel | ASP.NET Core 10 API (port 5443 internal) |
| `SolutionErp-Admin` | `*:443:admin.solutions.com.vn` HTTPS + `*:80` redirect | `C:\inetpub\apps\SolutionErp\Admin\` | static (no app pool .NET) | React build fe-admin |
| `SolutionErp-User` | `*:443:eoffice.solutions.com.vn` HTTPS + `*:80` redirect | `C:\inetpub\apps\SolutionErp\User\` | static | React build fe-user |
| `SolutionErp-Api` | `*:443:api.solutions.com.vn` (+`api.huypham.vn`) HTTPS | `C:\inetpub\solution-erp\api\` | out-of-process Kestrel | ASP.NET Core 10 API (port 5443 internal) |
| `SolutionErp-Admin` | `*:443:admin.solutions.com.vn` (+`admin.huypham.vn`) HTTPS + `*:80` redirect | `C:\inetpub\solution-erp\fe-admin\` | static (no app pool .NET) | React build fe-admin |
| `SolutionErp-User` | `*:443:eoffice.solutions.com.vn` HTTPS + `*:80` redirect | `C:\inetpub\solution-erp\fe-user\` | static | React build fe-user |
**SPA web.config:** 2 FE có `URL Rewrite` rule:
1. HTTP → HTTPS redirect (bắt buộc, CORS whitelist chỉ https)
2. `/api/* → http://127.0.0.1:5443/api/*` (ARR reverse proxy)
3. `/hubs/* → http://127.0.0.1:5443/hubs/*` (SignalR)
4. React Router fallback: `/*``/index.html`
> ⚠️ **Physical paths verified via `appcmd list site` S88** — runbook trước ghi `C:\inetpub\apps\SolutionErp\{Api,Admin,User}` (STALE, dir rỗng). Đường THẬT = `C:\inetpub\solution-erp\{api,fe-admin,fe-user}`.
**SPA web.config** (`{fe-admin,fe-user}/public/web.config`, **version-controlled S88** — Vite copy `public/*``dist/`→deploy `Copy-Item -Force`; `deploy.yml``Remove-Item …\* -Exclude web.config` = trước S88 file manage-tay trên server). Rule THẬT (đọc live S88):
1. **HTTP → HTTPS** redirect (`{HTTPS} ^OFF$`, Permanent; CORS whitelist chỉ https). KHÔNG có `.well-known` exclude (win-acme validation khác).
2. **SPA Routes** fallback: non-file/non-dir/non-`^/api` → rewrite `/` (index.html). *(KHÔNG có `/api` hay `/hubs` ARR proxy — FE gọi thẳng `https://api.solutions.com.vn` qua CORS.)*
3. `.webmanifest` mime + 3 security header (X-Content-Type-Options / X-Frame-Options DENY / Referrer-Policy).
4. **[S88 hardening] Cache:** root `<clientCache cacheControlMode="DisableCache">``index.html` (+ SPA-rewrite shell `/dashboard`…) = `Cache-Control: no-cache` (luôn revalidate → chống stale-shell-after-deploy = white-screen S88) · `<location path="assets"><clientCache UseMaxAge 365d + cacheControlCustom="public, immutable">` → hashed asset `immutable, max-age=1y`. **Verify:** `curl -I https://eoffice.solutions.com.vn/``no-cache`; `curl -I .../assets/index-*.js``immutable`; `/dashboard` → 200 (routing intact).
## Quick commands
@ -77,13 +79,13 @@ Get-Website -Name "SolutionErp-*" | Format-Table Name, State, Bindings
```powershell
# Serilog file rolling daily
Get-Content "C:\inetpub\apps\SolutionErp\Api\Logs\log-$(Get-Date -Format 'yyyyMMdd').txt" -Tail 50
Get-Content "C:\inetpub\solution-erp\api\Logs\log-$(Get-Date -Format 'yyyyMMdd').txt" -Tail 50
# IIS log
Get-Content "C:\inetpub\logs\LogFiles\W3SVC<ID>\u_ex$(Get-Date -Format 'yyMMdd').log" -Tail 30
# Stdout log khi crash startup
Get-Content "C:\inetpub\apps\SolutionErp\Api\Logs\stdout_*.log" -Tail 30
Get-Content "C:\inetpub\solution-erp\api\Logs\stdout_*.log" -Tail 30
```
### Health check
@ -258,7 +260,7 @@ sqlcmd -S .\SQLEXPRESS -U vrapp -P <pw> -Q "SELECT DB_NAME()"
# Expect: SolutionErp
# 4. appsettings connection string (qua Gitea secrets)
# Check C:\inetpub\apps\SolutionErp\Api\appsettings.Production.json có ConnectionStrings:DefaultConnection
# Check C:\inetpub\solution-erp\api\appsettings.Production.json có ConnectionStrings:DefaultConnection
```
## Deploy steps (CI/CD xanh)
@ -275,7 +277,7 @@ Push to main
→ npm run build (fe-admin + fe-user)
→ render appsettings.Production.json từ secrets (JWT_SECRET, DB_CONNECTION)
→ stop app pool SolutionErp-Api
xcopy publish → C:\inetpub\apps\SolutionErp\{Api,Admin,User}
Copy-Item publish/dist → C:\inetpub\solution-erp\{api,fe-admin,fe-user} (Remove-Item -Exclude web.config first)
→ start app pool
→ curl /health/ready → must be 200 trong 30s
→ report status
@ -290,9 +292,9 @@ cd fe-admin; npm ci; npm run build; cd ..
cd fe-user; npm ci; npm run build; cd ..
# Scp sang server (cần plink/pscp hoặc rsync)
scp -r .\publish\api\* user@server:C:/inetpub/apps/SolutionErp/Api/
scp -r .\fe-admin\dist\* user@server:C:/inetpub/apps/SolutionErp/Admin/
scp -r .\fe-user\dist\* user@server:C:/inetpub/apps/SolutionErp/User/
scp -r .\publish\api\* user@server:C:/inetpub/solution-erp/api/
scp -r .\fe-admin\dist\* user@server:C:/inetpub/solution-erp/fe-admin/
scp -r .\fe-user\dist\* user@server:C:/inetpub/solution-erp/fe-user/
# Trên server:
Restart-WebAppPool -Name "SolutionErp-Api"
@ -303,12 +305,12 @@ curl http://127.0.0.1:5443/health/ready
```powershell
# DB backup (script sẵn, chưa schedule):
& "C:\inetpub\apps\SolutionErp\scripts\backup-sql.ps1"
& "C:\inetpub\solution-erp\scripts\backup-sql.ps1"
# Output: backup/SolutionErp_<ts>.bak (compressed + retention 30d)
# Schedule daily 03:00:
schtasks /create /tn "SolutionErp Backup" `
/tr "powershell -ExecutionPolicy Bypass -File C:\inetpub\apps\SolutionErp\scripts\backup-sql.ps1" `
/tr "powershell -ExecutionPolicy Bypass -File C:\inetpub\solution-erp\scripts\backup-sql.ps1" `
/sc DAILY /st 03:00 /ru SYSTEM
```