tasks(ruvds-backup-daily-kreknin): close 🟢 — live на 04:30 MSK daily

End-to-end pipeline отработан, run #1 verified 20 sec, 8.8 GB snolla +
IIS configs + 14 certs + sshd state → /volume1/NetBackup/ruvds-iis/<date>/
на kreknin. Retention 7 daily, ntfy vds-backup topic.

Scripts checked into scripts/ruvds-backup-daily-kreknin/:
- setup.ps1 (one-time install: SSH key + rclone + configs + ScheduledTask)
- run.ps1 (live backup logic; Invoke-Rclone wrapper для NOTICE-on-stderr)
- README.md (decisions log, smoke instructions, atomic revert)

Bugs found and fixed during smoke (см. README Decisions log):
1. Backup-WebConfiguration -Force параметра нет → check + Remove first
2. rclone --log-file lock с PS Start-Transcript → drop --log-file
3. rclone NOTICE на stderr + $ErrorActionPreference=Stop → Invoke-Rclone
   wrapper temporarily switches к Continue
4. ssh-keyscan known_hosts не parsится rclone go-sftp → drop pinning,
   rely on key-auth

Закрывает "Backup strategy для RUVDS IIS" Open question в
[iis-migration-to-ruvds].

Open follow-ups (не блокер):
- PFX export pass plaintext в скрипте — TODO move to gpg/DPAPI
- Retention prune (kept 1 today) — verify в day 8
- Phone-side ntfy push — user verifies

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 11:49:44 +03:00
parent df5878cdad
commit 8db4b0d71c
5 changed files with 380 additions and 7 deletions

View File

@@ -0,0 +1,109 @@
#requires -Version 5.1
#requires -RunAsAdministrator
<#
.SYNOPSIS
One-time setup для RUVDS daily backup → kreknin.
Run on RUVDS as Administrator. Idempotent.
.DESCRIPTION
1. Generate ed25519 SSH key (`C:\ProgramData\backup\kreknin-key`).
2. Install rclone v1.74+ to `C:\Program Files\rclone\rclone.exe`.
3. Write rclone.conf (SFTP remote `kreknin` без host-key validation —
see Decisions log).
4. Write config.env с ntfy creds (chmod-equiv via icacls SYSTEM+Administrators).
5. Deploy run.ps1 (companion file в this folder).
6. Register ScheduledTask `RUVDS-Backup-Daily` daily @ 04:30 MSK as SYSTEM.
User action required between steps 1 и 6: add `kreknin-key.pub` to
`/volume1/homes/vitya/.ssh/authorized_keys` on kreknin Synology.
#>
[CmdletBinding()]
param(
[string]$NtfyUser = 'vitya',
[string]$NtfyPass = 'Pryakhin9',
[string]$NtfyUrl = 'https://ntfy.vds.kzntsv.site',
[string]$NtfyTopic = 'vds-backup',
[string]$ScheduleAt = '04:30'
)
$ErrorActionPreference = 'Stop'
$base = 'C:\ProgramData\backup'
New-Item -ItemType Directory -Path $base -Force | Out-Null
# 1. SSH key
$keyPath = "$base\kreknin-key"
if (-not (Test-Path $keyPath)) {
ssh-keygen -t ed25519 -f $keyPath -N '""' -C 'ruvds-backup' -q
& cmd.exe /c "icacls `"$keyPath`" /inheritance:r /grant `"SYSTEM:(F)`" /grant `"BUILTIN\Administrators:(F)`"" | Out-Null
Write-Host "[OK] SSH key generated: $keyPath"
Write-Host "ADD TO KREKNIN authorized_keys:"
Get-Content "$keyPath.pub"
Read-Host "Press Enter when pubkey added to kreknin"
} else {
Write-Host "[SKIP] SSH key exists"
}
# 2. rclone
$rcloneExe = 'C:\Program Files\rclone\rclone.exe'
if (-not (Test-Path $rcloneExe)) {
$ver = (Invoke-RestMethod 'https://downloads.rclone.org/version.txt' -UseBasicParsing).Trim() -replace '^rclone\s+v|^v',''
$url = "https://downloads.rclone.org/v$ver/rclone-v$ver-windows-amd64.zip"
$tmp = "$env:TEMP\rclone.zip"
Invoke-WebRequest $url -OutFile $tmp -UseBasicParsing
if (Test-Path "$env:TEMP\rclone") { Remove-Item "$env:TEMP\rclone" -Recurse -Force }
Expand-Archive $tmp -DestinationPath "$env:TEMP\rclone" -Force
New-Item -ItemType Directory 'C:\Program Files\rclone' -Force | Out-Null
Copy-Item "$env:TEMP\rclone\rclone-*-windows-amd64\rclone.exe" $rcloneExe -Force
Write-Host "[OK] rclone installed: v$ver"
} else {
Write-Host "[SKIP] rclone installed: $(& $rcloneExe --version | Select-Object -First 1)"
}
# 3. rclone.conf — no known_hosts_file (rclone go-sftp lib чувствителен к формату; ssh-key auth = trust enough)
@"
[kreknin]
type = sftp
host = 195.19.90.188
user = vitya
key_file = $keyPath
disable_hashcheck = true
"@ | Set-Content "$base\rclone.conf" -Encoding ASCII -Force
& cmd.exe /c "icacls `"$base\rclone.conf`" /inheritance:r /grant `"SYSTEM:(F)`" /grant `"BUILTIN\Administrators:(F)`"" | Out-Null
Write-Host "[OK] rclone.conf written"
# 4. config.env
@"
NTFY_URL=$NtfyUrl
NTFY_TOPIC=$NtfyTopic
NTFY_USER=$NtfyUser
NTFY_PASS=$NtfyPass
"@ | Set-Content "$base\config.env" -Encoding ASCII -Force
& cmd.exe /c "icacls `"$base\config.env`" /inheritance:r /grant `"SYSTEM:(F)`" /grant `"BUILTIN\Administrators:(F)`"" | Out-Null
Write-Host "[OK] config.env written"
# 5. Deploy run.ps1 (assumed alongside this setup.ps1)
$srcRun = Join-Path $PSScriptRoot 'run.ps1'
if (-not (Test-Path $srcRun)) { throw "run.ps1 not found alongside setup.ps1 ($srcRun)" }
Copy-Item $srcRun "$base\run.ps1" -Force
& cmd.exe /c "icacls `"$base\run.ps1`" /inheritance:r /grant `"SYSTEM:(F)`" /grant `"BUILTIN\Administrators:(F)`"" | Out-Null
Write-Host "[OK] run.ps1 deployed"
# 6. Scheduled Task
$taskName = 'RUVDS-Backup-Daily'
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -ExecutionPolicy Bypass -File $base\run.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At $ScheduleAt
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-StartWhenAvailable -MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Hours 2)
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
Write-Host "[OK] ScheduledTask '$taskName' registered ($ScheduleAt daily, SYSTEM, 2h max)"
Write-Host ""
Write-Host "=== Setup complete. Test with: Start-ScheduledTask -TaskName '$taskName' ==="
Write-Host "Log: $base\logs\<date>.log"