ruvds-backup: add email-notify (Yandex SMTP 587 STARTTLS) + fix size calc

Add dual-channel notifications — ntfy (already there) + email via
Send-MailMessage / Yandex SMTP, mirroring VDS msmtp pipeline.

Email: noreply@snolla.com -> vitya.kuznetsov@gmail.com.
Verified delivered (DKIM pass, SPF pass) via run #2 + manual smoke.

Settings sourced from C:\sites\snolla\Web.config <mailSettings> (Yandex
SMTP), cross-checked against pass-store snolla-smtp/full-env. Same
creds, same account as VDS backup msmtp uses.

SMTP port: 587 STARTTLS, NOT 465 implicit-TLS:
.NET SmtpClient / Send-MailMessage support only STARTTLS. Yandex
accepts both; we use 587 for native PS tooling. VDS msmtp uses 465
implicit-TLS — both work, different tools.

Size in email: switched from `rclone size --json | ConvertFrom-Json`
(parses fail when rclone NOTICE stderr leaks into stdout) to local
Get-ChildItem on C:\sites\snolla. Instant, no JSON dance.

setup.ps1: ned params for SMTP creds + OPS_NOTIFY_EMAIL; config.env
template extended.

README.md: notifications section split into ntfy + email subsections,
new SMTP-port + size-calc decisions in Decisions log.

Run #2 (12:30:03) and run #3 (12:33) both produced email delivery
receipts in user inbox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 12:35:25 +03:00
parent 8db4b0d71c
commit bb7dd40436
3 changed files with 73 additions and 15 deletions

View File

@@ -17,7 +17,6 @@ $remoteToday = "$remoteBase/$today"
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
Start-Transcript -Path $logFile -Append -Force | Out-Null
# Load ntfy creds
$cfg = @{}
if (Test-Path "$base\config.env") {
Get-Content "$base\config.env" | Where-Object { $_ -match '^[A-Z_]+=' } | ForEach-Object {
@@ -35,21 +34,32 @@ function Notify-Ntfy($title, $msg, $priority='default', $tags='') {
Invoke-RestMethod -Uri "$($cfg.NTFY_URL)/$topic" -Method POST `
-Headers @{ Authorization=$auth; Title=$title; Priority=$priority; Tags=$tags } `
-Body $msg -ContentType 'text/plain' -ErrorAction SilentlyContinue | Out-Null
} catch {}
} catch { Write-Host " ntfy WARNING: $($_.Exception.Message)" -ForegroundColor Yellow }
}
function Notify-Email($subject, $body) {
try {
if (-not $cfg.SMTP_HOST -or -not $cfg.SMTP_USER -or -not $cfg.SMTP_PASS -or -not $cfg.OPS_NOTIFY_EMAIL) { return }
$secpass = ConvertTo-SecureString $cfg.SMTP_PASS -AsPlainText -Force
$mailCred = New-Object PSCredential($cfg.SMTP_USER, $secpass)
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
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 $subject -Body $body -Encoding UTF8 `
-WarningAction SilentlyContinue
} finally { $ErrorActionPreference = $prevEAP }
} catch { Write-Host " email WARNING: $($_.Exception.Message)" -ForegroundColor Yellow }
}
# Invoke rclone tolerating its NOTICE stderr (PS Stop catches them otherwise).
# Returns array of stdout lines + non-zero $LASTEXITCODE on real failure.
function Invoke-Rclone {
param([Parameter(ValueFromRemainingArguments=$true)][string[]]$Args)
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$out = & $rcloneExe @Args 2>&1
return $out
} finally {
$ErrorActionPreference = $prevEAP
}
try { return & $rcloneExe @Args 2>&1 }
finally { $ErrorActionPreference = $prevEAP }
}
try {
@@ -125,8 +135,30 @@ try {
Remove-Item $certDir -Recurse -Force -ErrorAction SilentlyContinue
$duration = [int](New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
$msg = "RUVDS daily backup $today OK | $duration sec | $exported certs | snolla + IIS configs + ssh state"
$srcBytes = (Get-ChildItem 'C:\sites\snolla' -Recurse -File -EA SilentlyContinue | Measure-Object Length -Sum).Sum
$totalGB = '{0:N2}' -f ($srcBytes / 1GB)
$msg = "RUVDS daily backup $today OK | $duration sec | size $totalGB GB | $exported certs"
Notify-Ntfy "RUVDS backup OK ($today)" $msg 'default' 'green_circle'
$emailBody = @"
RUVDS daily backup completed successfully.
Date: $today
Duration: $duration seconds
Size: $totalGB GB
Certs: $exported PFX
Source: RUVDS ($env:COMPUTERNAME / 80.64.31.36)
Dest: kreknin:/volume1/NetBackup/ruvds-iis/$today/
Components synced:
- sites/snolla
- iis-config (applicationHost.config)
- iis-backup-webconfiguration
- certs (LE PFX exports)
- ssh-config
Log: $logFile
"@
Notify-Email "RUVDS backup $today -- SUCCESS" $emailBody
Write-Host "`n=== DONE in $duration sec ==="
} catch {
@@ -135,8 +167,10 @@ try {
Write-Host $_.ScriptStackTrace
$duration = [int](New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
Notify-Ntfy "RUVDS backup FAILED ($today)" "After $duration sec: $err" 'high' 'red_circle'
Notify-Email "RUVDS backup $today -- FAILED" "After $duration sec: $err`n`nLog: $logFile"
Stop-Transcript | Out-Null
exit 1
} finally {
try { Stop-Transcript | Out-Null } catch {}
}