Files
Assistant/deploy/windows/Send-DomovoyHealthNotification.ps1
m 5a07a13ac8
CI / test (push) Successful in 11s
CI / test (pull_request) Successful in 10s
ops: notify on production health transitions
2026-08-16 18:29:26 +03:00

126 lines
5.0 KiB
PowerShell

[CmdletBinding()]
param(
[string]$StatusPath = (Join-Path $env:LOCALAPPDATA 'Domovoy\monitoring\status.json'),
[string]$StatePath = (Join-Path $env:LOCALAPPDATA 'Domovoy\monitoring\notification-state.json'),
[ValidateRange(1, 168)]
[int]$ReminderHours = 6,
[switch]$Force,
[switch]$NoDisplay
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
function Write-AtomicJson {
param([Parameter(Mandatory)]$Value, [Parameter(Mandatory)][string]$Path)
$directory = Split-Path -Parent $Path
if (-not (Test-Path -LiteralPath $directory -PathType Container)) {
New-Item -ItemType Directory -Path $directory -Force | Out-Null
}
$temporary = Join-Path $directory ('.{0}.{1}.tmp' -f (Split-Path -Leaf $Path), [guid]::NewGuid().ToString('N'))
try {
$Value | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $temporary -Encoding utf8NoBOM
Move-Item -LiteralPath $temporary -Destination $Path -Force
}
finally {
if (Test-Path -LiteralPath $temporary) { Remove-Item -LiteralPath $temporary -Force }
}
}
if (-not (Test-Path -LiteralPath $StatusPath -PathType Leaf)) { throw "Health status not found: $StatusPath" }
$status = Get-Content -LiteralPath $StatusPath -Raw | ConvertFrom-Json
if ($status.version -ne 1 -or $status.overall -notin @('healthy', 'degraded')) { throw 'Unsupported health status document' }
$now = [DateTimeOffset]::UtcNow
$previous = $null
if (Test-Path -LiteralPath $StatePath -PathType Leaf) {
$previous = Get-Content -LiteralPath $StatePath -Raw | ConvertFrom-Json
}
$kind = $null
if ($Force) {
$kind = if ($status.overall -eq 'healthy') { 'recovered' } else { 'degraded' }
}
elseif ($status.overall -eq 'degraded') {
if ($null -eq $previous -or $previous.last_status -ne 'degraded') {
$kind = 'degraded'
}
elseif ($null -ne $previous.last_notification_at_utc) {
$lastNotification = if ($previous.last_notification_at_utc -is [DateTime]) {
[DateTimeOffset]$previous.last_notification_at_utc
}
else {
[DateTimeOffset]::Parse([string]$previous.last_notification_at_utc, [Globalization.CultureInfo]::InvariantCulture)
}
if (($now - $lastNotification).TotalHours -ge $ReminderHours) { $kind = 'reminder' }
}
}
elseif ($null -ne $previous -and $previous.last_status -eq 'degraded') {
$kind = 'recovered'
}
$lastNotificationAt = if ($null -ne $previous -and $null -ne $previous.last_notification_at_utc) {
([DateTimeOffset]$previous.last_notification_at_utc).ToString('o')
}
else { $null }
$lastNotificationKind = if ($null -ne $previous) { $previous.last_notification_kind } else { $null }
if ($null -ne $kind) {
$failedNames = @($status.checks | Where-Object { -not $_.ok } | ForEach-Object { [string]$_.name })
switch ($kind) {
'degraded' {
$title = 'Домовой: требуется внимание'
$body = if ($failedNames.Count -gt 0) { 'Сбой: ' + ($failedNames -join ', ') } else { 'Мониторинг обнаружил сбой.' }
$icon = 'Error'
}
'reminder' {
$title = 'Домовой: сбой продолжается'
$body = if ($failedNames.Count -gt 0) { 'Не исправлено: ' + ($failedNames -join ', ') } else { 'Состояние остается ухудшенным.' }
$icon = 'Warning'
}
default {
$title = 'Домовой: работа восстановлена'
$body = 'Все проверки снова проходят успешно.'
$icon = 'Info'
}
}
if (-not $NoDisplay) {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$notification = [System.Windows.Forms.NotifyIcon]::new()
try {
$notification.Icon = switch ($icon) {
'Error' { [System.Drawing.SystemIcons]::Error }
'Warning' { [System.Drawing.SystemIcons]::Warning }
default { [System.Drawing.SystemIcons]::Information }
}
$notification.Visible = $true
$notification.BalloonTipIcon = [System.Enum]::Parse([System.Windows.Forms.ToolTipIcon], $icon)
$notification.BalloonTipTitle = $title
$notification.BalloonTipText = $body
$notification.ShowBalloonTip(8000)
Start-Sleep -Seconds 9
}
finally {
$notification.Dispose()
}
}
$lastNotificationAt = $now.ToString('o')
$lastNotificationKind = $kind
}
$state = [ordered]@{
version = 1
last_status = [string]$status.overall
last_observed_at_utc = $now.ToString('o')
last_notification_at_utc = $lastNotificationAt
last_notification_kind = $lastNotificationKind
}
Write-AtomicJson -Value $state -Path $StatePath
$result = if ($null -eq $kind) { 'suppressed' } elseif ($NoDisplay) { 'simulated' } else { 'sent' }
Write-Output "notification=$result status=$($status.overall) kind=$kind"