Files
admin/scripts/iis-migration-to-ruvds/02-source-transfer.ps1
vitya 7693400239 tasks(iis-migration-to-ruvds): partial cutover live — kupimknigi DNS flipped
Session 2026-05-23 evening → 2026-05-24: full snolla site migration
windows-recovery-host → RUVDS Win Server 2025 Core, partial DNS cutover для
1 prod hostname (kupimknigi.spb.ru), оставшиеся 24 hostnames pending.

What's live on RUVDS:
- snolla IIS site catch-all *:80: + 25 HTTPS SNI bindings (1 per hostname,
  LE certs от 2026-04-23 / valid до 2026-07-22)
- 8.66 GB / 44725 files transferred via scp после ISP-block discovery
- ApplicationPoolIdentity + ACL grant verified
- HTTP/2 auto-negotiated, MSSQL/imgproxy/MinIO connectivity OK from RUVDS

Findings (Decisions log в task file для деталей):
1. Outbound 445 блокирует home ISP (не RUVDS FW) — `windows-server-2025-core-bootstrap.md`
   SMB-section deprecated, SSH/scp = canonical transfer-метод.
2. Home network HTTP-middlebox mangles Host header в outbound external HTTP —
   тест с source даёт garbled response; тест с VDS (третья сеть) даёт корректный.
3. traefik acme.json → IIS PFX recipe: extract base64 cert+key per cert →
   openssl pkcs12 -export → Import-PfxCertificate + AddSslCertificate by
   thumbprint with SslFlags=1 (SNI). Reusable, потенциально новый concept.
4. IIS 10 на Win Server 2025 говорит HTTP/2 by default через TLS.
5. 4 hostnames (maljarka.tandemmebel.ru + 3 rimiz) → 502/404 на RUVDS;
   на source IIS:8089 возвращают 200 default-page. Pre-existing CMS-tenant
   config gap, не migration defect.

Scripts added:
- scripts/iis-migration-to-ruvds/01-ruvds-bootstrap.ps1 (idempotent)
- scripts/iis-migration-to-ruvds/02-source-transfer.ps1 (не использовался —
  SMB не работает; оставлен как reference)
- scripts/iis-migration-to-ruvds/README.md

Cleanup done:
- Plaintext PFX/PEM keys в C:\Users\vitya\iis-backup-pre-ruvds\certs\ удалены
  (PFX import на RUVDS уже сделан, source-of-truth = traefik acme.json).
- `.secrets/` уже удалён ранее в этой session (см. предыдущий commit).

Source state: IIS:8089 + traefik routes ALIVE — rollback ready. Decommission
после 1-week soak с RUVDS как live prod.

Не push'нуто — ждёт user grant per project-discipline Rule 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:01:20 +03:00

172 lines
7.6 KiB
PowerShell

#requires -Version 5.1
#requires -RunAsAdministrator
<#
.SYNOPSIS
Source-side transfer: backup IIS state + robocopy snolla site to RUVDS.
.DESCRIPTION
Run on source (DESKTOP-NSEF0UK / windows-recovery-host) in PowerShell as Administrator.
Steps:
1. Probe RUVDS SMB 445 reachability (fail fast).
2. Pull RUVDS Administrator password from pass-store.
3. Backup IIS state to C:\Users\vitya\iis-backup-pre-ruvds\:
- applicationHost.config
- appcmd dump of all sites (for reference)
- appcmd dump of snolla site config + apppool (for recreate on RUVDS)
4. net-use mount \\80.64.31.36\sites with creds.
5. Copy IIS backup files into the share (so RUVDS can grab them for recreate).
6. robocopy C:\sites\snolla -> \\80.64.31.36\sites\snolla
(/Z resume-on-disconnect, /MT:8 parallel, /R:2 /W:5 retries, /XJ skip junctions)
7. Verify count + size match.
8. Disconnect SMB.
Transcript: C:\Users\vitya\iis-backup-pre-ruvds\transfer-log.txt
#>
[CmdletBinding()]
param(
[string]$RuvdsIp = '80.64.31.36',
[string]$ShareName = 'sites',
[string]$SiteName = 'snolla',
[string]$SourceSite = 'C:\sites\snolla',
[string]$BackupDir = 'C:\Users\vitya\iis-backup-pre-ruvds'
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
if (-not (Test-Path $BackupDir)) { New-Item -ItemType Directory -Path $BackupDir -Force | Out-Null }
Start-Transcript -Path (Join-Path $BackupDir 'transfer-log.txt') -Append -Force | Out-Null
function Step($name) { Write-Host "`n=== $name ===" -ForegroundColor Cyan }
function Ok($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
function Skip($msg) { Write-Host " [SKIP] $msg" -ForegroundColor Yellow }
function Fail($msg) { Write-Host " [FAIL] $msg" -ForegroundColor Red }
$shareUnc = "\\$RuvdsIp\$ShareName"
$mountedShare = $false
try {
Step '1. Probe RUVDS SMB 445'
$smb = Test-NetConnection -ComputerName $RuvdsIp -Port 445 -InformationLevel Quiet -WarningAction SilentlyContinue
if (-not $smb) { throw "RUVDS:445 not reachable. Re-run 01-ruvds-bootstrap.ps1 on RUVDS." }
Ok "RUVDS:445 reachable"
Step '2. Pull RUVDS password from pass-store'
$bash = 'C:\Program Files\Git\bin\bash.exe'
if (-not (Test-Path $bash)) { throw "bash.exe not found at $bash; pass-store unreachable" }
$passOut = & $bash -lc 'pass show ruvds-iis/full-env'
$passLine = $passOut | Where-Object { $_ -match '^RUVDS_IIS_PASS=' } | Select-Object -First 1
if (-not $passLine) { throw "RUVDS_IIS_PASS not found in pass show ruvds-iis/full-env" }
$ruvdsPass = $passLine -replace '^RUVDS_IIS_PASS=',''
Ok "credentials loaded from pass-store"
Step '3. Backup source IIS state'
$appcmd = "$env:SystemRoot\system32\inetsrv\appcmd.exe"
if (-not (Test-Path $appcmd)) { throw "appcmd.exe not found -- IIS Management Tools not installed on source?" }
Copy-Item 'C:\Windows\System32\inetsrv\config\applicationHost.config' (Join-Path $BackupDir 'applicationHost.config') -Force
Ok "applicationHost.config backed up"
& $appcmd list site /config:* /xml | Out-File -FilePath (Join-Path $BackupDir 'all-sites.xml') -Encoding UTF8
Ok "appcmd list site (all) dumped"
& $appcmd list site /name:$SiteName /config:* /xml | Out-File -FilePath (Join-Path $BackupDir 'snolla-site.xml') -Encoding UTF8
Ok "snolla-site.xml dumped"
& $appcmd list apppool /xml | Out-File -FilePath (Join-Path $BackupDir 'all-apppools.xml') -Encoding UTF8
Ok "all-apppools.xml dumped"
Step '4. Mount RUVDS share'
cmd.exe /c "net use $shareUnc /user:Administrator $ruvdsPass" 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
cmd.exe /c "net use $shareUnc /delete /y" 2>&1 | Out-Null
cmd.exe /c "net use $shareUnc /user:Administrator $ruvdsPass" 2>&1 | Tee-Object -Variable mountOut | Out-Null
if ($LASTEXITCODE -ne 0) { throw "net use failed: $mountOut" }
}
$mountedShare = $true
Ok "mounted $shareUnc"
Step '5. Copy IIS backup files to share (for RUVDS recreate-script access)'
$remoteBackupDir = Join-Path $shareUnc '_iis-backup'
if (-not (Test-Path $remoteBackupDir)) { New-Item -ItemType Directory -Path $remoteBackupDir -Force | Out-Null }
Copy-Item (Join-Path $BackupDir '*.xml') $remoteBackupDir -Force
Copy-Item (Join-Path $BackupDir 'applicationHost.config') $remoteBackupDir -Force
Ok "IIS backup files copied to $remoteBackupDir"
Step '6. robocopy snolla site'
$srcDir = $SourceSite
$dstDir = Join-Path $shareUnc $SiteName
if (-not (Test-Path $srcDir)) { throw "source $srcDir not found" }
$srcSizeGB = [math]::Round(((Get-ChildItem $srcDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum / 1GB), 2)
Write-Host " Source size: $srcSizeGB GB"
Write-Host " Destination: $dstDir"
Write-Host " This may take 30-90 min on home uplink. Progress goes to log."
Write-Host ""
$robocopyLog = Join-Path $BackupDir 'robocopy-snolla.log'
$robocopyArgs = @(
$srcDir, $dstDir,
'*.*',
'/S', '/E',
'/DCOPY:DA', '/COPY:DAT',
'/Z',
'/MT:8',
'/R:2', '/W:5',
'/XJ',
'/TEE',
"/LOG+:$robocopyLog",
'/NP', '/NDL'
)
$rcStart = Get-Date
& robocopy.exe @robocopyArgs
$rcExit = $LASTEXITCODE
$rcDuration = (Get-Date) - $rcStart
# robocopy exit codes: 0-7 = success/partial, 8+ = error
if ($rcExit -ge 8) {
throw "robocopy failed with exit $rcExit. See $robocopyLog tail."
}
Ok ("robocopy done in {0:hh\:mm\:ss} (exit={1})" -f $rcDuration, $rcExit)
Step '7. Verify count + size'
$srcCount = (Get-ChildItem $srcDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object).Count
$dstCount = (Get-ChildItem $dstDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object).Count
$srcBytes = (Get-ChildItem $srcDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
$dstBytes = (Get-ChildItem $dstDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
Write-Host " Source: $srcCount files, $([math]::Round($srcBytes / 1MB, 0)) MB"
Write-Host " Dest: $dstCount files, $([math]::Round($dstBytes / 1MB, 0)) MB"
if ($srcCount -ne $dstCount) { Fail "FILE COUNT MISMATCH ($srcCount vs $dstCount)"; throw "count mismatch" }
if ($srcBytes -ne $dstBytes) { Fail "SIZE MISMATCH ($srcBytes vs $dstBytes bytes)"; throw "size mismatch" }
Ok "count + size match"
Step '8. Summary'
Write-Host ""
Write-Host " Source site: $srcDir"
Write-Host " RUVDS dest: $dstDir"
Write-Host " Files transferred: $dstCount"
Write-Host " Bytes transferred: $([math]::Round($dstBytes / 1GB, 2)) GB"
Write-Host " Duration: $('{0:hh\:mm\:ss}' -f $rcDuration)"
Write-Host " IIS backup local: $BackupDir"
Write-Host " IIS backup remote: $remoteBackupDir (on RUVDS as C:\sites\_iis-backup\)"
Write-Host ""
Write-Host " Transcript: $(Join-Path $BackupDir 'transfer-log.txt')" -ForegroundColor DarkGray
Write-Host " Robocopy log: $robocopyLog" -ForegroundColor DarkGray
Write-Host ""
Write-Host " Next: run 03-ruvds-recreate-sites.ps1 on RUVDS." -ForegroundColor Cyan
} catch {
Fail $_.Exception.Message
Write-Host $_.ScriptStackTrace -ForegroundColor DarkRed
exit 1
} finally {
if ($mountedShare) {
cmd.exe /c "net use $shareUnc /delete /y" 2>&1 | Out-Null
Write-Host "`n [cleanup] SMB share dismounted" -ForegroundColor DarkGray
}
Stop-Transcript | Out-Null
}