feat(gateway): include production Telegram user gateway
Version the NestJS TDLib gateway with Docker deployment, migrations, automated tests and platform integration documentation while excluding all account sessions, credentials, runtime databases and backups.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user