[CLAUDE] Scripts: fix migrate-domains.ps1 ASCII-only (gotcha #30 PS 5.1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m46s

This commit is contained in:
pqhuy1987
2026-04-24 09:55:57 +07:00
parent 66c1a5c170
commit b93dacff44

View File

@ -1,24 +1,18 @@
# Migrate domains from *.huypham.vn to solutions.com.vn (Phase 6+ rebrand). # 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. # Run on VPS with admin privilege. Idempotent - retry if fail mid-way.
# #
# Workflow (3 IIS sites): # Workflow (3 IIS sites):
# 1. Add binding mới cho domain mới (*:80 + *:443 với cert self-signed tạm) # 1. Add binding for new domain (port 80)
# 2. Run win-acme xin cert Let's Encrypt qua HTTP-01 challenge (port 80) # 2. Run win-acme for Let's Encrypt cert via HTTP-01 challenge
# 3. Verify /health/ready + /health/live qua domain mới # 3. Verify /health/ready + /health/live via new domain
# 4. Remove binding cũ (*:huypham.vn + *:user.huypham.vn) — OPTIONAL via -RemoveOld # 4. Remove old binding (optional, via -RemoveOld)
# #
# Usage: # Usage:
# .\migrate-domains.ps1 # giữ binding cũ, thêm mới # .\migrate-domains.ps1 # keep old binding, add new
# .\migrate-domains.ps1 -RemoveOld # xóa binding cũ sau verify # .\migrate-domains.ps1 -RemoveOld # remove old after verify
# .\migrate-domains.ps1 -SkipCert # bỏ bước win-acme (nếu anh đã có cert) # .\migrate-domains.ps1 -SkipCert # skip win-acme step
# #
# Prereq: # ASCII-only per gotcha #30 (PS 5.1 parser fail on UTF-8 diacritics).
# - 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()] [CmdletBinding()]
param( param(
@ -43,26 +37,28 @@ function Write-Warn2($msg){ Write-Host " [!!] $msg" -ForegroundColor Yellow }
# ===================== 1. Pre-flight checks ===================== # ===================== 1. Pre-flight checks =====================
Write-Step "Pre-flight checks" Write-Step "Pre-flight checks"
# 1a. Verify DNS của 3 domain mới trỏ đúng IP VPS $vpsIp = "103.124.94.38"
$vpsIp = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing -TimeoutSec 5).Content.Trim() try {
$vpsIp = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing -TimeoutSec 5).Content.Trim()
} catch {
Write-Warn2 "Cannot detect public IP via ipify - fallback to hardcoded $vpsIp"
}
Write-Host " VPS public IP: $vpsIp" Write-Host " VPS public IP: $vpsIp"
foreach ($d in @($NewApi, $NewAdmin, $NewUser)) { foreach ($d in @($NewApi, $NewAdmin, $NewUser)) {
try { try {
$resolved = (Resolve-DnsName -Name $d -Type A -ErrorAction Stop | Select-Object -First 1).IPAddress $resolved = (Resolve-DnsName -Name $d -Type A -ErrorAction Stop | Select-Object -First 1).IPAddress
if ($resolved -eq $vpsIp) { if ($resolved -eq $vpsIp) {
Write-OK "DNS $d -> $resolved" Write-OK "DNS $d = $resolved"
} else { } else {
Write-Warn2 "DNS $d -> $resolved (KHONG KHOP VPS IP $vpsIp) Let's Encrypt se fail!" Write-Warn2 "DNS $d = $resolved (not matching VPS IP $vpsIp) - Let's Encrypt may fail"
Read-Host "Continue anyway? (Ctrl+C abort, Enter continue)"
} }
} catch { } catch {
Write-Warn2 "DNS resolve FAIL cho $d$_" Write-Warn2 "DNS resolve FAIL for $d"
exit 1 exit 1
} }
} }
# 1b. Verify 3 IIS site tồn tại
$siteMap = @( $siteMap = @(
@{ Site = "SolutionErp-Api"; Old = $OldApi; New = $NewApi }, @{ Site = "SolutionErp-Api"; Old = $OldApi; New = $NewApi },
@{ Site = "SolutionErp-Admin"; Old = $OldAdmin; New = $NewAdmin }, @{ Site = "SolutionErp-Admin"; Old = $OldAdmin; New = $NewAdmin },
@ -70,20 +66,19 @@ $siteMap = @(
) )
foreach ($s in $siteMap) { foreach ($s in $siteMap) {
if (-not (Test-Path "IIS:\Sites\$($s.Site)")) { if (-not (Test-Path "IIS:\Sites\$($s.Site)")) {
Write-Error "Site '$($s.Site)' khong ton tai. Chay setup-iis-sites.ps1 truoc." Write-Error "Site '$($s.Site)' not found. Run setup-iis-sites.ps1 first."
exit 1 exit 1
} }
Write-OK "IIS site $($s.Site) ready" Write-OK "IIS site $($s.Site) ready"
} }
# ===================== 2. Add new bindings (keep old) ===================== # ===================== 2. Add new bindings (keep old) =====================
Write-Step "Add binding moi cho 3 site (giu binding cu)" Write-Step "Add HTTP binding for 3 new domains"
foreach ($s in $siteMap) { foreach ($s in $siteMap) {
$site = $s.Site $site = $s.Site
$new = $s.New $new = $s.New
# Check binding HTTP (port 80) cho domain moi
$existingHttp = Get-WebBinding -Name $site -Protocol http -HostHeader $new -ErrorAction SilentlyContinue $existingHttp = Get-WebBinding -Name $site -Protocol http -HostHeader $new -ErrorAction SilentlyContinue
if (-not $existingHttp) { if (-not $existingHttp) {
New-WebBinding -Name $site -IPAddress "*" -Port 80 -HostHeader $new -Protocol http | Out-Null New-WebBinding -Name $site -IPAddress "*" -Port 80 -HostHeader $new -Protocol http | Out-Null
@ -93,19 +88,19 @@ foreach ($s in $siteMap) {
} }
} }
# ===================== 3. Request cert Let's Encrypt (win-acme) ===================== # ===================== 3. Request cert via win-acme =====================
if (-not $SkipCert) { if (-not $SkipCert) {
Write-Step "Request cert Let's Encrypt cho 3 domain moi" Write-Step "Request Let's Encrypt cert for 3 new domains"
$WacsExe = "C:\Program Files\win-acme\wacs.exe" $WacsExe = "C:\Program Files\win-acme\wacs.exe"
if (-not (Test-Path $WacsExe)) { if (-not (Test-Path $WacsExe)) {
Write-Error "win-acme chua cai. Run setup-ssl.ps1 truoc, hoac dùng -SkipCert." Write-Error "win-acme not installed. Run setup-ssl.ps1 first, or use -SkipCert."
exit 1 exit 1
} }
foreach ($s in $siteMap) { foreach ($s in $siteMap) {
Write-Host "" Write-Host ""
Write-Host "==> Issue cert $($s.New)" -ForegroundColor Cyan Write-Host "==> Cert for $($s.New)" -ForegroundColor Cyan
$siteId = (Get-Website $s.Site).Id $siteId = (Get-Website $s.Site).Id
$wacsArgs = @( $wacsArgs = @(
@ -119,59 +114,57 @@ if (-not $SkipCert) {
) )
& $WacsExe @wacsArgs & $WacsExe @wacsArgs
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Warn2 "Cert $($s.New) FAIL exit $LASTEXITCODE — skip, check:" Write-Warn2 "Cert $($s.New) FAIL exit $LASTEXITCODE - check:"
Write-Warn2 " 1. Port 80 internet -> VPS open?" Write-Warn2 " 1. Port 80 internet -> VPS open?"
Write-Warn2 " 2. DNS $($s.New) -> $vpsIp?" Write-Warn2 " 2. DNS $($s.New) -> $vpsIp?"
Write-Warn2 " 3. HTTP binding $($s.Site) *:80:$($s.New) created?" Write-Warn2 " 3. HTTP binding created?"
} else { } else {
Write-OK "Cert $($s.New) installed (auto HTTPS binding + http->https redirect)" Write-OK "Cert $($s.New) installed (HTTPS binding + http->https)"
} }
} }
} else { } else {
Write-Host "==> SkipCert flag bo qua xin cert Let's Encrypt" -ForegroundColor Yellow Write-Host "==> SkipCert flag - bypass win-acme" -ForegroundColor Yellow
} }
# ===================== 4. Verify endpoint moi ===================== # ===================== 4. Verify new endpoints =====================
Write-Step "Verify endpoint moi" Write-Step "Verify new endpoints"
Start-Sleep -Seconds 3 # cho IIS reload binding Start-Sleep -Seconds 3
function Test-Endpoint($url, $expectOk) { function Test-Endpoint($url) {
try { try {
$r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 10 -SkipCertificateCheck $r = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 10 -SkipCertificateCheck
if ($r.StatusCode -eq 200) { Write-OK "$url -> 200 OK"; return $true } if ($r.StatusCode -eq 200) { Write-OK "$url = 200 OK"; return $true }
Write-Warn2 "$url -> $($r.StatusCode) (expected 200)"; return $false Write-Warn2 "$url = $($r.StatusCode) (expected 200)"; return $false
} catch { } catch {
Write-Warn2 "$url FAIL: $_" Write-Warn2 "$url FAIL"
return $false return $false
} }
} }
$apiLive = Test-Endpoint "https://$NewApi/health/live" $true $apiLive = Test-Endpoint "https://$NewApi/health/live"
$apiReady = Test-Endpoint "https://$NewApi/health/ready" $true $apiReady = Test-Endpoint "https://$NewApi/health/ready"
$adminOk = Test-Endpoint "https://$NewAdmin" $true $adminOk = Test-Endpoint "https://$NewAdmin"
$userOk = Test-Endpoint "https://$NewUser" $true $userOk = Test-Endpoint "https://$NewUser"
$allOk = $apiLive -and $apiReady -and $adminOk -and $userOk $allOk = $apiLive -and $apiReady -and $adminOk -and $userOk
# ===================== 5. Remove old bindings (optional) ===================== # ===================== 5. Remove old bindings (optional) =====================
if ($RemoveOld) { if ($RemoveOld) {
if (-not $allOk) { if (-not $allOk) {
Write-Warn2 "Endpoint moi CHUA verify OK skip remove old binding (giu fallback)" Write-Warn2 "New endpoints not verified OK - skip removing old (keep fallback)"
} else { } else {
Write-Step "Remove binding cu (huypham.vn)" Write-Step "Remove old bindings (huypham.vn)"
foreach ($s in $siteMap) { foreach ($s in $siteMap) {
$site = $s.Site $site = $s.Site
$old = $s.Old $old = $s.Old
# Remove HTTP
$httpOld = Get-WebBinding -Name $site -Protocol http -HostHeader $old -ErrorAction SilentlyContinue $httpOld = Get-WebBinding -Name $site -Protocol http -HostHeader $old -ErrorAction SilentlyContinue
if ($httpOld) { if ($httpOld) {
Remove-WebBinding -Name $site -Protocol http -HostHeader $old -Port 80 Remove-WebBinding -Name $site -Protocol http -HostHeader $old -Port 80
Write-OK "Removed HTTP *:80:$old from $site" Write-OK "Removed HTTP *:80:$old from $site"
} }
# Remove HTTPS
$httpsOld = Get-WebBinding -Name $site -Protocol https -HostHeader $old -ErrorAction SilentlyContinue $httpsOld = Get-WebBinding -Name $site -Protocol https -HostHeader $old -ErrorAction SilentlyContinue
if ($httpsOld) { if ($httpsOld) {
Remove-WebBinding -Name $site -Protocol https -HostHeader $old -Port 443 Remove-WebBinding -Name $site -Protocol https -HostHeader $old -Port 443
@ -181,30 +174,33 @@ if ($RemoveOld) {
} }
} else { } else {
Write-Host "" Write-Host ""
Write-Host "[INFO] Binding cu (*.huypham.vn) van giu active. Run -RemoveOld sau khi verify xong." -ForegroundColor Yellow Write-Host "[INFO] Old bindings (*.huypham.vn) still active. Run with -RemoveOld after verify." -ForegroundColor Yellow
} }
# ===================== 6. Summary ===================== # ===================== 6. Summary =====================
Write-Host "" Write-Host ""
Write-Host "=" * 70 -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host " MIGRATION DONE" -ForegroundColor Cyan Write-Host " MIGRATION DONE" -ForegroundColor Cyan
Write-Host "=" * 70 -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host "" Write-Host ""
Write-Host "3 domain moi:" Write-Host "3 new domains:"
Write-Host " - https://$NewApi/health/live $(if ($apiLive) {'[OK]'} else {'[FAIL]'})" $statusApi = if ($apiLive) { "[OK]" } else { "[FAIL]" }
Write-Host " - https://$NewApi/health/ready $(if ($apiReady) {'[OK]'} else {'[FAIL]'})" $statusReady = if ($apiReady) { "[OK]" } else { "[FAIL]" }
Write-Host " - https://$NewAdmin $(if ($adminOk) {'[OK]'} else {'[FAIL]'})" $statusAdmin = if ($adminOk) { "[OK]" } else { "[FAIL]" }
Write-Host " - https://$NewUser $(if ($userOk) {'[OK]'} else {'[FAIL]'})" $statusUser = if ($userOk) { "[OK]" } else { "[FAIL]" }
Write-Host " - https://$NewApi/health/live $statusApi"
Write-Host " - https://$NewApi/health/ready $statusReady"
Write-Host " - https://$NewAdmin $statusAdmin"
Write-Host " - https://$NewUser $statusUser"
Write-Host "" Write-Host ""
Write-Host "Next step:" Write-Host "Next:"
Write-Host " 1. Trigger CI/CD redeploy (push empty commit hoac manual workflow dispatch)" Write-Host " 1. Trigger CI/CD redeploy (push empty commit)"
Write-Host " BE rebuild voi CORS moi (allow https://admin.solutions.com.vn + eoffice.solutions.com.vn)" Write-Host " - BE rebuild with new CORS"
Write-Host " FE rebuild voi VITE_API_BASE_URL=https://api.solutions.com.vn" Write-Host " - FE rebuild with VITE_API_BASE_URL=https://$NewApi"
Write-Host " 2. Test login qua browser: https://$NewAdmin + https://$NewUser" Write-Host " 2. Test login via browser"
Write-Host " 3. Sau 1-2 ngay verify stable → chay lai script voi -RemoveOld" Write-Host " 3. After 1-2 days verify stable: .\migrate-domains.ps1 -RemoveOld -SkipCert"
Write-Host " .\migrate-domains.ps1 -RemoveOld -SkipCert"
Write-Host "" Write-Host ""
if (-not $allOk) { if (-not $allOk) {
Write-Warn2 "CO ENDPOINT FAIL — kiem tra Event Log + IIS log truoc khi proceed." Write-Warn2 "Some endpoints failed - check Event Log and IIS log before proceed."
exit 1 exit 1
} }