Skip to content

Repository files navigation

demo

CrateDownloadsMSRVLicensePlatformPRs

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

CommandDescription
oopsUndo the most recent restorable snapshot
oops undo [id]Undo a specific snapshot (default: newest)
oops listBrowse snapshot history (TUI, ↑↓/jk to navigate)
oops diff <id>Show files captured in a snapshot (TUI)
oops statusDaemon status, storage usage, lingering state
oops gcTrigger garbage collection
oops pin <id> / oops unpin <id>Exempt / unexempt a snapshot from GC
oops init <bash|zsh|fish>Print shell hook script
oops daemonRun 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

VariableDefaultDescription
OOPS_HOOK_TIMEOUT_MS200Max ms to wait for daemon ack before running the command unprotected
OOPS_ALLOW_PATHS$HOMEColon-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

Star History

RepoStars

Releases

Contributors

Languages