Skip to content

Repository files navigation

demo

Crate Downloads MSRV License Platform PRs

Undo daemon for destructive shell commands

Before rm, mv, sed -i, >, git reset --hard, and friends run, their target files get snapshotted — so you can undo the damage.


oops

→ undoes whatever you just broke.


Install

cargo build --release
install -Dm755 target/release/oops ~/.local/bin/oops
install -Dm644 systemd/oopsd.service ~/.config/systemd/user/oopsd.service
systemctl --user daemon-reload
systemctl --user enable --now oopsd
loginctl enable-linger "$USER"
eval "$(oops init bash)"   # or: zsh, fish

Commands

Command Description
oops Undo the most recent restorable snapshot
oops undo [id] Undo a specific snapshot (default: newest)
oops list Browse snapshot history (TUI, ↑↓/jk to navigate)
oops diff <id> Show files captured in a snapshot (TUI)
oops status Daemon status, storage usage, lingering state
oops gc Trigger garbage collection
oops pin <id> / oops unpin <id> Exempt / unexempt a snapshot from GC
oops init <bash|zsh|fish> Print shell hook script
oops daemon Run the background daemon (started via systemd)

Commands that talk to the daemon exit with code 2 and print setup instructions if it's not running.

How it works

A preexec hook (bash DEBUG trap, zsh/fish preexec) intercepts every command before execution and sends it to the daemon over a Unix socket. The daemon classifies the command — if it's destructive, it snapshots every at-risk path before acknowledging the hook. Only then does the shell run the command.

Files are copied (reflink on Btrfs/ZFS, plain copy elsewhere) to ~/.local/share/oops/blobs/<id>/. Metadata lives in SQLite. mtime is recorded for conflict detection during restore.

oops undo replays the snapshot: directories first (with original permissions), then files from blobs. The latest snapshot restores unconditionally. Older snapshots check mtime to avoid clobbering independently modified files. .git/ is never touched.

Configuration

Variable Default Description
OOPS_HOOK_TIMEOUT_MS 200 Max ms to wait for daemon ack before running the command unprotected
OOPS_ALLOW_PATHS $HOME Colon-separated additional directories to permit snapshotting

Data lives in $XDG_DATA_HOME/oops (~/.local/share/oops). Retention is 48 hours or 2 GB, whichever hits first. Pinned snapshots are exempt.

Limitations

Shell-hook capture cannot intercept destructive commands from cron, systemd services, GUI applications, scripts without the hook loaded, or non-interactive shells. fd redirects (1>&2, >&2) are correctly ignored; only truncating redirects (> file) trigger a snapshot. See oops status for the full degraded-warning message.

Project layout

  • oops-core/ — types, command classifier, redirect scanner, SQLite storage, IPC protocol
  • oops/ — single binary (CLI + daemon subcommand)
  • oops-tui/ — ratatui terminal UI for list and diff
  • oops/shell-hooks/ — bash, zsh, fish preexec hook scripts
  • systemd/ — systemd user service unit

License

MIT