From df5878cdaddc33625517b49c1b1d119d130c8d28 Mon Sep 17 00:00:00 2001 From: vitya Date: Sun, 24 May 2026 10:11:34 +0300 Subject: [PATCH] =?UTF-8?q?tasks:=20+=20[ruvds-backup-daily-kreknin]=20?= =?UTF-8?q?=E2=9A=AA=20ready=20=E2=80=94=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BB?= =?UTF-8?q?=D0=BB=D0=B5=D0=BB=D1=8C=20=D0=BA=20VDS=20backup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Создаю задачу пока [iis-migration-to-ruvds] в soak window. Закрывает Open-question "Backup strategy для RUVDS IIS" из migration task. Pattern параллель [vds-backup-rsync-kreknin] 🟢 — daily backup → kreknin synology, 7 daily snapshots retention, ntfy на vds-backup topic. Differences vs VDS: - Win Server 2025 Core → rclone single-binary, не rsync (no cygwin/WSL) - Task Scheduler SYSTEM-account, не cron-root - 04:30 MSK (за 30 мин до VDS backup чтобы не пересекать uplink) - Backup scope: C:\sites\snolla + applicationHost.config + IIS native Backup-WebConfiguration + cert store PFX export + sshd config Decisions log в task file: tool=rclone (rejected restic encrypted-dedupe как overkill для transitional setup, robocopy-over-SMB fragile); SYSTEM-principal mirrors VDS root-cron pattern; PFX export pass=temp 'pfximport' (TODO: pass-equivalent на Windows когда найдём). Open questions для решения user'а: when to start (recommend сейчас, не ждать full cutover), kreknin SSH ACL add pubkey to authorized_keys (требует user action). Co-Authored-By: Claude Opus 4.7 (1M context) --- .tasks/STATUS.md | 12 ++ .tasks/ruvds-backup-daily-kreknin.md | 214 +++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 .tasks/ruvds-backup-daily-kreknin.md diff --git a/.tasks/STATUS.md b/.tasks/STATUS.md index 05cb548..ea93af0 100644 --- a/.tasks/STATUS.md +++ b/.tasks/STATUS.md @@ -698,6 +698,18 @@ Copy-Item C:\sites\snolla\Web.config.bak-pre-vds-cutover-20260522 C:\sites\snoll --- +## ⚪ [ruvds-backup-daily-kreknin] — ежедневный backup RUVDS → kreknin (rclone SFTP), параллель `[vds-backup-rsync-kreknin]` + +**Status:** ready +**Where I stopped:** (not started — создан 2026-05-24 параллельно soak'у `[iis-migration-to-ruvds]`) +**Next action:** см. [ruvds-backup-daily-kreknin.md](ruvds-backup-daily-kreknin.md) полный sketch. Order: (1) install rclone single-binary на RUVDS, (2) generate SSH key для kreknin auth + user добавляет pubkey в kreknin `/volume1/homes/vitya/.ssh/authorized_keys`, (3) написать `C:\ProgramData\backup\run.ps1` (snolla sync + IIS config snapshot + cert export + ntfy notification + retention prune 7 daily), (4) Register-ScheduledTask `RUVDS-Backup-Daily` SYSTEM-account, 04:30 MSK (за 30 мин до VDS backup 05:00), (5) manual smoke run + verify kreknin path + ntfy push в vds-backup topic. + +Pattern параллель `[vds-backup-rsync-kreknin]` 🟢 (VDS Ubuntu + rsync + cron). Differences: Win Server Core → rclone (single .exe) + Task Scheduler + SYSTEM principal. Trigger: пока soak window открыт — даже до full cutover backup нужен для rollback-evidence. **Закрывает Open question "Backup strategy для RUVDS IIS"** из `[iis-migration-to-ruvds]`. +**Branch:** master + + +--- + ## 🟡 [iis-migration-to-ruvds] — RUVDS IIS live для 1 prod hostname (`kupimknigi.spb.ru`), 24 в очереди; источник held для rollback **Status:** in_progress (DNS partial cutover — soak window 2026-05-24) diff --git a/.tasks/ruvds-backup-daily-kreknin.md b/.tasks/ruvds-backup-daily-kreknin.md new file mode 100644 index 0000000..039639a --- /dev/null +++ b/.tasks/ruvds-backup-daily-kreknin.md @@ -0,0 +1,214 @@ +# ruvds-backup-daily-kreknin + +## Goal + +Ежедневный backup с RUVDS (`80.64.31.36` / Win Server 2025 Core) → [[../entities/kreknin-synology]] (`195.19.90.188 / kreknin.site`) в **04:30 MSK** (за 30 мин до `[vds-backup-rsync-kreknin]` 05:00, чтобы не пересекать uplink). После каждого прохода — ntfy push на topic `vds-backup` (тот же что VDS backup пушит — общий agg-канал). + +Параллель `[vds-backup-rsync-kreknin]` 🟢 (VDS Ubuntu → kreknin via rsync `--link-dest`), но source = Windows Server Core, поэтому: +- Cron нет → Windows Task Scheduler +- rsync нативного нет → **rclone** sync over SFTP (см. Decisions ниже) +- root-cron-as-uid недоступен → SYSTEM scheduled task + +## Scope (what to backup) + +Минимум для disaster-recovery RUVDS state: + +| Path on RUVDS | Reason | Approx size | +|---|---|---| +| `C:\sites\snolla\` | CMS site content + Web.config + media | 8.66 GB (grows slowly) | +| `C:\Windows\System32\inetsrv\config\applicationHost.config` | IIS site/binding/apppool config | <1 MB | +| Backup-WebConfiguration export | IIS native config snapshot (`Backup-WebConfiguration -Name ...`) → `%SystemRoot%\System32\inetsrv\backup\` | <5 MB | +| `C:\ProgramData\ssh\` (sshd_config + administrators_authorized_keys) | SSH access state | <50 KB | +| LE certs export (`Get-ChildItem Cert:\LocalMachine\My` → Export-PfxCertificate с временным пасс'ом + git-tracked) | для restore без re-extract из traefik acme.json | <1 MB | + +**Не бэкапить:** +- `C:\Windows\` system files — RUVDS rebuild = fresh install (~5 мин через RUVDS panel). +- `C:\Program Files\` — installed software re-installable from scripts (bootstrap.ps1). +- IIS logs (`C:\inetpub\logs\LogFiles\`) — диагностические, не recovery-critical. Optional. + +## Open questions + +- [ ] **Tool:** rclone sync over SFTP (см. Decisions ниже — recommended). Alternative — restic (encrypted dedupe) — defer как для VDS; RUVDS↔kreknin link через public internet, но обе стороны ours, single-hop. Add encryption если threat model changes. +- [ ] **Retention:** 7 daily snapshots — следуем VDS-pattern. Per-day dirs (`/volume1/NetBackup/ruvds-iis/2026-05-25/`) — без hardlink dedup, 8.66 GB × 7 = ~60 GB на kreknin (5.7T free, easy). +- [ ] **Auth — RUVDS→kreknin SSH key:** генерировать новый ed25519 на RUVDS (`ssh-keygen` ships with OpenSSH client/server caps), pubkey добавить в `/volume1/homes/vitya/.ssh/authorized_keys` на kreknin через user. Private key хранится в `C:\ProgramData\backup\ssh_key` (ACL: SYSTEM + Administrators only). +- [ ] **Notification:** ntfy `vds-backup` topic — reuse (общий backup-канал, удобно для phone alerts). Title включает source-host чтобы отличать VDS-backup от RUVDS-backup. +- [ ] **Encryption-at-rest на kreknin:** none (как для VDS). RUVDS data = sites content (public web pages anyway) + config files. Не sensitive enough для encryption overhead в transitional setup. + +## Implementation sketch + +### 1. rclone install + config + +```powershell +# Install rclone (one-time) +$ver = (Invoke-RestMethod 'https://downloads.rclone.org/version.txt').Trim('v').Trim() +$url = "https://downloads.rclone.org/v$ver/rclone-v$ver-windows-amd64.zip" +$tmp = "$env:TEMP\rclone.zip" +Invoke-WebRequest $url -OutFile $tmp -UseBasicParsing +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" 'C:\Program Files\rclone\' -Force +[Environment]::SetEnvironmentVariable('Path', "$env:Path;C:\Program Files\rclone", 'Machine') + +# Generate SSH key для kreknin auth (one-time) +ssh-keygen -t ed25519 -f C:\ProgramData\backup\kreknin-key -N '""' -C 'ruvds-backup' +icacls C:\ProgramData\backup\kreknin-key /inheritance:r /grant 'SYSTEM:(F)' /grant 'Administrators:(F)' +# Затем user добавляет .pub в kreknin /volume1/homes/vitya/.ssh/authorized_keys + +# rclone config — SFTP remote +@' +[kreknin] +type = sftp +host = 195.19.90.188 +user = vitya +key_file = C:\ProgramData\backup\kreknin-key +disable_hashcheck = true +'@ | Set-Content C:\ProgramData\backup\rclone.conf -Encoding ASCII +``` + +### 2. Backup script `C:\ProgramData\backup\run.ps1` + +```powershell +$ErrorActionPreference = 'Stop' +$today = Get-Date -Format 'yyyy-MM-dd' +$start = Get-Date +$logFile = "C:\ProgramData\backup\logs\$today.log" +New-Item -ItemType Directory (Split-Path $logFile) -Force | Out-Null +Start-Transcript -Path $logFile -Append -Force | Out-Null + +try { + # 1. IIS config snapshot + Backup-WebConfiguration -Name "daily-$today" -Force | Out-Null + $iisBackupDir = "$env:SystemRoot\System32\inetsrv\backup\daily-$today" + + # 2. Export cert store (LocalMachine\My) to PFX with timestamp passphrase + $pfxPass = ConvertTo-SecureString 'pfximport' -AsPlainText -Force # TODO move to pass-equivalent + $certDir = "C:\ProgramData\backup\certs-$today" + New-Item -ItemType Directory $certDir -Force | Out-Null + Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.HasPrivateKey } | ForEach-Object { + $name = ($_.Subject -replace '[^A-Za-z0-9.-]','_').Substring(0, [math]::Min(40, $_.Subject.Length)) + try { + Export-PfxCertificate -Cert $_ -FilePath "$certDir\$name.pfx" -Password $pfxPass -ErrorAction SilentlyContinue | Out-Null + } catch {} + } + + # 3. rclone sync — push to dated dir on kreknin + & 'C:\Program Files\rclone\rclone.exe' sync ` + --config C:\ProgramData\backup\rclone.conf ` + --log-file $logFile --log-level INFO ` + --transfers 4 --checkers 8 ` + C:\sites\snolla ` + "kreknin:/volume1/NetBackup/ruvds-iis/$today/sites/snolla/" + if ($LASTEXITCODE -ne 0) { throw "rclone snolla failed" } + + & 'C:\Program Files\rclone\rclone.exe' copy ` + --config C:\ProgramData\backup\rclone.conf ` + C:\Windows\System32\inetsrv\config\applicationHost.config ` + "kreknin:/volume1/NetBackup/ruvds-iis/$today/iis-config/" + + & 'C:\Program Files\rclone\rclone.exe' sync ` + --config C:\ProgramData\backup\rclone.conf ` + $iisBackupDir ` + "kreknin:/volume1/NetBackup/ruvds-iis/$today/iis-backup-$today/" + + & 'C:\Program Files\rclone\rclone.exe' sync ` + --config C:\ProgramData\backup\rclone.conf ` + $certDir ` + "kreknin:/volume1/NetBackup/ruvds-iis/$today/certs/" + + & 'C:\Program Files\rclone\rclone.exe' sync ` + --config C:\ProgramData\backup\rclone.conf ` + C:\ProgramData\ssh ` + "kreknin:/volume1/NetBackup/ruvds-iis/$today/ssh-config/" + + # 4. Retention prune — keep last 7 daily snapshots + & 'C:\Program Files\rclone\rclone.exe' lsd ` + --config C:\ProgramData\backup\rclone.conf ` + 'kreknin:/volume1/NetBackup/ruvds-iis/' | + Where-Object { $_ -match '\d{4}-\d{2}-\d{2}' } | + ForEach-Object { ($_ -split '\s+')[-1] } | + Sort-Object | Select-Object -SkipLast 7 | + ForEach-Object { + & 'C:\Program Files\rclone\rclone.exe' purge ` + --config C:\ProgramData\backup\rclone.conf ` + "kreknin:/volume1/NetBackup/ruvds-iis/$_" + } + + # 5. Cleanup local temp + Remove-Item $certDir -Recurse -Force + Remove-Item $iisBackupDir -Recurse -Force -ErrorAction SilentlyContinue + + # 6. Notification — ntfy success + $duration = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds + $msg = "RUVDS backup $today OK | $([math]::Round($duration,0))s" + $ntfyToken = (Get-Content C:\ProgramData\backup\ntfy-token.txt) + Invoke-RestMethod -Uri 'https://ntfy.vds.kzntsv.site/vds-backup' ` + -Method POST ` + -Headers @{ Authorization = "Bearer $ntfyToken"; Title = "RUVDS backup success"; Priority = 'default'; Tags = 'green_circle' } ` + -Body $msg + +} catch { + # Notification — ntfy failure + $msg = "RUVDS backup $today FAILED: $($_.Exception.Message)" + $ntfyToken = (Get-Content C:\ProgramData\backup\ntfy-token.txt -ErrorAction SilentlyContinue) + if ($ntfyToken) { + Invoke-RestMethod -Uri 'https://ntfy.vds.kzntsv.site/vds-backup' ` + -Method POST ` + -Headers @{ Authorization = "Bearer $ntfyToken"; Title = "RUVDS backup FAILED"; Priority = 'high'; Tags = 'red_circle' } ` + -Body $msg + } + throw +} finally { + Stop-Transcript | Out-Null +} +``` + +### 3. Schedule via Windows Task Scheduler + +```powershell +$action = New-ScheduledTaskAction -Execute 'powershell.exe' ` + -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\ProgramData\backup\run.ps1' +$trigger = New-ScheduledTaskTrigger -Daily -At '04:30' +$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew +Register-ScheduledTask -TaskName 'RUVDS-Backup-Daily' ` + -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force +``` + +## Decisions log + +- **2026-05-24 (task creation):** Tool = **rclone**, not robocopy-over-SMB and not restic. Reasoning: + - rsync на Win Server Core отсутствует (no cygwin/WSL on Core). Установка cygwin/MSYS — лишний пакет, security surface. + - robocopy + SMB-mount kreknin требует Synology SMB включён + Windows credentials cached + UNC reliability over WAN — fragile. + - rclone — single .exe (~50 MB), SFTP backend native, reliable, well-supported on Server Core, log-friendly. + - restic — encrypted+dedupe — overkill пока link trusted; defer like VDS. + +- **2026-05-24:** **Run as SYSTEM**, не пользовательский account. SYSTEM имеет full доступ к `C:\sites\` + `C:\Windows\System32\inetsrv\` + cert store, не требует stored-password в Task Scheduler. Pattern matches `[vds-backup-rsync-kreknin]` root-cron decision. + +- **2026-05-24:** **PFX export `pfximport` password** — temporary, will move to pass-equivalent on RUVDS (или derived из машинной creds). Open question: how to handle pass-on-Windows-Core analog. Defer пока migration не cutover'нется полностью. + +## Open questions (additional) + +- [ ] **Когда стартовать:** сейчас (parallel с soak window `[iis-migration-to-ruvds]`) или после full cutover? Recommendation: **сейчас** — даже до full cutover, backup snolla content на RUVDS даёт rollback-evidence если RUVDS state corrupt'нется. Lateral risk minimal — backup идёт через ssh-key auth, кroil-кroil traffic. +- [ ] **kreknin SSH ACL on `/volume1/homes/vitya/`** — текущий ACL разрешает `vitya` user. RUVDS пушит как `vitya@kreknin` через SFTP — нужна добавка pubkey в authorized_keys. Без user action не сработает. +- [ ] **Backup `C:\sites\snolla` size growth** — CMS file uploads через админку могут вырасти со временем. Monitor disk usage на kreknin через quarterly check. + +## Completed steps + +(none yet) + +## Notes + +- **Триггер:** task создан 2026-05-24, можно стартовать paralleltno с soak'ом RUVDS. Не блокировать full DNS swap (если backup pipeline ломается — rollback на source IIS:8089 всегда возможен). +- **Integration:** ntfy topic `vds-backup` — общий с VDS backup. Title differentiator чтобы phone-side фильтровать. +- **Атомарный revert (откат всей задачи):** + ```powershell + # На RUVDS: + Unregister-ScheduledTask -TaskName 'RUVDS-Backup-Daily' -Confirm:$false + Remove-Item C:\ProgramData\backup -Recurse -Force + # На kreknin: (через SSH) + ssh vitya@195.19.90.188 'rm -rf /volume1/NetBackup/ruvds-iis' + ``` +- **Relation to [iis-migration-to-ruvds]:** этот backup закрывает "Backup strategy для RUVDS IIS" open-question из migration task. Когда migration переходит в 🟢, эту задачу нужно явно отметить как dependency-closer. +- **Future evolution:** когда snolla-on-node будет ready и IIS станет obsolete (long-term goal per migration task notes) — этот backup можно retire вместе с RUVDS. + +