Skip to content

Repository files navigation

SafeKeep

Lightweight, bulletproof snapshot backup for Windows — point-and-click GUI or one-command CLI. Python standard library only (no pip install).

SafeKeep backs up multiple source folders to one or more destinations as dated, point-in-time snapshots. Unchanged files are shared between snapshots via NTFS hardlinks, so keeping ~14 daily snapshots costs roughly the space of one copy. Copying is done with Windows' robocopy for metadata-preserving, network-tolerant transfers. Local/NAS destinations work today; cloud (Google Drive, S3, …) drops in later through a clean destination interface.


Highlights

  • GUI + CLI — a tkinter GUI (folder pickers, live progress bar, streaming per-file log, one-click UAC elevation) wraps the exact same engine as the CLI.
  • Point-in-time snapshots — each run is a timestamped, browsable copy. Delete a file by accident today and yesterday's snapshot still has it.
  • Space-efficient — NTFS hardlink dedup means N snapshots ≈ 1× the data + deltas.
  • Multi-destination, multi-source — back up many folders to many targets, each with its own retention.
  • Permission-aware & NAS-friendly — auto-detects whether a destination supports NTFS ACLs (your NAS doesn't) and copies data only; auto-converts mapped drive letters (U:\) to UNC so backups still work when elevated.
  • Robust by design — atomic finalize with retries, crash recovery (a failed run never deletes good data), per-destination offline tolerance, scoped pruning.
  • No dependencies — runs on the Python standard library alone.

Requirements

  • Windows 10/11
  • Python 3.11+ (uses the built-in tomllib; tested on 3.14)
  • robocopy (ships with all modern Windows)

Quick start (GUI)

  1. Launch it. Double-click SafeKeep.lnk (cleanest) or Start SafeKeep.bat. The first time, the app creates SafeKeep.lnk for you automatically.

    ⚠️ Don't double-click safekeep_gui.pyw directly. On systems with a broken Windows py launcher it silently does nothing. The .lnk/.bat call pythonw.exe directly and always work. (If a launch ever fails, details are written to logs/gui_error.log.)

  2. Add folders under Folders & Settings — click Add… and pick source folders and a destination (your NAS, an external drive, …). Hit Save.
  3. Back up on the Backup tab → ▶ Back up now (toggle Dry run to preview first). Watch the progress bar and live log.
  4. Verify on the Snapshots tab → Verify latest to confirm the snapshot matches your live files.
  5. Schedule on the Schedule tab → Install daily task so it runs automatically every day.

Configuration

SafeKeep reads config.toml (next to the script). A sanitized template ships as config.example.toml — copy it to config.toml, or just configure via the GUI and hit Save. The GUI is the easy path; the file is for power users.

[backup]
retention            = 14        # snapshots to keep per destination
use_hardlinks        = true      # NTFS dedup across snapshots
copy_security        = false     # also copy ACLs/owner (NTFS targets only)
backup_mode          = false     # robocopy /B — read all files (needs admin)
restartable          = false     # robocopy /Z — resilient on flaky links (slower)
ignore_access_errors = true      # permission errors are warnings, not failures
daily_time           = "03:00"   # Task Scheduler trigger (24h HH:MM)

default_exclude = ["**/node_modules", "**/__pycache__", "**/.venv",
                   "**/.git/objects", "**/.DS_Store", "**/Thumbs.db"]

# As many destinations as you like. Use a UNC path (\\server\share\…) for a NAS
# so it still works when the app runs elevated (mapped letters like U:\ are
# hidden from elevated processes — SafeKeep auto-converts them).
[[destinations]]
name = "nas"
type = "local"
path = "\\\\server\\share\\backups\\safekeep"
# retention = 30          # optional per-destination override
# sources = ["proj-a"]    # optional: only these source labels go here

# As many sources as you like. `label` is the folder name inside each snapshot.
[[sources]]
path  = "D:/projects/my-app"
label = "my-app"
# exclude = ["**/build"]  # optional per-source override of default_exclude

Cloud (Google Drive, S3, B2, OneDrive, …) works too via rclone — see Cloud backup.


How it works

  • Snapshot = a real folder tree. Each run creates <dest>/snapshots/<YYYY-MM-DD_HHMMSS>/<label>/…. Snapshots are ordinary directories — browse them or copy files out by hand anytime.
  • Hardlink dedup. Before copying, SafeKeep links any unchanged file (same path, size, mtime) from the previous snapshot into the new one. Identical files exist once on disk, referenced by many snapshots. Pruning a snapshot just removes a reference; data survives while any snapshot points to it.
  • robocopy for the rest. New/changed files are copied with /COPY:DAT (timestamps + attributes) and bounded network retries, output streamed live.
  • Atomic finalize. A snapshot is built as <stamp>.partial, marked complete, then renamed. If the rename fails (a transient SMB "access denied"), it's retried; if a run crashes, the next run recovers the complete partial instead of deleting it.

Permissions & access

Option What it does
Copy security/ACLs Copy NTFS ACLs/owner (/COPY:DATS). Only works to NTFS targets — SafeKeep probes each destination and falls back to data only if it can't (your NAS, exFAT/FAT), so this never breaks a copy.
Backup mode robocopy /B reads files you'd otherwise get access denied on (other users' files, locked files). Needs administrator rights — the GUI auto-prompts UAC and relaunches elevated. If /B still lacks rights, it auto-retries without it.
Restartable robocopy /Z (or /ZB with backup mode) — robust to dropped network links; slower.
Skip files I can't read Remaining permission errors become warnings with a skipped count instead of failing the run. On by default.

Mapped drives & elevation: an elevated (UAC) process does not see your mapped U:\. SafeKeep automatically converts mapped-letter destinations to UNC (\\server\share) on launch so backups work whether elevated or not. For sources, prefer local or UNC paths.


Scheduling

safekeep.py install registers a Windows Task Scheduler task (SafeKeep Backup) that runs daily at daily_time. It's configured to:

  • StartWhenAvailable — catch up after a missed run (PC was off/asleep),
  • WakeToRun — wake a sleeping machine,
  • IgnoreNew — never run two copies at once.

Runs as your user, no password, no admin needed (unless Backup mode is on). Remove with safekeep.py uninstall.


Restoring

python safekeep.py list                                          # find a snapshot
python safekeep.py restore --source my-app --to D:\recovered \
    --snapshot 2026-08-12_030000

Or use Snapshots → Restore… in the GUI. Snapshots are plain folders, so you can also just copy files out manually.


Command-line reference

Command Purpose
run [--dry-run] one backup pass (one snapshot per destination)
list [--dest NAME] list snapshots with file counts/sizes
verify [--dest NAME] [--snapshot S] [--hash] compare a snapshot to live sources
restore --source LABEL --to PATH [--dest N] [--snapshot S] [--yes] copy a snapshot back out
prune [--dest NAME] [--keep N] delete snapshots beyond retention
install / uninstall register/remove the daily scheduled task

Globals: --config PATH, --verbose, --version.


Troubleshooting

  • Double-clicking the .pyw does nothing. Use SafeKeep.lnk or Start SafeKeep.bat (they bypass the broken py launcher). Errors go to logs/gui_error.log.
  • ERROR 1307 … Copying NTFS Security. You had Copy security/ACLs on for a non-NTFS destination. SafeKeep now auto-detects this and copies data only — turn the option off to silence the warning.
  • WinError 5 / Access is denied finalizing a snapshot on a NAS. A transient SMB rename issue — SafeKeep retries the rename and recovers complete partials, so it no longer loses data.
  • Elevated run can't see U:\. Mapped letters aren't linked into elevated sessions. Use a UNC destination path (the app converts it for you).
  • robocopy rc=16 "Backup and Restore Files user rights". Backup mode needs admin — run elevated, or it auto-retries without /B.

Cloud backup (rclone)

📖 New to rclone? See the Cloud setup guide for a full, step-by-step walkthrough (install rclone, configure Google Drive/S3/B2/…, and connect it to SafeKeep).

Cloud destinations are built in via rclone, which speaks Google Drive, S3, B2, OneDrive, Dropbox, Backblaze, and many more. Each run uploads only new/changed files — unchanged files are server-side-copied from the previous snapshot (rclone --copy-dest), so cloud stays cheap.

Setup:

  1. Install rclone and configure a remote once:
    rclone config        # e.g. create a "gdrive" remote
    
  2. Add a destination in config.toml (or the GUI's Folders & Settings shows it once added):
    [[destinations]]
    name     = "gdrive"
    type     = "rclone"
    remote   = "gdrive:backups/safekeep"   # any rclone remote:path
    retention = 90

That's it — run, list, verify (uses rclone check), restore (rclone copy back), and prune (rclone purge) all work for cloud destinations exactly like local ones. SafeKeep detects whether rclone is installed and the remote is reachable on each run, and skips cleanly (with a clear message) if not. Each destination is independent, so you can back up to NAS and cloud in the same run.


Files

safekeep.py          engine + CLI (stdlib only)
safekeep_gui.pyw     tkinter GUI
Start SafeKeep.bat   double-click launcher (calls pythonw.exe directly)
SafeKeep.lnk         shortcut (auto-created on first GUI run; not in repo)
config.example.toml  sanitized config template
config.toml          YOUR config (gitignored)
logs/                rotating run logs + gui_error.log (gitignored)

License

MIT — see LICENSE.

About

A simple Backup tool.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages