# Migrate domains from *.huypham.vn to solutions.com.vn (Phase 6+ rebrand). # Run on VPS with admin privilege. Idempotent - retry if fail mid-way. # # Workflow (3 IIS sites): # 1. Add binding for new domain (port 80) # 2. Run win-acme for Let's Encrypt cert via HTTP-01 challenge # 3. Verify /health/ready + /health/live via new domain # 4. Remove old binding (optional, via -RemoveOld) # # Usage: # .\migrate-domains.ps1 # keep old binding, add new # .\migrate-domains.ps1 -RemoveOld # remove old after verify # .\migrate-domains.ps1 -SkipCert # skip win-acme step # # ASCII-only per gotcha #30 (PS 5.1 parser fail on UTF-8 diacritics). [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" $vpsIp = "103.124.94.38" 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" 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 (not matching VPS IP $vpsIp) - Let's Encrypt may fail" } } catch { Write-Warn2 "DNS resolve FAIL for $d" exit 1 } } $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)' not found. Run setup-iis-sites.ps1 first." exit 1 } Write-OK "IIS site $($s.Site) ready" } # ===================== 2. Add new bindings (keep old) ===================== Write-Step "Add HTTP binding for 3 new domains" foreach ($s in $siteMap) { $site = $s.Site $new = $s.New $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 via win-acme ===================== if (-not $SkipCert) { Write-Step "Request Let's Encrypt cert for 3 new domains" $WacsExe = "C:\Program Files\win-acme\wacs.exe" if (-not (Test-Path $WacsExe)) { Write-Error "win-acme not installed. Run setup-ssl.ps1 first, or use -SkipCert." exit 1 } foreach ($s in $siteMap) { Write-Host "" Write-Host "==> Cert for $($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 - check:" Write-Warn2 " 1. Port 80 internet -> VPS open?" Write-Warn2 " 2. DNS $($s.New) -> $vpsIp?" Write-Warn2 " 3. HTTP binding created?" } else { Write-OK "Cert $($s.New) installed (HTTPS binding + http->https)" } } } else { Write-Host "==> SkipCert flag - bypass win-acme" -ForegroundColor Yellow } # ===================== 4. Verify new endpoints ===================== Write-Step "Verify new endpoints" Start-Sleep -Seconds 3 function Test-Endpoint($url) { 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" $apiReady = Test-Endpoint "https://$NewApi/health/ready" $adminOk = Test-Endpoint "https://$NewAdmin" $userOk = Test-Endpoint "https://$NewUser" $allOk = $apiLive -and $apiReady -and $adminOk -and $userOk # ===================== 5. Remove old bindings (optional) ===================== if ($RemoveOld) { if (-not $allOk) { Write-Warn2 "New endpoints not verified OK - skip removing old (keep fallback)" } else { Write-Step "Remove old bindings (huypham.vn)" foreach ($s in $siteMap) { $site = $s.Site $old = $s.Old $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" } $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] Old bindings (*.huypham.vn) still active. Run with -RemoveOld after verify." -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 new domains:" $statusApi = if ($apiLive) { "[OK]" } else { "[FAIL]" } $statusReady = if ($apiReady) { "[OK]" } else { "[FAIL]" } $statusAdmin = if ($adminOk) { "[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 "Next:" Write-Host " 1. Trigger CI/CD redeploy (push empty commit)" Write-Host " - BE rebuild with new CORS" Write-Host " - FE rebuild with VITE_API_BASE_URL=https://$NewApi" Write-Host " 2. Test login via browser" Write-Host " 3. After 1-2 days verify stable: .\migrate-domains.ps1 -RemoveOld -SkipCert" Write-Host "" if (-not $allOk) { Write-Warn2 "Some endpoints failed - check Event Log and IIS log before proceed." exit 1 }