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