owpengram-server/scripts/check-local-runtime.ps1
2026-07-27 23:35:20 +03:00

261 lines
8.9 KiB
PowerShell

<#
.SYNOPSIS
Checks the local telesrv runtime state.
.DESCRIPTION
Reports the listening PID/process, git commit, schema version, MTProto port,
Android connection/package status, and recent server log errors. The script is
read-only and is intended to run before/after Android and TDesktop validation.
#>
[CmdletBinding()]
param(
[int]$Port = 2398,
[string]$ServerLogPath,
[string]$AndroidPackage = "org.telegram.messenger.beta",
[string]$DeviceSerial,
[string]$PostgresContainer = "owpengram-postgres",
[string]$Database = "telesrv",
[string]$DbUser = "telesrv",
[int]$RecentLogLines = 1200,
[switch]$SkipAdb
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
if (-not $ServerLogPath) {
$latestLog = Get-ChildItem (Join-Path $RepoRoot "logs") -Filter "telesrv-*.err.log" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($latestLog) {
$ServerLogPath = $latestLog.FullName
} else {
$ServerLogPath = Join-Path $RepoRoot "logs\telesrv.err.log"
}
}
$Failures = New-Object System.Collections.Generic.List[string]
function Write-Step([string]$Message) {
Write-Host ""
Write-Host "== $Message =="
}
function Add-Failure([string]$Message) {
$script:Failures.Add($Message) | Out-Null
Write-Host "[fail] $Message"
}
function Write-Ok([string]$Message) {
Write-Host "[ok] $Message"
}
function Invoke-External {
param(
[string]$FilePath,
[string[]]$Arguments,
[switch]$AllowFailure
)
$oldErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
$output = & $FilePath @Arguments 2>&1
$exitCode = $LASTEXITCODE
} finally {
$ErrorActionPreference = $oldErrorActionPreference
}
$text = ($output | ForEach-Object { $_.ToString() }) -join "`n"
if ($exitCode -ne 0 -and -not $AllowFailure) {
throw "$FilePath $($Arguments -join ' ') failed with exit code ${exitCode}:`n$text"
}
[pscustomobject]@{ ExitCode = $exitCode; Output = $text }
}
function Invoke-PsqlScalar([string]$Sql) {
$result = Invoke-External "docker" @(
"exec", $PostgresContainer,
"psql", "-U", $DbUser, "-d", $Database,
"-v", "ON_ERROR_STOP=1",
"-At", "-c", $Sql
) -AllowFailure
if ($result.ExitCode -ne 0) {
Add-Failure "PostgreSQL query failed: $($result.Output)"
return ""
}
return $result.Output.Trim()
}
function Read-SharedLogLines {
if (-not (Test-Path -LiteralPath $ServerLogPath)) {
return @()
}
$stream = [System.IO.File]::Open($ServerLogPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
$reader = New-Object System.IO.StreamReader($stream)
try {
$lines = New-Object System.Collections.Generic.List[string]
while (-not $reader.EndOfStream) {
$lines.Add($reader.ReadLine()) | Out-Null
}
return $lines
} finally {
$reader.Dispose()
}
} finally {
$stream.Dispose()
}
}
function Get-AdbArgs([string[]]$Arguments) {
if ($DeviceSerial) {
return @("-s", $DeviceSerial) + $Arguments
}
return $Arguments
}
function Invoke-Adb([string[]]$Arguments, [switch]$AllowFailure) {
Invoke-External "adb" (Get-AdbArgs $Arguments) -AllowFailure:$AllowFailure
}
function Get-JsonFieldFromLog {
param([string[]]$Lines, [string]$Field)
for ($i = $Lines.Count - 1; $i -ge 0; $i--) {
if ($Lines[$i] -match ('"' + [regex]::Escape($Field) + '"\s*:\s*"?([^",}]+)"?')) {
return $Matches[1]
}
}
return ""
}
Write-Step "Git"
Push-Location $RepoRoot
try {
$head = (Invoke-External "git" @("rev-parse", "HEAD") -AllowFailure).Output.Trim()
$branch = (Invoke-External "git" @("branch", "--show-current") -AllowFailure).Output.Trim()
$dirty = (Invoke-External "git" @("status", "--porcelain", "--untracked-files=no") -AllowFailure).Output.Trim()
Write-Host "branch=$branch"
Write-Host "head=$head"
if ($dirty) {
Write-Host "tree_state=dirty"
} else {
Write-Host "tree_state=clean"
}
} finally {
Pop-Location
}
Write-Step "Process and Port"
$listeners = @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue)
if ($listeners.Count -eq 0) {
Add-Failure "no process is listening on port $Port"
} else {
foreach ($ownerPid in @($listeners | Select-Object -ExpandProperty OwningProcess -Unique)) {
$proc = Get-Process -Id $ownerPid -ErrorAction SilentlyContinue
if ($proc) {
$path = $null
try { $path = $proc.Path } catch { $path = "" }
Write-Host ("pid={0} name={1} start={2} path={3}" -f $proc.Id, $proc.ProcessName, $proc.StartTime, $path)
} else {
Write-Host "pid=$ownerPid"
}
}
Write-Ok "port $Port is listening"
}
$established = @(Get-NetTCPConnection -LocalPort $Port -State Established -ErrorAction SilentlyContinue)
Write-Host "established_connections=$($established.Count)"
foreach ($conn in $established | Select-Object -First 12) {
Write-Host (" {0}:{1} -> {2}:{3} pid={4}" -f $conn.LocalAddress, $conn.LocalPort, $conn.RemoteAddress, $conn.RemotePort, $conn.OwningProcess)
}
Write-Step "PostgreSQL Schema"
$schema = Invoke-PsqlScalar "SELECT version::text || '|' || dirty::text FROM schema_migrations ORDER BY version DESC LIMIT 1;"
if ($schema) {
$parts = $schema -split "\|", 2
Write-Host "schema_version=$($parts[0])"
Write-Host "schema_dirty=$($parts[1])"
$schemaDirty = ""
if ($parts.Count -gt 1) {
$schemaDirty = $parts[1].Trim().ToLowerInvariant()
}
if ($schemaDirty -in @("f", "false", "0")) {
Write-Ok "schema is clean"
} else {
Add-Failure "schema_migrations is dirty"
}
}
Write-Step "Server Log"
Write-Host "log=$ServerLogPath"
$lines = @(Read-SharedLogLines)
if ($lines.Count -eq 0) {
Add-Failure "server log is missing or empty"
} else {
$readyLines = @($lines | Where-Object { $_ -like "*telesrv 服务就绪*" })
if ($readyLines.Count -gt 0) {
$ready = @($readyLines)[-1]
Write-Host $ready
$runtimeCommit = Get-JsonFieldFromLog @($ready) "git_commit"
$runtimeSchema = Get-JsonFieldFromLog @($ready) "schema_version"
$runtimePID = Get-JsonFieldFromLog @($ready) "pid"
Write-Host "runtime_git_commit=$runtimeCommit"
Write-Host "runtime_schema_version=$runtimeSchema"
Write-Host "runtime_pid=$runtimePID"
if ($head -and $runtimeCommit -and $runtimeCommit -ne $head) {
Add-Failure "runtime git_commit $runtimeCommit != HEAD $head"
} elseif ($runtimeCommit) {
Write-Ok "runtime commit matches HEAD"
}
} else {
Add-Failure "server log has no 'telesrv 服务就绪' line"
}
$recent = @($lines | Select-Object -Last $RecentLogLines)
$bad = @($recent | Where-Object {
$_ -cmatch "INTERNAL_SERVER_ERROR|Unhandled RPC|NOT_IMPLEMENTED|bad_msg|panic|\tERROR\t"
})
if ($bad.Count -eq 0) {
Write-Ok "recent log has no internal/unhandled/bad_msg errors"
} else {
Add-Failure "recent log has $($bad.Count) suspicious error lines"
$bad | Select-Object -Last 40 | ForEach-Object { Write-Host $_ }
}
}
Write-Step "Android"
if ($SkipAdb) {
Write-Host "adb checks skipped"
} else {
$adb = Get-Command adb -ErrorAction SilentlyContinue
if (-not $adb) {
Add-Failure "adb is not available"
} else {
$devices = Invoke-Adb @("devices") -AllowFailure
$deviceLines = @($devices.Output -split "`r?`n" | Where-Object { $_ -match "\tdevice$" })
Write-Host "adb_devices=$($deviceLines.Count)"
if ($deviceLines.Count -lt 1) {
Add-Failure "no adb device connected"
} elseif ($deviceLines.Count -gt 1 -and -not $DeviceSerial) {
Add-Failure "multiple adb devices; pass -DeviceSerial"
} else {
$model = (Invoke-Adb @("shell", "getprop", "ro.product.model") -AllowFailure).Output.Trim()
$sdk = (Invoke-Adb @("shell", "getprop", "ro.build.version.sdk") -AllowFailure).Output.Trim()
$pkg = Invoke-Adb @("shell", "dumpsys", "package", $AndroidPackage) -AllowFailure
Write-Host "device_model=$model sdk=$sdk"
if ($pkg.Output -match "versionName=([^\r\n]+)") {
Write-Ok "Android package $AndroidPackage installed version=$($Matches[1])"
} else {
Add-Failure "Android package $AndroidPackage not found"
}
}
}
}
Write-Host ""
if ($Failures.Count -gt 0) {
Write-Host "Runtime check failed:"
foreach ($failure in $Failures) {
Write-Host " - $failure"
}
exit 1
}
Write-Host "Runtime check passed."