# RestoreGuard (CLI prototype) RestoreGuard is a working prototype that takes hardlink-deduplicated snapshot backups -- the same proven mechanism used by the sibling RescueVault tool, where unchanged files are hardlinked instead of copied so repeated snapshots cost almost no extra disk space -- but replaces RescueVault's flat "keep last N" pruning with a real grandfather-father-son (GFS) tiered retention policy: every snapshot is kept for the first 24 hours, then thinned to one per day for a week, then to one per week for a month, and anything older is removed. This is the actual "why pay more for Pro" difference between the two tools: RescueVault's pruning treats every kept snapshot the same regardless of age; RestoreGuard keeps recent history dense and old history sparse, which is how real backup products actually manage retention. The --now flag (see below) is documented as a real feature for auditing what a retention policy would decide at any specific point in time, not just a testing convenience. Disk imaging, boot rescue media creation, and SMART-based recovery scans all need raw- device access that this prototype does not attempt and are on the roadmap -- see ../plan.md for the full product plan. ## Build from source Requires Go 1.24+, no external dependencies. go build -o restoreguard . Cross-compile for another platform: GOOS=windows GOARCH=amd64 go build -o restoreguard.exe . GOOS=darwin GOARCH=arm64 go build -o restoreguard . ## Usage restoreguard backup [--apply] restoreguard prune [--now ] [--apply] restoreguard list [--now ] [--json] restoreguard help ### backup restoreguard backup [--apply] Takes a snapshot of into /snapshots//. Every file in is compared (by sha256) against the same relative path in the most recent prior snapshot: - unchanged file -> hardlinked (os.Link) to the prior snapshot's copy, so its bytes are never duplicated on disk - new/changed file -> copied in full A sidecar manifest, .restoreguard-manifest.json, is written into each snapshot directory recording every file's path, size, mod time, sha256, and whether it was "copied" or "hardlinked" for that snapshot. This is necessary because there is no portable, reliable way to rediscover "this file is a hardlink, and to what" purely from os.FileInfo/os.Stat across Windows, macOS, and Linux after the fact -- the manifest is the source of truth prune and list rely on. Without --apply: dry run. Lists each file as NEW / CHANGED / UNCHANGED and totals, without touching disk. With --apply: performs the backup for real and prints a summary. ### prune restoreguard prune [--now ] [--apply] Applies the tiered (GFS) retention policy to the snapshots found in /snapshots/. Every snapshot's age is computed as (now minus the snapshot's created_at, from its manifest), then classified into exactly one tier by age: age < 24h hourly tier -> KEEP, always (no thinning at all) 24h <= age < 7d daily tier -> KEEP only the newest snapshot per UTC calendar day; older same-day snapshots are removed 7d <= age < 28d weekly tier -> KEEP only the newest snapshot per ISO calendar week; older same-week snapshots are removed age >= 28d (none) -> REMOVE Deliberate simplification: this prototype has no long-term retention tier beyond 28 days (no monthly/yearly bucket). A real product aimed at long-horizon archival would likely add one; this prototype keeps the policy to three tiers for clarity and because it is enough to prove the GFS concept end to end. --now pins what "now" means for every age calculation in that run (e.g. --now 2026-08-10T12:00:00Z). It defaults to the real current wall-clock time when omitted. This is a genuine, useful feature of the tool, not merely a testing hack: it lets an operator ask "what would this retention policy decide about my snapshots as of last Tuesday, or as of a future date" for auditing purposes, in addition to making the tiering logic deterministically testable without waiting real days or weeks between snapshots. Without --apply: dry run. Prints, per snapshot: its id, computed age, which tier it falls into, and the KEEP/REMOVE decision with the exact reason (e.g. "daily tier - superseded by a newer snapshot on 2026-08-07"). Removes nothing. With --apply: actually deletes (os.RemoveAll) the directories for every snapshot classified REMOVE. Correctness note on why this is safe: removing an older snapshot directory never destroys data still needed by a surviving, newer snapshot. A hardlink is just an additional directory entry pointing at the same underlying inode; the operating system only frees a file's data once its link count drops to zero. RestoreGuard only ever deletes whole snapshot directories -- it never modifies files inside a surviving snapshot -- so every surviving snapshot's hardlinked files keep working correctly (same inode, same content, still readable) even after an older snapshot they may once have pointed back to for deduplication is pruned away. This was verified directly: after pruning removed 5 of 6 snapshots that a surviving snapshot's files were originally hardlinked from (in a chain going back to snapshot 1), the survivor's files remained fully readable with correct content, and `stat` showed its link count correctly reflect the removal (dropping from 6 down to 1, the last remaining link). ### list restoreguard list [--now ] [--json] Lists every snapshot in /snapshots/ with its id, created-at timestamp, computed age (relative to --now or real current time), file count, and its current retention tier -- or "REMOVE (next prune)" if a `restoreguard prune` run with the same --now would delete it. Uses the identical classification logic as prune, so `list --now X` and `prune --now X` always agree on every snapshot's fate. --json prints the same data as a JSON array instead of a table. ## Manifest format Each snapshot directory contains .restoreguard-manifest.json: { "snapshot_id": "20260810-020441.039838317", "created_at": "2026-08-10T02:04:41Z", "source": "/path/to/source", "files": [ { "path": "a.txt", "size": 17, "mod_time": "2026-08-10T02:04:33Z", "sha256": "...", "action": "hardlinked" } ] } ## Prebuilt binaries See ../downloads/ for prebuilt binaries (Windows/macOS/Linux) and CHECKSUMS.txt for their SHA-256 hashes. These are unsigned indie builds -- Windows SmartScreen and macOS Gatekeeper will warn on first run, which is expected until a code-signing certificate is in place.