param( [Parameter(Mandatory = $true)] [string]$BackupDirectory ) $ErrorActionPreference = 'Stop' $source = [System.IO.Path]::GetFullPath($BackupDirectory) $manifest = Get-Content (Join-Path $source 'manifest.json') | ConvertFrom-Json foreach ($file in @('postgres.dump', 'redis.rdb', 'tdlib-data.tar.gz')) { $actual = (Get-FileHash -Algorithm SHA256 (Join-Path $source $file)).Hash if ($actual -ne $manifest.sha256.$file) { throw "Checksum mismatch: $file" } } $postgresContainer = 'tgw-restore-postgres' $redisContainer = 'tgw-restore-redis' $filesVolume = 'tgw_restore_files' function Remove-TestResources { $containerNames = @(docker ps -a --format '{{.Names}}') if ($containerNames -contains $postgresContainer) { docker rm -f $postgresContainer | Out-Null } if ($containerNames -contains $redisContainer) { docker rm -f $redisContainer | Out-Null } $volumeNames = @(docker volume ls --format '{{.Name}}') if ($volumeNames -contains $filesVolume) { docker volume rm $filesVolume | Out-Null } } try { Remove-TestResources docker run -d --name $postgresContainer -e POSTGRES_PASSWORD=restore-test ` -e POSTGRES_DB=telegram_gateway_restore postgres:16-alpine | Out-Null $postgresReady = $false for ($attempt = 0; $attempt -lt 30; $attempt++) { docker exec $postgresContainer pg_isready -U postgres -d telegram_gateway_restore 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { $postgresReady = $true break } Start-Sleep -Seconds 1 } if (-not $postgresReady) { throw 'Isolated PostgreSQL restore container did not become ready' } docker cp (Join-Path $source 'postgres.dump') "${postgresContainer}:/tmp/gateway.dump" docker exec $postgresContainer pg_restore -U postgres -d telegram_gateway_restore /tmp/gateway.dump $tables = [int](docker exec $postgresContainer psql -U postgres -d telegram_gateway_restore -Atc ` "select count(*) from information_schema.tables where table_schema='public';") $tenants = [int](docker exec $postgresContainer psql -U postgres -d telegram_gateway_restore -Atc ` 'select count(*) from tenants;') $accounts = [int](docker exec $postgresContainer psql -U postgres -d telegram_gateway_restore -Atc ` 'select count(*) from tg_accounts;') if ($tables -ne [int]$manifest.expected.publicTables -or $tenants -ne [int]$manifest.expected.tenantRows -or $accounts -ne [int]$manifest.expected.accountRows) { throw "PostgreSQL restore count mismatch" } docker create --name $redisContainer redis:7-alpine | Out-Null docker cp (Join-Path $source 'redis.rdb') "${redisContainer}:/data/dump.rdb" docker run --rm -v "${source}:/backup:ro" redis:7-alpine ` redis-check-rdb /backup/redis.rdb | Out-Null if ($LASTEXITCODE -ne 0) { throw 'Redis RDB integrity check failed' } docker start $redisContainer | Out-Null $redisReady = $false for ($attempt = 0; $attempt -lt 20; $attempt++) { $ping = docker exec $redisContainer redis-cli PING 2>$null if ($ping -eq 'PONG') { $redisReady = $true break } Start-Sleep -Milliseconds 500 } if (-not $redisReady) { throw 'Isolated Redis restore container did not become ready' } $redisSize = [int](docker exec $redisContainer redis-cli DBSIZE) # BullMQ keys have TTLs and may legitimately expire between SAVE and this # isolated restore. The RDB checksum/load must succeed and the restored key # count must never exceed the snapshot-time upper bound. if ($redisSize -gt [int]$manifest.expected.redisDbSize) { throw "Redis restore produced more keys than the snapshot" } docker volume create $filesVolume | Out-Null docker run --rm -v "${filesVolume}:/restore" -v "${source}:/backup:ro" ` node:20-bookworm-slim ` tar -xzf /backup/tdlib-data.tar.gz -C /restore $fileCount = [int](docker run --rm -v "${filesVolume}:/restore:ro" ` node:20-bookworm-slim sh -c ` "find /restore/tdlib_data /restore/uploads -type f 2>/dev/null | wc -l") if ($fileCount -ne [int]$manifest.expected.tdlibFileCount) { throw "TDLib/uploads restore file count mismatch" } Write-Output ( "restore_verified tables=$tables tenants=$tenants accounts=$accounts " + "redis_keys=$redisSize redis_snapshot_max=$($manifest.expected.redisDbSize) " + "files=$fileCount checksums=ok" ) } finally { Remove-TestResources }