Third backup tier (after Dalidou local + T420 off-host): pull-based backup to the user's Windows main computer. - scripts/windows/atocore-backup-pull.ps1: PowerShell script using built-in OpenSSH scp. Fail-open: exits cleanly if Dalidou unreachable (e.g., laptop on the road). Pulls whole snapshots dir (~45MB, bounded by Dalidou's retention policy). - docs/windows-backup-setup.md: Task Scheduler setup (automated + manual). Runs daily 10:00 local, catches up missed days via StartWhenAvailable, retries 2x on failure. Verified: pulled 3 snapshots (45MB) to C:\Users\antoi\Documents\ATOCore_Backups\. Task "AtoCore Backup Pull" registered in Task Scheduler, State: Ready. Three independent backup tiers now: Dalidou local, T420 off-host, user Windows machine. Any two can fail without data loss. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
3.2 KiB
PowerShell
88 lines
3.2 KiB
PowerShell
# atocore-backup-pull.ps1
|
|
#
|
|
# Pull the latest AtoCore backup snapshot from Dalidou to this Windows machine.
|
|
# Designed to be run by Windows Task Scheduler. Fail-open by design -- if
|
|
# Dalidou is unreachable (laptop on the road, etc.), exit cleanly without error.
|
|
#
|
|
# Usage (manual test):
|
|
# powershell.exe -ExecutionPolicy Bypass -File atocore-backup-pull.ps1
|
|
#
|
|
# Scheduled task: see docs/windows-backup-setup.md for Task Scheduler config.
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
|
|
# --- Configuration ---
|
|
$Remote = "papa@dalidou"
|
|
$RemoteSnapshots = "/srv/storage/atocore/backups/snapshots"
|
|
$LocalBackupDir = "$env:USERPROFILE\Documents\ATOCore_Backups"
|
|
$LogDir = "$LocalBackupDir\_logs"
|
|
$ReachabilityTest = 5 # seconds timeout for SSH probe
|
|
|
|
# --- Setup ---
|
|
if (-not (Test-Path $LocalBackupDir)) {
|
|
New-Item -ItemType Directory -Path $LocalBackupDir -Force | Out-Null
|
|
}
|
|
if (-not (Test-Path $LogDir)) {
|
|
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
|
}
|
|
|
|
$Timestamp = Get-Date -Format "yyyy-MM-dd_HHmmss"
|
|
$LogFile = "$LogDir\backup-$Timestamp.log"
|
|
|
|
function Log($msg) {
|
|
$line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $msg
|
|
Write-Host $line
|
|
Add-Content -Path $LogFile -Value $line
|
|
}
|
|
|
|
Log "=== AtoCore backup pull starting ==="
|
|
Log "Remote: $Remote"
|
|
Log "Local target: $LocalBackupDir"
|
|
|
|
# --- Reachability check: fail open if Dalidou is offline ---
|
|
Log "Checking Dalidou reachability..."
|
|
$probe = & ssh -o ConnectTimeout=$ReachabilityTest -o BatchMode=yes `
|
|
-o StrictHostKeyChecking=accept-new `
|
|
$Remote "echo ok" 2>&1
|
|
if ($LASTEXITCODE -ne 0 -or $probe -ne "ok") {
|
|
Log "Dalidou unreachable ($probe) -- fail-open exit"
|
|
exit 0
|
|
}
|
|
Log "Dalidou reachable."
|
|
|
|
# --- Pull the entire snapshots directory ---
|
|
# Dalidou's retention policy (7 daily + 4 weekly + 6 monthly) already caps
|
|
# the snapshot count, so pulling the whole dir is bounded and simple. scp
|
|
# will overwrite local files -- we rely on this to pick up new snapshots.
|
|
Log "Pulling snapshots via scp..."
|
|
$LocalSnapshotsDir = Join-Path $LocalBackupDir "snapshots"
|
|
if (-not (Test-Path $LocalSnapshotsDir)) {
|
|
New-Item -ItemType Directory -Path $LocalSnapshotsDir -Force | Out-Null
|
|
}
|
|
|
|
& scp -o BatchMode=yes -r "${Remote}:${RemoteSnapshots}/*" "$LocalSnapshotsDir\" 2>&1 |
|
|
ForEach-Object { Add-Content -Path $LogFile -Value $_ }
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Log "scp failed with exit $LASTEXITCODE"
|
|
exit 0 # fail-open
|
|
}
|
|
|
|
# --- Stats ---
|
|
$snapshots = Get-ChildItem -Path $LocalSnapshotsDir -Directory |
|
|
Where-Object { $_.Name -match "^\d{8}T\d{6}Z$" } |
|
|
Sort-Object Name -Descending
|
|
|
|
$totalSize = (Get-ChildItem $LocalSnapshotsDir -Recurse -File | Measure-Object -Property Length -Sum).Sum
|
|
$SizeMB = [math]::Round($totalSize / 1MB, 2)
|
|
$latest = if ($snapshots.Count -gt 0) { $snapshots[0].Name } else { "(none)" }
|
|
|
|
Log ("Pulled {0} snapshots successfully (total {1} MB, latest: {2})" -f $snapshots.Count, $SizeMB, $latest)
|
|
Log "=== backup complete ==="
|
|
|
|
# --- Log retention: keep last 30 log files ---
|
|
Get-ChildItem -Path $LogDir -Filter "backup-*.log" |
|
|
Sort-Object Name -Descending |
|
|
Select-Object -Skip 30 |
|
|
ForEach-Object { Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue }
|