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.
- 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.
- Windows 10/11
- Python 3.11+ (uses the built-in
tomllib; tested on 3.14) robocopy(ships with all modern Windows)
- Launch it. Double-click
SafeKeep.lnk(cleanest) orStart SafeKeep.bat. The first time, the app createsSafeKeep.lnkfor you automatically.⚠️ Don't double-clicksafekeep_gui.pywdirectly. On systems with a broken Windowspylauncher it silently does nothing. The.lnk/.batcallpythonw.exedirectly and always work. (If a launch ever fails, details are written tologs/gui_error.log.) - Add folders under Folders & Settings — click Add… and pick source folders and a destination (your NAS, an external drive, …). Hit Save.
- Back up on the Backup tab → ▶ Back up now (toggle Dry run to preview first). Watch the progress bar and live log.
- Verify on the Snapshots tab → Verify latest to confirm the snapshot matches your live files.
- Schedule on the Schedule tab → Install daily task so it runs automatically every day.
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_excludeCloud (Google Drive, S3, B2, OneDrive, …) works too via rclone — see Cloud backup.
- 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.
robocopyfor 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.
| 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.
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.
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 | 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.
- Double-clicking the
.pywdoes nothing. UseSafeKeep.lnkorStart SafeKeep.bat(they bypass the brokenpylauncher). Errors go tologs/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 deniedfinalizing 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.
📖 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:
- Install rclone and configure a remote once:
rclone config # e.g. create a "gdrive" remote - 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.
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)
MIT — see LICENSE.