scripts(backup-notifications): unify push + email format across VDS/RUVDS/windows-host
3 host-pipelines (VDS bash, RUVDS+windows-host ps1) had drifted formats: ntfy title `VDS backup OK $D` vs `RUVDS backup OK ($D)`, tags `white_check_mark` vs `green_circle`, email subject `[VDS] backup OK` vs `RUVDS backup -- SUCCESS`. Phone-side фильтрация и desktop reading ломались за счёт inconsistency. Unified to: - ntfy push: title `<HOST> backup OK <date>`, body `<duration_human>, size=<>, snapshots=<>, dest=kreknin:<>`, tags `green_circle` (OK) / `red_circle` (FAILED). - email: subject `[<HOST>] backup <STATUS> <date>` (STATUS=OK|FAILED), body — structured Date/Duration/Size/Snapshots/Source/Dest/Components/Log. Failure body extends with `Tail (last 40 lines)`. Also imports VDS `run.sh` into repo as `scripts/vds-backup-rsync-kreknin/` — closes drift из общего `scripts/<slug>/` pattern (RUVDS+windows-host уже жили там; VDS жил только на /opt/stacks/backup/scripts/). Deploy status: - VDS: deployed via scp + sudo install, sha256=27b09ca272bb, smoke ntfy+email ✓ - RUVDS: deployed via scp + Move-Item, sha256=f3bb57a86af5, smoke ntfy+email ✓ - windows-host: deploy.ps1 + smoke-notify.ps1 готовы в scripts/, **pending elevated PS у user'а** (ACL=SYSTEM+Administrators, не пишется без UAC). Spec + decisions + completed: .tasks/unify-backup-notifications.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
scripts/windows-host-fallback-backup-daily/deploy.ps1
Normal file
27
scripts/windows-host-fallback-backup-daily/deploy.ps1
Normal file
@@ -0,0 +1,27 @@
|
||||
# Deploy local repo run.ps1 -> C:\ProgramData\backup\run.ps1 with .bak rollback.
|
||||
# MUST be run elevated (ProgramData ACL = SYSTEM + Administrators).
|
||||
# Usage: open elevated PowerShell, cd to repo root, then: & .\scripts\windows-host-fallback-backup-daily\deploy.ps1
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoSrc = Join-Path $PSScriptRoot 'run.ps1'
|
||||
$dst = 'C:\ProgramData\backup\run.ps1'
|
||||
$bak = "$dst.bak-pre-unify"
|
||||
|
||||
if (-not (Test-Path $repoSrc)) { throw "source missing: $repoSrc" }
|
||||
if (-not (Test-Path $dst)) { throw "destination missing: $dst (windows-host backup not initialized?)" }
|
||||
|
||||
# Backup current (if no pre-unify backup yet)
|
||||
if (-not (Test-Path $bak)) {
|
||||
Copy-Item $dst $bak -Force
|
||||
Write-Host " backup: $bak"
|
||||
} else {
|
||||
Write-Host " backup already present: $bak"
|
||||
}
|
||||
|
||||
Copy-Item $repoSrc $dst -Force
|
||||
$srcHash = (Get-FileHash $repoSrc -Algorithm SHA256).Hash
|
||||
$dstHash = (Get-FileHash $dst -Algorithm SHA256).Hash
|
||||
if ($srcHash -ne $dstHash) { throw "hash mismatch after copy: src=$srcHash dst=$dstHash" }
|
||||
Write-Host " deployed: $dst"
|
||||
Write-Host " sha256: $($dstHash.Substring(0,12))"
|
||||
Write-Host " mtime: $((Get-Item $dst).LastWriteTime)"
|
||||
@@ -31,6 +31,14 @@ $rcloneCfg = "$base\rclone.conf"
|
||||
$remoteBase = "kreknin:NetBackup/windows-host"
|
||||
$remoteToday = "$remoteBase/$today"
|
||||
|
||||
$HostLabel = 'windows-host'
|
||||
$SourceDisplay = "windows-host ($env:COMPUTERNAME / 94.19.247.14)"
|
||||
$DestDisplay = "kreknin:/volume1/NetBackup/windows-host/$today/"
|
||||
|
||||
function Format-Duration([int]$sec) {
|
||||
'{0}m{1:D2}s' -f ([int]($sec / 60)), ($sec % 60)
|
||||
}
|
||||
|
||||
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
|
||||
Start-Transcript -Path $logFile -Append -Force | Out-Null
|
||||
|
||||
@@ -161,23 +169,29 @@ try {
|
||||
|
||||
Remove-Item $mssqlLocalDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$duration = [int](New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||
$durationSec = [int](New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||
$durationHuman = Format-Duration $durationSec
|
||||
$sitesBytes = (Get-ChildItem 'C:\sites' -Recurse -File -EA SilentlyContinue | Measure-Object Length -Sum).Sum
|
||||
$minioBytes = (Get-ChildItem 'C:\Users\vitya\projects\docker\diskstation\minio\data' -Recurse -File -EA SilentlyContinue | Measure-Object Length -Sum).Sum
|
||||
$totalGB = '{0:N2}' -f (($sitesBytes + $minioBytes) / 1GB)
|
||||
$sizeStr = '{0:N2} GB' -f (($sitesBytes + $minioBytes) / 1GB)
|
||||
$snapshotCount = if ($existing) { @($existing).Count } else { 1 }
|
||||
|
||||
$msg = "windows-host daily backup $today OK | $duration sec | $totalGB GB (sites+minio) | 5 DBs"
|
||||
Notify-Ntfy "windows-host backup OK ($today)" $msg 'default' 'green_circle'
|
||||
# ntfy push (single line)
|
||||
$ntfyBody = "$durationHuman, size=$sizeStr, snapshots=$snapshotCount, dest=$DestDisplay"
|
||||
Notify-Ntfy "$HostLabel backup OK $today" $ntfyBody 'default' 'green_circle'
|
||||
|
||||
# email (structured)
|
||||
$emailBody = @"
|
||||
windows-host (DESKTOP-NSEF0UK) daily backup completed successfully.
|
||||
$HostLabel daily backup completed successfully.
|
||||
|
||||
Date: $today
|
||||
Duration: $duration seconds
|
||||
Size: $totalGB GB (sites+minio data; mssql .bak compressed separately)
|
||||
Source: windows-host (DESKTOP-NSEF0UK / 94.19.247.14)
|
||||
Dest: kreknin:/volume1/NetBackup/windows-host/$today/
|
||||
Date: $today
|
||||
Duration: $durationHuman
|
||||
Size: $sizeStr
|
||||
Snapshots: $snapshotCount
|
||||
Source: $SourceDisplay
|
||||
Dest: $DestDisplay
|
||||
|
||||
Components synced:
|
||||
Components:
|
||||
- mssql (5 DBs: MoreThenCms, StayerCalculator, StayerPrice, stostayer, TireService)
|
||||
- sites (C:\sites\)
|
||||
- minio (data dir)
|
||||
@@ -186,16 +200,35 @@ Components synced:
|
||||
|
||||
Log: $logFile
|
||||
"@
|
||||
Notify-Email "windows-host backup $today -- SUCCESS" $emailBody
|
||||
Write-Host "`n=== DONE in $duration sec ==="
|
||||
Notify-Email "[$HostLabel] backup OK $today" $emailBody
|
||||
Write-Host "`n=== DONE in $durationHuman ==="
|
||||
|
||||
} catch {
|
||||
$err = $_.Exception.Message
|
||||
Write-Host "`n=== FAILED: $err ===" -ForegroundColor Red
|
||||
Write-Host $_.ScriptStackTrace
|
||||
$duration = [int](New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||
Notify-Ntfy "windows-host backup FAILED ($today)" "After $duration sec: $err" 'high' 'red_circle'
|
||||
Notify-Email "windows-host backup $today -- FAILED" "After $duration sec: $err`n`nLog: $logFile"
|
||||
$durationSec = [int](New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||
$durationHuman = Format-Duration $durationSec
|
||||
$tailLog = ''
|
||||
try { $tailLog = (Get-Content $logFile -Tail 40 -EA SilentlyContinue) -join "`n" } catch {}
|
||||
|
||||
# ntfy push (single line)
|
||||
Notify-Ntfy "$HostLabel backup FAILED $today" "After $durationHuman`: $err. See $logFile" 'high' 'red_circle'
|
||||
|
||||
# email (structured)
|
||||
$emailBody = @"
|
||||
$HostLabel daily backup FAILED.
|
||||
|
||||
Date: $today
|
||||
Duration: $durationHuman
|
||||
Error: $err
|
||||
Source: $SourceDisplay
|
||||
Log: $logFile
|
||||
|
||||
Tail (last 40 lines):
|
||||
$tailLog
|
||||
"@
|
||||
Notify-Email "[$HostLabel] backup FAILED $today" $emailBody
|
||||
Stop-Transcript | Out-Null
|
||||
exit 1
|
||||
} finally {
|
||||
|
||||
64
scripts/windows-host-fallback-backup-daily/smoke-notify.ps1
Normal file
64
scripts/windows-host-fallback-backup-daily/smoke-notify.ps1
Normal file
@@ -0,0 +1,64 @@
|
||||
# Smoke-test notify channels with NEW unified format.
|
||||
# Sends one TEST-tagged ntfy push + one TEST-tagged email — no backup run.
|
||||
# MUST be run elevated (config.env ACL = SYSTEM + Administrators).
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$base = 'C:\ProgramData\backup'
|
||||
$cfg = @{}
|
||||
Get-Content "$base\config.env" | Where-Object { $_ -match '^[A-Z_]+=' } | ForEach-Object {
|
||||
$kv = $_ -split '=', 2
|
||||
$cfg[$kv[0]] = $kv[1]
|
||||
}
|
||||
|
||||
$today = Get-Date -Format 'yyyy-MM-dd'
|
||||
$HostLabel = 'windows-host'
|
||||
$SourceDisplay = "windows-host ($env:COMPUTERNAME / 94.19.247.14)"
|
||||
$DestDisplay = "kreknin:/volume1/NetBackup/windows-host/$today/"
|
||||
$durationHuman = '81m08s'
|
||||
$sizeStr = '23.10 GB'
|
||||
$snapshotCount = 1
|
||||
$logFile = "C:\ProgramData\backup\logs\$today.log"
|
||||
|
||||
# ntfy
|
||||
$pair = "$($cfg.NTFY_USER):$($cfg.NTFY_PASS)"
|
||||
$auth = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($pair))
|
||||
$ntfyBody = "$durationHuman, size=$sizeStr, snapshots=$snapshotCount, dest=$DestDisplay"
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$($cfg.NTFY_URL)/vds-backup" -Method POST `
|
||||
-Headers @{ Authorization=$auth; Title="[TEST] $HostLabel backup OK $today"; Priority='default'; Tags='green_circle' } `
|
||||
-Body $ntfyBody -ContentType 'text/plain' -ErrorAction Stop | Out-Null
|
||||
Write-Host 'ntfy_ok'
|
||||
} catch { Write-Host ('ntfy_fail: ' + $_.Exception.Message) }
|
||||
|
||||
# email
|
||||
$emailBody = @"
|
||||
$HostLabel daily backup completed successfully.
|
||||
|
||||
Date: $today
|
||||
Duration: $durationHuman
|
||||
Size: $sizeStr
|
||||
Snapshots: $snapshotCount
|
||||
Source: $SourceDisplay
|
||||
Dest: $DestDisplay
|
||||
|
||||
Components:
|
||||
- mssql (5 DBs: MoreThenCms, StayerCalculator, StayerPrice, stostayer, TireService)
|
||||
- sites (C:\sites\)
|
||||
- minio (data dir)
|
||||
- traefik (config + acme.json)
|
||||
- iis-config (applicationHost.config) + iis-backup-webconfiguration
|
||||
|
||||
Log: $logFile
|
||||
|
||||
[TEST RUN — sent from unify-backup-notifications smoke]
|
||||
"@
|
||||
|
||||
$secpass = ConvertTo-SecureString $cfg.SMTP_PASS -AsPlainText -Force
|
||||
$mailCred = New-Object PSCredential($cfg.SMTP_USER, $secpass)
|
||||
try {
|
||||
Send-MailMessage -SmtpServer $cfg.SMTP_HOST -Port ([int]$cfg.SMTP_PORT) -UseSsl `
|
||||
-Credential $mailCred -From $cfg.SMTP_FROM -To $cfg.OPS_NOTIFY_EMAIL `
|
||||
-Subject "[TEST][$HostLabel] backup OK $today" -Body $emailBody -Encoding UTF8 `
|
||||
-WarningAction SilentlyContinue -ErrorAction Stop
|
||||
Write-Host 'email_ok'
|
||||
} catch { Write-Host ('email_fail: ' + $_.Exception.Message) }
|
||||
Reference in New Issue
Block a user