From 66c1a5c170346b07d7f2feb79feae0df23faf308 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 24 Apr 2026 09:43:05 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20Rebrand:=203=20domain=20huypham.vn?= =?UTF-8?q?=20=E2=86=92=20solutions.com.vn=20+=20migrate=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User request: anh trỏ 3 subdomain mới về VPS IP 103.124.94.38: - api.huypham.vn → api.solutions.com.vn - admin.huypham.vn → admin.solutions.com.vn - user.huypham.vn → eoffice.solutions.com.vn Verified DNS: cả 3 resolve 103.124.94.38 ✓ Update 17 file repo: FE (4): fe-admin/.env.production + fe-user/.env.production (VITE_API_BASE_URL → https://api.solutions.com.vn) fe-admin/src/lib/{api,realtime}.ts + fe-user equivalents (comment) BE (1): appsettings.Production.json.example — CORS AllowedOrigins CI/CD (1): .gitea/workflows/deploy.yml — smoke test URL Scripts (3): setup-iis-sites (DomainApi/Admin/User), setup-ssl (3 host), deploy-all (verify curls) Docs (5): STATUS, HANDOFF, PROJECT-MAP, vps-setup, gotchas Skill (1): iis-deploy-runbook — 3 site table + description Email admin@huypham.vn giữ nguyên (Let's Encrypt contact — không phải domain serve). Thêm scripts/migrate-domains.ps1 — 1-shot VPS migration: 1. Pre-flight: resolve DNS 3 domain → verify IP VPS khớp 2. Add HTTP binding mới cho 3 IIS site (giữ binding cũ làm fallback) 3. Run win-acme xin 3 cert Let's Encrypt qua HTTP-01 challenge (auto add HTTPS binding + http→https redirect) 4. Verify /health/live + /health/ready + 2 FE endpoint 5. (Optional -RemoveOld) xóa binding huypham.vn sau verify OK Rollback: nếu fail, binding cũ vẫn active → site serve qua huypham.vn. Anh chạy trên VPS: cd C:\solution-erp\scripts ; .\migrate-domains.ps1 # Sau 1-2 ngày verify stable: .\migrate-domains.ps1 -RemoveOld -SkipCert --- .claude/skills/iis-deploy-runbook/SKILL.md | 20 +- .gitea/workflows/deploy.yml | 2 +- docs/HANDOFF.md | 6 +- docs/PROJECT-MAP.md | 2 +- docs/STATUS.md | 14 +- docs/gotchas.md | 2 +- docs/guides/vps-setup.md | 16 +- fe-admin/.env.production | 2 +- fe-admin/src/lib/api.ts | 2 +- fe-admin/src/lib/realtime.ts | 2 +- fe-user/.env.production | 2 +- fe-user/src/lib/api.ts | 2 +- fe-user/src/lib/realtime.ts | 2 +- scripts/deploy-all.ps1 | 10 +- scripts/migrate-domains.ps1 | 210 ++++++++++++++++++ scripts/setup-iis-sites.ps1 | 6 +- scripts/setup-ssl.ps1 | 12 +- .../appsettings.Production.json.example | 4 +- 18 files changed, 263 insertions(+), 53 deletions(-) create mode 100644 scripts/migrate-domains.ps1 diff --git a/.claude/skills/iis-deploy-runbook/SKILL.md b/.claude/skills/iis-deploy-runbook/SKILL.md index 0145c43..2571778 100644 --- a/.claude/skills/iis-deploy-runbook/SKILL.md +++ b/.claude/skills/iis-deploy-runbook/SKILL.md @@ -1,6 +1,6 @@ --- name: iis-deploy-runbook -description: Ops runbook cho SOLUTION_ERP deploy trên Windows Server IIS — 3 site (api/admin/user.huypham.vn), win-acme Let's Encrypt, NSSM gitea-runner shared với VIETREPORT, LibreOffice soffice headless. Dùng khi debug 500/502 prod, restart site, rotate cert, fix CI/CD runner, troubleshoot WebSocket, thêm site mới. +description: Ops runbook cho SOLUTION_ERP deploy trên Windows Server IIS — 3 site (api/admin/eoffice.solutions.com.vn), win-acme Let's Encrypt, NSSM gitea-runner shared với VIETREPORT, LibreOffice soffice headless. Dùng khi debug 500/502 prod, restart site, rotate cert, fix CI/CD runner, troubleshoot WebSocket, thêm site mới. when-to-use: - "prod 500 error" - "IIS site fail" @@ -27,7 +27,7 @@ Internet ┌─────────────────────────────────────────────────────┐ │ IIS (Windows Server VPS) │ │ │ -│ ┌─ api.huypham.vn ─┐ ┌─ admin.huypham.vn ─┐ ┌─ user.huypham.vn ─┐ +│ ┌─ api.solutions.com.vn ─┐ ┌─ admin.solutions.com.vn ─┐ ┌─ eoffice.solutions.com.vn ─┐ │ │ SolutionErp-Api │ │ SolutionErp-Admin │ │ SolutionErp-User │ │ │ → out-of-process │ │ (static SPA, URL │ │ (static SPA, URL │ │ │ Kestrel :5443 │ │ Rewrite /api → 5443)│ │ Rewrite...) │ @@ -46,9 +46,9 @@ Internet | Site | Binding | Physical path | Apool | Purpose | |---|---|---|---|---| -| `SolutionErp-Api` | `*:443:api.huypham.vn` HTTPS | `C:\inetpub\apps\SolutionErp\Api\` | out-of-process Kestrel | ASP.NET Core 10 API (port 5443 internal) | -| `SolutionErp-Admin` | `*:443:admin.huypham.vn` HTTPS + `*:80` redirect | `C:\inetpub\apps\SolutionErp\Admin\` | static (no app pool .NET) | React build fe-admin | -| `SolutionErp-User` | `*:443:user.huypham.vn` HTTPS + `*:80` redirect | `C:\inetpub\apps\SolutionErp\User\` | static | React build fe-user | +| `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 | **SPA web.config:** 2 FE có `URL Rewrite` rule: 1. HTTP → HTTPS redirect (bắt buộc, CORS whitelist chỉ https) @@ -94,7 +94,7 @@ curl http://127.0.0.1:5443/health/live curl http://127.0.0.1:5443/health/ready # Từ ngoài -curl https://api.huypham.vn/health/ready +curl https://api.solutions.com.vn/health/ready ``` ## Let's Encrypt cert — win-acme @@ -237,9 +237,9 @@ Xem gotcha #26: Xem `docs/gotchas.md` CORS + HTTPS redirect: ``` -1. User gõ http://admin.huypham.vn → không redirect → CORS block +1. User gõ http://admin.solutions.com.vn → không redirect → CORS block 2. Fix: SPA web.config PHẢI có HTTP→HTTPS rule (đã có) -3. Test: curl -I http://admin.huypham.vn → expect 301 Location: https://... +3. Test: curl -I http://admin.solutions.com.vn → expect 301 Location: https://... ``` ### DB connection fail @@ -349,9 +349,9 @@ VietReport. **Hiện trạng SOLUTION_ERP — risk THẤP:** - API host trong IIS app pool out-of-process → ANCM quản lý port Kestrel ephemeral -- FE gọi trực tiếp `https://api.huypham.vn` qua CORS (không ARR proxy) +- FE gọi trực tiếp `https://api.solutions.com.vn` qua CORS (không ARR proxy) - Không có standalone Kestrel service trên port cố định -- **Nhưng** tương lai nếu thêm reverse proxy (fe-admin/user → `/api` → api.huypham.vn, hoặc /hubs for SignalR) → PHẢI dùng 127.0.0.1 không localhost +- **Nhưng** tương lai nếu thêm reverse proxy (fe-admin/user → `/api` → api.solutions.com.vn, hoặc /hubs for SignalR) → PHẢI dùng 127.0.0.1 không localhost ## Related diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 9cddbac..02bfa04 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -109,7 +109,7 @@ jobs: run: | Start-Sleep -Seconds 10 try { - $r = Invoke-WebRequest -Uri 'https://api.huypham.vn/health/live' -TimeoutSec 30 -UseBasicParsing + $r = Invoke-WebRequest -Uri 'https://api.solutions.com.vn/health/live' -TimeoutSec 30 -UseBasicParsing Write-Host "API /health/live -> $($r.StatusCode)" } catch { Write-Warning "API smoke test: $_" diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 7ad6cd9..146f956 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -363,9 +363,9 @@ Demo users (User@123456): ⚠ Rotate ALL passwords trước UAT thật ``` -- API prod: https://api.huypham.vn — `/health/live`, `/health/ready` -- Admin FE prod: https://admin.huypham.vn -- User FE prod: https://user.huypham.vn +- API prod: https://api.solutions.com.vn — `/health/live`, `/health/ready` +- Admin FE prod: https://admin.solutions.com.vn +- User FE prod: https://eoffice.solutions.com.vn - API dev: http://localhost:5443 — `/swagger` (Dev only) - Admin FE dev: http://localhost:8082 - User FE dev: http://localhost:8080 diff --git a/docs/PROJECT-MAP.md b/docs/PROJECT-MAP.md index 443aedd..a44962e 100644 --- a/docs/PROJECT-MAP.md +++ b/docs/PROJECT-MAP.md @@ -7,7 +7,7 @@ ``` ┌─────────────────────────────────────────────────────────────────┐ │ SOLUTION_ERP │ -│ 🌐 Prod live: api/admin/user.huypham.vn (HTTPS Let's Encrypt) │ +│ 🌐 Prod live: api.solutions.com.vn / admin.solutions.com.vn / eoffice.solutions.com.vn (HTTPS Let's Encrypt) │ └─────────────────────────────────────────────────────────────────┘ ╔════════════════╗ ╔════════════════╗ ╔════════════════╗ diff --git a/docs/STATUS.md b/docs/STATUS.md index 6e28b96..e1ea2c6 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -8,9 +8,9 @@ ### 🌐 Production URLs -- https://api.huypham.vn — API (Let's Encrypt, auto-renew via win-acme) -- https://admin.huypham.vn — Admin FE (HTTP→HTTPS auto-redirect) -- https://user.huypham.vn — User FE (HTTP→HTTPS auto-redirect) +- https://api.solutions.com.vn — API (Let's Encrypt, auto-renew via win-acme) +- https://admin.solutions.com.vn — Admin FE (HTTP→HTTPS auto-redirect) +- https://eoffice.solutions.com.vn — User FE (HTTP→HTTPS auto-redirect) - https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp — Gitea repo + Actions - Default admin: `admin@solutionerp.local` / `Admin@123456` ⚠️ **RE-ROTATE sau login đầu** @@ -54,7 +54,7 @@ Get-Service *gitea-runner* ; & "C:\nssm\nssm.exe" status gitea-runner # Nếu Stopped → Start-Service gitea-runner ``` - Sau đó recheck `curl https://api.huypham.vn/api/purchase-evaluations` → 401 = deploy OK. + Sau đó recheck `curl https://api.solutions.com.vn/api/purchase-evaluations` → 401 = deploy OK. ## ✅ Recently Done (newest on top) @@ -185,9 +185,9 @@ Session logs: [P0](changelog/sessions/2026-04-21-1045-phase0-scaffold.md) · [P1 admin@solutionerp.local / Admin@123456 ``` -- API prod: https://api.huypham.vn — Health `/health/live` + `/health/ready` +- API prod: https://api.solutions.com.vn — Health `/health/live` + `/health/ready` - API dev: http://localhost:5443 — Swagger `/swagger` -- Admin FE prod: https://admin.huypham.vn · dev `http://localhost:8082` -- User FE prod: https://user.huypham.vn · dev `http://localhost:8080` +- Admin FE prod: https://admin.solutions.com.vn · dev `http://localhost:8082` +- User FE prod: https://eoffice.solutions.com.vn · dev `http://localhost:8080` - SQL prod: `.\SQLEXPRESS` / `SolutionErp` / `vrapp` - SQL dev: `(localdb)\MSSQLLocalDB` / `SolutionErp_Dev` diff --git a/docs/gotchas.md b/docs/gotchas.md index da2120a..e052330 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -328,7 +328,7 @@ subdomain có ARR proxy về `:3000`. **SOLUTION_ERP relevance:** - API host trong IIS app pool out-of-process (ANCM tự quản lý port Kestrel ephemeral) → risk THẤP -- FE gọi trực tiếp `https://api.huypham.vn` (không ARR proxy) → risk THẤP +- FE gọi trực tiếp `https://api.solutions.com.vn` (không ARR proxy) → risk THẤP - **NHƯNG** nếu tương lai thêm ARR reverse proxy (fe-admin/user `/api` proxy) hoặc deploy Kestrel standalone qua NSSM → PHẢI apply 3 rules trên - Scripts + skill doc đã update `localhost` → `127.0.0.1` để đồng bộ diff --git a/docs/guides/vps-setup.md b/docs/guides/vps-setup.md index 76a2d5c..5ad2538 100644 --- a/docs/guides/vps-setup.md +++ b/docs/guides/vps-setup.md @@ -7,7 +7,7 @@ - **VPS OS:** Windows Server (có IIS + SQL Server) - **Shared với:** VIETREPORT project — naming isolation bắt buộc -- **DNS đã trỏ:** `api.huypham.vn`, `admin.huypham.vn`, `user.huypham.vn`, `git.baocaogiaoduc.vn` → `103.124.94.38` +- **DNS đã trỏ:** `api.solutions.com.vn`, `admin.solutions.com.vn`, `eoffice.solutions.com.vn`, `git.baocaogiaoduc.vn` → `103.124.94.38` - **Prefix resources:** `SolutionErp-*` (app pool, site), `SolutionErp` (DB), `C:\inetpub\solution-erp\` (path) ## 1. Prerequisites trên VPS (đã có sẵn với VIETREPORT) @@ -92,21 +92,21 @@ dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=S ```bash # Health check -curl https://api.huypham.vn/health/live # → Healthy -curl https://api.huypham.vn/health/ready # → Healthy (DB probe) +curl https://api.solutions.com.vn/health/live # → Healthy +curl https://api.solutions.com.vn/health/ready # → Healthy (DB probe) # Login -curl -X POST https://api.huypham.vn/api/auth/login \ +curl -X POST https://api.solutions.com.vn/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@solutionerp.local","password":"Admin@123456"}' # → accessToken JWT # FE -open https://admin.huypham.vn # fe-admin login page -open https://user.huypham.vn # fe-user login page +open https://admin.solutions.com.vn # fe-admin login page +open https://eoffice.solutions.com.vn # fe-user login page # SSL grade -# https://www.ssllabs.com/ssltest/analyze.html?d=api.huypham.vn +# https://www.ssllabs.com/ssltest/analyze.html?d=api.solutions.com.vn ``` ## 7. Sau go-live (bắt buộc) @@ -114,7 +114,7 @@ open https://user.huypham.vn # fe-user login page - [ ] **Đổi password admin** từ `Admin@123456` → mạnh. Warning log xuất hiện khi còn dùng default. - [ ] **Rotate secrets** đã post trong chat (SA, vrapp, Gitea token, JWT) — tất cả đã vượt khỏi VPS, cần đổi mới - [ ] **Backup SQL** daily schedule: `schtasks /Create /TN 'SolutionErp SQL Backup' /TR 'powershell -File C:\solution-erp\scripts\backup-sql.ps1 -SaPassword ' /SC DAILY /ST 02:00 /RU SYSTEM` -- [ ] **Disable Swagger prod**: Program.cs đã có `if (IsDevelopment())` — verify URL `https://api.huypham.vn/swagger` → 404 +- [ ] **Disable Swagger prod**: Program.cs đã có `if (IsDevelopment())` — verify URL `https://api.solutions.com.vn/swagger` → 404 - [ ] **Monitor**: kiểm `C:\inetpub\solution-erp\logs\` ngày đầu, watch for ERR ## 8. Co-existence với VIETREPORT — checklist diff --git a/fe-admin/.env.production b/fe-admin/.env.production index 68e8bc8..902e9aa 100644 --- a/fe-admin/.env.production +++ b/fe-admin/.env.production @@ -1 +1 @@ -VITE_API_BASE_URL=https://api.huypham.vn +VITE_API_BASE_URL=https://api.solutions.com.vn diff --git a/fe-admin/src/lib/api.ts b/fe-admin/src/lib/api.ts index f11407e..b3b90c1 100644 --- a/fe-admin/src/lib/api.ts +++ b/fe-admin/src/lib/api.ts @@ -5,7 +5,7 @@ export const REFRESH_KEY = 'solution-erp-admin-refresh' export const USER_KEY = 'solution-erp-admin-user' // Dev: Vite proxy /api → :5443 (vite.config.ts) -// Prod: VITE_API_BASE_URL = https://api.huypham.vn (env.production) +// Prod: VITE_API_BASE_URL = https://api.solutions.com.vn (env.production) const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '') + '/api' export const api = axios.create({ diff --git a/fe-admin/src/lib/realtime.ts b/fe-admin/src/lib/realtime.ts index 57d62d7..eefc4cb 100644 --- a/fe-admin/src/lib/realtime.ts +++ b/fe-admin/src/lib/realtime.ts @@ -4,7 +4,7 @@ import { TOKEN_KEY } from '@/lib/api' // Hub URL resolution: // - Dev: Vite proxy forwards /api → :5443 but SignalR bypasses axios, so we // hit the API origin directly from the browser. -// - Prod: VITE_API_BASE_URL (https://api.huypham.vn) +// - Prod: VITE_API_BASE_URL (https://api.solutions.com.vn) const HUB_URL = (import.meta.env.VITE_API_BASE_URL ?? window.location.origin) + '/hubs/notifications' let connection: HubConnection | null = null diff --git a/fe-user/.env.production b/fe-user/.env.production index 68e8bc8..902e9aa 100644 --- a/fe-user/.env.production +++ b/fe-user/.env.production @@ -1 +1 @@ -VITE_API_BASE_URL=https://api.huypham.vn +VITE_API_BASE_URL=https://api.solutions.com.vn diff --git a/fe-user/src/lib/api.ts b/fe-user/src/lib/api.ts index f3913ff..8011cfe 100644 --- a/fe-user/src/lib/api.ts +++ b/fe-user/src/lib/api.ts @@ -5,7 +5,7 @@ export const REFRESH_KEY = 'solution-erp-user-refresh' export const USER_KEY = 'solution-erp-user-user' // Dev: Vite proxy /api → :5443 (vite.config.ts) -// Prod: VITE_API_BASE_URL = https://api.huypham.vn (env.production) +// Prod: VITE_API_BASE_URL = https://api.solutions.com.vn (env.production) const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '') + '/api' export const api = axios.create({ diff --git a/fe-user/src/lib/realtime.ts b/fe-user/src/lib/realtime.ts index 57d62d7..eefc4cb 100644 --- a/fe-user/src/lib/realtime.ts +++ b/fe-user/src/lib/realtime.ts @@ -4,7 +4,7 @@ import { TOKEN_KEY } from '@/lib/api' // Hub URL resolution: // - Dev: Vite proxy forwards /api → :5443 but SignalR bypasses axios, so we // hit the API origin directly from the browser. -// - Prod: VITE_API_BASE_URL (https://api.huypham.vn) +// - Prod: VITE_API_BASE_URL (https://api.solutions.com.vn) const HUB_URL = (import.meta.env.VITE_API_BASE_URL ?? window.location.origin) + '/hubs/notifications' let connection: HubConnection | null = null diff --git a/scripts/deploy-all.ps1 b/scripts/deploy-all.ps1 index 870b942..032cc67 100644 --- a/scripts/deploy-all.ps1 +++ b/scripts/deploy-all.ps1 @@ -15,7 +15,7 @@ # - SQL Server with login 'sa' + 'vrapp' exists # - .NET 10 Hosting Bundle installed # - Port 80+443 firewall open -# - DNS api/admin/user.huypham.vn pointing to VPS +# - DNS api/admin/eoffice.solutions.com.vn pointing to VPS [CmdletBinding()] param( @@ -120,7 +120,7 @@ if (Test-Path $example) { if (-not $SkipSsl) { Write-Banner "Step 4/4 (SSL): win-acme Let's Encrypt" Write-Host "WARNING: Port 80 must be reachable from Internet (Let's Encrypt HTTP-01)" - Write-Host " Test from outside: curl http://api.huypham.vn" + Write-Host " Test from outside: curl http://api.solutions.com.vn" $confirm = Read-Host "Continue? (y/N)" if ($confirm -eq 'y') { try { @@ -151,9 +151,9 @@ Write-Banner "[OK] DEPLOY ALL DONE - $([int]$duration.TotalMinutes)m $([int]($du Write-Host "" Write-Host "Next:" Write-Host " 1. Verify 3 domains:" -Write-Host " curl https://api.huypham.vn/health/live" -Write-Host " curl -I https://admin.huypham.vn" -Write-Host " curl -I https://user.huypham.vn" +Write-Host " curl https://api.solutions.com.vn/health/live" +Write-Host " curl -I https://admin.solutions.com.vn" +Write-Host " curl -I https://eoffice.solutions.com.vn" Write-Host "" Write-Host " 2. Set remaining 2 Gitea secrets (if not done):" Write-Host " https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/settings/actions/secrets" diff --git a/scripts/migrate-domains.ps1 b/scripts/migrate-domains.ps1 new file mode 100644 index 0000000..4725220 --- /dev/null +++ b/scripts/migrate-domains.ps1 @@ -0,0 +1,210 @@ +# Migrate domains from *.huypham.vn to solutions.com.vn (Phase 6+ rebrand). +# Run trên VPS với admin privilege. Idempotent — chạy lại nếu fail giữa chừng. +# +# Workflow (3 IIS sites): +# 1. Add binding mới cho domain mới (*:80 + *:443 với cert self-signed tạm) +# 2. Run win-acme xin cert Let's Encrypt qua HTTP-01 challenge (port 80) +# 3. Verify /health/ready + /health/live qua domain mới +# 4. Remove binding cũ (*:huypham.vn + *:user.huypham.vn) — OPTIONAL via -RemoveOld +# +# Usage: +# .\migrate-domains.ps1 # giữ binding cũ, thêm mới +# .\migrate-domains.ps1 -RemoveOld # xóa binding cũ sau verify +# .\migrate-domains.ps1 -SkipCert # bỏ bước win-acme (nếu anh đã có cert) +# +# Prereq: +# - DNS 3 domain mới đã trỏ 103.124.94.38 (user confirm) +# - Port 80+443 firewall open cho internet +# - win-acme đã cài (script setup-ssl.ps1 cài sẵn) +# - IIS binding cũ còn active (huypham.vn) +# +# Rollback: nếu fail, binding cũ vẫn còn → site vẫn phục vụ qua *.huypham.vn + +[CmdletBinding()] +param( + [string]$NewApi = "api.solutions.com.vn", + [string]$NewAdmin = "admin.solutions.com.vn", + [string]$NewUser = "eoffice.solutions.com.vn", + [string]$OldApi = "api.huypham.vn", + [string]$OldAdmin = "admin.huypham.vn", + [string]$OldUser = "user.huypham.vn", + [string]$AdminEmail = "admin@huypham.vn", + [switch]$RemoveOld, + [switch]$SkipCert +) + +$ErrorActionPreference = 'Stop' +Import-Module WebAdministration + +function Write-Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } +function Write-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green } +function Write-Warn2($msg){ Write-Host " [!!] $msg" -ForegroundColor Yellow } + +# ===================== 1. Pre-flight checks ===================== +Write-Step "Pre-flight checks" + +# 1a. Verify DNS của 3 domain mới trỏ đúng IP VPS +$vpsIp = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing -TimeoutSec 5).Content.Trim() +Write-Host " VPS public IP: $vpsIp" + +foreach ($d in @($NewApi, $NewAdmin, $NewUser)) { + try { + $resolved = (Resolve-DnsName -Name $d -Type A -ErrorAction Stop | Select-Object -First 1).IPAddress + if ($resolved -eq $vpsIp) { + Write-OK "DNS $d -> $resolved" + } else { + Write-Warn2 "DNS $d -> $resolved (KHONG KHOP VPS IP $vpsIp) — Let's Encrypt se fail!" + Read-Host "Continue anyway? (Ctrl+C abort, Enter continue)" + } + } catch { + Write-Warn2 "DNS resolve FAIL cho $d — $_" + exit 1 + } +} + +# 1b. Verify 3 IIS site tồn tại +$siteMap = @( + @{ Site = "SolutionErp-Api"; Old = $OldApi; New = $NewApi }, + @{ Site = "SolutionErp-Admin"; Old = $OldAdmin; New = $NewAdmin }, + @{ Site = "SolutionErp-User"; Old = $OldUser; New = $NewUser } +) +foreach ($s in $siteMap) { + if (-not (Test-Path "IIS:\Sites\$($s.Site)")) { + Write-Error "Site '$($s.Site)' khong ton tai. Chay setup-iis-sites.ps1 truoc." + exit 1 + } + Write-OK "IIS site $($s.Site) ready" +} + +# ===================== 2. Add new bindings (keep old) ===================== +Write-Step "Add binding moi cho 3 site (giu binding cu)" + +foreach ($s in $siteMap) { + $site = $s.Site + $new = $s.New + + # Check binding HTTP (port 80) cho domain moi + $existingHttp = Get-WebBinding -Name $site -Protocol http -HostHeader $new -ErrorAction SilentlyContinue + if (-not $existingHttp) { + New-WebBinding -Name $site -IPAddress "*" -Port 80 -HostHeader $new -Protocol http | Out-Null + Write-OK "Add HTTP binding: $site *:80:$new" + } else { + Write-Host " HTTP binding exists: $site *:80:$new" + } +} + +# ===================== 3. Request cert Let's Encrypt (win-acme) ===================== +if (-not $SkipCert) { + Write-Step "Request cert Let's Encrypt cho 3 domain moi" + + $WacsExe = "C:\Program Files\win-acme\wacs.exe" + if (-not (Test-Path $WacsExe)) { + Write-Error "win-acme chua cai. Run setup-ssl.ps1 truoc, hoac dùng -SkipCert." + exit 1 + } + + foreach ($s in $siteMap) { + Write-Host "" + Write-Host "==> Issue cert $($s.New)" -ForegroundColor Cyan + + $siteId = (Get-Website $s.Site).Id + $wacsArgs = @( + "--target", "manual", + "--host", $s.New, + "--store", "certificatestore", + "--installation", "iis", + "--installationsiteid", $siteId, + "--accepttos", + "--emailaddress", $AdminEmail + ) + & $WacsExe @wacsArgs + if ($LASTEXITCODE -ne 0) { + Write-Warn2 "Cert $($s.New) FAIL exit $LASTEXITCODE — skip, check:" + Write-Warn2 " 1. Port 80 internet -> VPS open?" + Write-Warn2 " 2. DNS $($s.New) -> $vpsIp?" + Write-Warn2 " 3. HTTP binding $($s.Site) *:80:$($s.New) created?" + } else { + Write-OK "Cert $($s.New) installed (auto HTTPS binding + http->https redirect)" + } + } +} else { + Write-Host "==> SkipCert flag — bo qua xin cert Let's Encrypt" -ForegroundColor Yellow +} + +# ===================== 4. Verify endpoint moi ===================== +Write-Step "Verify endpoint moi" + +Start-Sleep -Seconds 3 # cho IIS reload binding + +function Test-Endpoint($url, $expectOk) { + try { + $r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 10 -SkipCertificateCheck + if ($r.StatusCode -eq 200) { Write-OK "$url -> 200 OK"; return $true } + Write-Warn2 "$url -> $($r.StatusCode) (expected 200)"; return $false + } catch { + Write-Warn2 "$url FAIL: $_" + return $false + } +} + +$apiLive = Test-Endpoint "https://$NewApi/health/live" $true +$apiReady = Test-Endpoint "https://$NewApi/health/ready" $true +$adminOk = Test-Endpoint "https://$NewAdmin" $true +$userOk = Test-Endpoint "https://$NewUser" $true + +$allOk = $apiLive -and $apiReady -and $adminOk -and $userOk + +# ===================== 5. Remove old bindings (optional) ===================== +if ($RemoveOld) { + if (-not $allOk) { + Write-Warn2 "Endpoint moi CHUA verify OK — skip remove old binding (giu fallback)" + } else { + Write-Step "Remove binding cu (huypham.vn)" + foreach ($s in $siteMap) { + $site = $s.Site + $old = $s.Old + + # Remove HTTP + $httpOld = Get-WebBinding -Name $site -Protocol http -HostHeader $old -ErrorAction SilentlyContinue + if ($httpOld) { + Remove-WebBinding -Name $site -Protocol http -HostHeader $old -Port 80 + Write-OK "Removed HTTP *:80:$old from $site" + } + + # Remove HTTPS + $httpsOld = Get-WebBinding -Name $site -Protocol https -HostHeader $old -ErrorAction SilentlyContinue + if ($httpsOld) { + Remove-WebBinding -Name $site -Protocol https -HostHeader $old -Port 443 + Write-OK "Removed HTTPS *:443:$old from $site" + } + } + } +} else { + Write-Host "" + Write-Host "[INFO] Binding cu (*.huypham.vn) van giu active. Run -RemoveOld sau khi verify xong." -ForegroundColor Yellow +} + +# ===================== 6. Summary ===================== +Write-Host "" +Write-Host "=" * 70 -ForegroundColor Cyan +Write-Host " MIGRATION DONE" -ForegroundColor Cyan +Write-Host "=" * 70 -ForegroundColor Cyan +Write-Host "" +Write-Host "3 domain moi:" +Write-Host " - https://$NewApi/health/live $(if ($apiLive) {'[OK]'} else {'[FAIL]'})" +Write-Host " - https://$NewApi/health/ready $(if ($apiReady) {'[OK]'} else {'[FAIL]'})" +Write-Host " - https://$NewAdmin $(if ($adminOk) {'[OK]'} else {'[FAIL]'})" +Write-Host " - https://$NewUser $(if ($userOk) {'[OK]'} else {'[FAIL]'})" +Write-Host "" +Write-Host "Next step:" +Write-Host " 1. Trigger CI/CD redeploy (push empty commit hoac manual workflow dispatch)" +Write-Host " → BE rebuild voi CORS moi (allow https://admin.solutions.com.vn + eoffice.solutions.com.vn)" +Write-Host " → FE rebuild voi VITE_API_BASE_URL=https://api.solutions.com.vn" +Write-Host " 2. Test login qua browser: https://$NewAdmin + https://$NewUser" +Write-Host " 3. Sau 1-2 ngay verify stable → chay lai script voi -RemoveOld" +Write-Host " .\migrate-domains.ps1 -RemoveOld -SkipCert" +Write-Host "" +if (-not $allOk) { + Write-Warn2 "CO ENDPOINT FAIL — kiem tra Event Log + IIS log truoc khi proceed." + exit 1 +} diff --git a/scripts/setup-iis-sites.ps1 b/scripts/setup-iis-sites.ps1 index ad2aeb7..471d2c3 100644 --- a/scripts/setup-iis-sites.ps1 +++ b/scripts/setup-iis-sites.ps1 @@ -24,9 +24,9 @@ $PathTemplates = "$Root\api\wwwroot\templates" $AppPoolApi = "SolutionErp-Api" -$DomainApi = "api.huypham.vn" -$DomainAdmin = "admin.huypham.vn" -$DomainUser = "user.huypham.vn" +$DomainApi = "api.solutions.com.vn" +$DomainAdmin = "admin.solutions.com.vn" +$DomainUser = "eoffice.solutions.com.vn" $SiteApi = "SolutionErp-Api" $SiteAdmin = "SolutionErp-Admin" diff --git a/scripts/setup-ssl.ps1 b/scripts/setup-ssl.ps1 index 8045348..ecf3413 100644 --- a/scripts/setup-ssl.ps1 +++ b/scripts/setup-ssl.ps1 @@ -8,7 +8,7 @@ # Prereq: # - IIS sites created (run setup-iis-sites.ps1 first) # - Port 80 from Internet -> VPS open (Let's Encrypt HTTP-01 challenge) -# - 3 domains api/admin/user.huypham.vn pointing DNS to VPS IP +# - 3 domains api/admin/eoffice.solutions.com.vn pointing DNS to VPS IP # # Output: # - 3 cert in Windows Cert Store (LocalMachine\My) @@ -37,9 +37,9 @@ if (-not (Test-Path $WacsExe)) { # ===================== 2. Check IIS sites exist ===================== Import-Module WebAdministration $domains = @( - @{ Site = "SolutionErp-Api"; HostName = "api.huypham.vn" }, - @{ Site = "SolutionErp-Admin"; HostName = "admin.huypham.vn" }, - @{ Site = "SolutionErp-User"; HostName = "user.huypham.vn" } + @{ Site = "SolutionErp-Api"; HostName = "api.solutions.com.vn" }, + @{ Site = "SolutionErp-Admin"; HostName = "admin.solutions.com.vn" }, + @{ Site = "SolutionErp-User"; HostName = "eoffice.solutions.com.vn" } ) foreach ($d in $domains) { @@ -100,5 +100,5 @@ if ($task) { Write-Host "" Write-Host "[OK] SSL setup DONE" -ForegroundColor Green -Write-Host " Test: openssl s_client -connect api.huypham.vn:443 < /dev/null | openssl x509 -noout -subject -dates" -Write-Host " Or browser: https://api.huypham.vn/health/live" +Write-Host " Test: openssl s_client -connect api.solutions.com.vn:443 < /dev/null | openssl x509 -noout -subject -dates" +Write-Host " Or browser: https://api.solutions.com.vn/health/live" diff --git a/src/Backend/SolutionErp.Api/appsettings.Production.json.example b/src/Backend/SolutionErp.Api/appsettings.Production.json.example index 0d572e9..17c5489 100644 --- a/src/Backend/SolutionErp.Api/appsettings.Production.json.example +++ b/src/Backend/SolutionErp.Api/appsettings.Production.json.example @@ -10,8 +10,8 @@ "RefreshTokenExpiryDays": 7 }, "AllowedOrigins": [ - "https://admin.huypham.vn", - "https://user.huypham.vn" + "https://admin.solutions.com.vn", + "https://eoffice.solutions.com.vn" ], "Identity": { "Password": {