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:
wmq
2026-08-01 18:38:20 +08:00
parent af49d22ca7
commit 06112602ec
142 changed files with 23107 additions and 7 deletions
@@ -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
}