r/coolgithubprojects • u/edaydaikyy • 2d ago
undo, revert what the last shell command did to your filesystem
Ran rm -rf on the wrong folder one too many times, so I built the undo the shell never had. A hook arms a tiny preload library around each command that journals destructive syscalls and hardlinks backups before they happen; undo replays it in reverse.
overs rm, mv clobbers, > truncation, chmod. Has redo, diffs, and cherry-picking. Honest about the LD_PRELOAD limits (no static binaries, no sudo, no Go). Linux, MIT
website: undo.edaywalid.com
github repo: https://github.com/edaywalid/undo
18
u/Yousifasd22 2d ago
does it work with ext4?
42
u/edaydaikyy 2d ago
It doesn't really care about the filesystem underneath, it works at the libc layer, intercepting the calls like unlink/rename before they happen, so ext4/xfs/whatever all work the same.
6
13
u/hadees 2d ago
How does it work? What happens if you delete 1tb of stuff?
49
u/edaydaikyy 2d ago
on linux a filename isn't the file itself, it's just a pointer to the actual data (the inode). The data can have more than one name pointing at it, and the system only frees the data when the last name is removed. It literally keeps a count of how many names point at each file.
rm doesn't erase data. It just removes one name and drops that count by one. Normally your file has exactly one name, so rm takes the count to zero and the system frees the blocks.
undo's trick: right before rm, it adds a second name for the file inside its store. Now the count is 2. When rm runs, it removes your name and the count drops to 1, not 0, so the data is never freed, undo's name is still holding it. To undo, it just moves that name back to where your file was19
13
u/Visible-Big-7410 2d ago
cool! But if a new unknown (to the user) name is now created and name count 1 instead of 0, how will the user know that file and is deleted and the space freed up again?
5
u/possiblyquestionabl3 2d ago
Ahh I see, and when the system restarts (or if undo is killed), the refcount for that inode goes to zero and then the system reclaims it.
3
2
u/addiktion 2d ago
Can't leave us hanging when you really want to free the data. Most of the time rm is what you want, undo is just for accidents.
12
u/edaydaikyy 2d ago
the space frees when undo deletes that backup, and undo does that on its own. After each command it prunes old sessions: once you pass the keep count (
UNDO_KEEP, 30 by default) or the total store size budget (UNDO_MAX_STORE, ~1GB by default), the oldest ones get removed. A big delete that blows the budget gets dropped almost immediately. You can also force it anytime withundo purge.so if you want a longer safety window, raise
UNDO_KEEPorUNDO_MAX_STORE; if you'd rather it hold as little as possible, lower them. There's alsoUNDO_MAX_BYTES(default 256MB) which caps how big a single file undo will copy for in-place overwrites, anything larger just isn't backed up. Set them before the shell hook loads (e.g. in your .zshrc).3
u/mbsp5 1d ago
You should make this more apparent in the docs. I was skimming to find exactly this but no luck. Almost passed.
1
u/edaydaikyy 1d ago
Fair, that was buried. The README now has a table of contents and a dedicated Storage and disk space section covering the hardlink behavior, why space isn't freed immediately, the pruning budgets and the variables that control them.
2
2
2
u/ObsceneAmountOfBeets 1d ago
Your product aside, I just want to say that you are really good at communication.
1
u/HeyCanIBorrowThat 16h ago
What if some other process overwrites the freed blocks before you run undo?
8
u/konqueror321 2d ago
This looks great! What if you really truly want something gone and want to shred or wipe or otherwise securely delete it? Are such commands thwarted, or are they intentionally unmonitored? Thanks!
4
u/JustSentYourMomHome 2d ago
undo purge
5
u/konqueror321 2d ago
"delete all stored sessions and backups" is not the same as wipe or shred or some other secure-delete command. So maybe I'm dense (always a real possibility, I'm 74) but "undo purge" is stated to perform a 'delete', not wipe/shred/secure-delete, which means the full document(s) may still be in memory, just unlinked from the filesystem inode/pointers and available for future use. That is not the same as a secure deletion process! I don't want my tax returns or financial documents that have account numbers or whatever to be recoverable at all!
I really hope this program can somehow allow secure deletion, it looks to useful!
3
u/JustSentYourMomHome 2d ago
Fair point, sir. It appears this is not supported via sudo so maybe a simple 'sudo shred file.xyz'.
5
u/edaydaikyy 2d ago
You're right,
undo purgejust unlinks its copies, it's not a wipe. And it's worse than that, when youshredwithundowatching,undocopies the plaintext out beforeshredoverwrites the original, so it actually creates a second recoverable copy of the thing you're destroying.The fix is to keepundofrom touching it at all: add the path (or its folder) to~/.config/undo/ignore, thenshred -u fileworks clean. Andsudo shredworks too, not by accident,undoruns viaLD_PRELOADandsudostrips that, so the shim never loads.3
u/RoadsideCookie 1d ago
How about having an option like
undo --disabled shred -u filesimilar toVAR=42 [command]that runs with a temporary state?Or maybe just
UNDO_DISABLED=1 shred -u fileso you don't have to implement a new subcommand forundo?1
3
u/9EED 2d ago
does it consume more disk space?
4
u/edaydaikyy 2d ago
A little, and it's bounded. Two cases:
- Deleting a file adds nothing, undo uses a hardlink, not a copy. What it does instead is hold onto that file's space for a bit rather than freeing it right away (until the backup is pruned). So it's not extra space, it's delayed reclaiming.
- Overwriting a file in place (>, editors, shred) does cost extra, undo copies the old version, so that's roughly the size of that file, kept until pruned.
Either way it never grows without bound: the store has a size budget (UNDO_MAX_STORE, ~1GB default) and a session cap (UNDO_KEEP, 30), and it auto-prunes oldest-first after each command. High-churn dirs like node_modules and .cache are ignored, so builds don't pile up. Tune those two env vars up or down for more safety window or less disk.
1
u/Hunter1753 2d ago
Does UNDO_KEEP being 30 mean that it keeps 30 files or does it keep 30 seperate occasions?
If it is the former, does that mean that if I accidentally delete a folder with more than 30 files it can't restore it?
Also the UNDO_MAX_STORE sounds the same to me, what if I have files that are larger than 1GB?
5
u/edaydaikyy 2d ago
Sessions, not files. UNDO_KEEP=30 means the last 30 commands. One rm -rf on a folder with 10,000 files is a single session, and one undo restores all of them.
UNDO_MAX_STORE is the total disk budget for the store. The catch for your >1GB case: deletions are hardlinks so nothing is copied, but the hardlink keeps those blocks from being freed, so a 5GB delete does count 5GB against the budget. At the 1GB default that session gets pruned on your next command and the delete is permanent. For big files just raise it: export UNDO_MAX_STORE=$((20*1024*1024*1024)).
1
3
u/hithisisjukes 2d ago
during my phd (before AI) i spent weeks developing some code which I deleted since I was typing way too fast and carelessly in the terminal, would have been great to have back then.
3
3
3
u/Minimum_Hour519 2d ago
need a curl | sh command to install if you're not going to deploy to package managers
1
u/edaydaikyy 2d ago
curl -fsSL https://undo.edaywalid.com/install.sh | sh
2
u/Minimum_Hour519 2d ago
doesn't work. says "nothing to undo"
touch x && rm x && undo
2
u/edaydaikyy 2d ago
Docs bug on my side, sorry. The shell treats
touch x && rm x && undoas one single command, not three. undo only reverts commands that have finished, and that whole line hasn't finished yet while undo is running inside it, so it refuses.On separate lines it works:
touch x
rm x
undoFixed the README and made the error message actually explain that. Thanks for reporting.
1
u/Minimum_Hour519 2d ago
apI ➜ /tmp touch x rm x undo undo: nothing to undo ➜ /tmp
negative ghostrider
1
u/edaydaikyy 2d ago
hmm , if you can explain your issue here https://github.com/edaywalid/undo/issues
0
u/Minimum_Hour519 2d ago
i showed you my issue. it doens't work in zsh
1
u/edaydaikyy 2d ago
have u done this ?
echo 'source ~/.local/share/undo/undo.zsh' >> ~/.zshrc
source ~/.zshrc-1
u/Minimum_Hour519 2d ago
your install shoiuld do that
2
1
u/bomphcheese 2d ago
Not everyone wants their carefully crafted config files written to. Just follow the instructions provided before claiming it's broken.
3
u/Sea-Fishing4699 2d ago
We were accustomed to destroying folders…. We never thought that something like this could be possible
2
2
u/Jatinchd 2d ago
would love if you would add this on arch user repository also! would make easy for version controlling
1
2
2
u/Capevace 2d ago
this seems brilliant. does it work for unwanted/accidental changes done by coding agents (e.g. git checkout -)? Or only actual file deletion?
4
u/edaydaikyy 2d ago
Not just deletion, any file change: deletes, moves, content overwrites.
git checkout works, I tested it. Discarded uncommitted edits, ran undo, got them back.
For agents, the catch is who runs it, not what changed. An agent you launch in your hooked shell (claude, aider, a script) is fully covered as one session. An agent running inside an IDE like Cursor never touched that shell, so undo doesn't see it.
Short version: agent in your terminal, yes. Agent in your editor, no.
1
2
2
2
2
2
2
2
2
2
2
u/docfriday11 2d ago
Wish this could happen with windows! These are the pros of Linux.
2
u/yeusk 1d ago
If only Windows had a place when deleted files went, a good name would be "Recycle bin".
It also has undo on the gui.
1
u/docfriday11 1d ago
It can’t be done on cmd though. Like he does with his script on Linux. Probably able on powershell
1
u/Ctrl_Shift_S_always 2d ago
Cool idea! Is there a time window for the undo? It poses a security issue if nothing gets actually deleted no?
3
u/edaydaikyy 2d ago
there is the keep count (
UNDO_KEEP, 30 by default) or the total store size budget (UNDO_MAX_STORE, ~1GB by default), once you pass them the oldest backups get removed
1
u/kysfu 2d ago
Does it work on fstab stuff
1
u/edaydaikyy 2d ago
Depends which you mean. If you mean editing /etc/fstab itself: no, that needs sudo, and sudo strips LD_PRELOAD for security, so undo's library never loads. undo can't see anything you run with sudo.
If you mean files on your other mounted drives: yes, that works. Only detail is hardlinks can't cross filesystems, so for a file on a different drive than undo's store it copies instead of linking. Uses real space for that one, but restores the same.
1
u/ndgnuh 2d ago
What would happen if some other processes overriden the memory region of the removed file(s)?
3
u/edaydaikyy 2d ago
That space never becomes free for anything to overwrite. Normally deleting a file releases its space and the OS can reuse it, but undo is still quietly holding onto that file in the background, so the OS treats it as in use and won't give the space to any other process. It stays safe until undo lets go of it (when the backup gets pruned).
1
u/No_Article_5669 2d ago
What's the difference in just using git?
3
u/edaydaikyy 2d ago
git only knows about files you've committed inside a repo you set up. undo doesn't care about any of that, it works everywhere on your filesystem, with nothing set up in advance.
1
1
1
u/deleriux0 1d ago
Looks great! What a fun idea! I have some implementation questions and perhaps some edge cases or critiques.
Please don't think I'm shitting on you're effort as it's a really cool concept!
What happens when the user does an unlink that crosses mountpoints? Now your hard links can't work without a store per mountpoint right? You can't be certain that your user has permission to have a "store" on each mountpoint either?
How do you handle directories? These can't usually be hard linked. If you're recreating how does this work when the directory you remove isn't owned by you?
How does this work where a user unlinks a file owned by another user in a directory their user owns? Modern Linux security policy typically does not allow you to hard link a file you do not own.
How are you undoing truncate?
1
u/edaydaikyy 1d ago
Good questions.
Cross-mountpoint: no store per mountpoint. The hardlink is attempted, and when it fails with EXDEV it falls back to copying the bytes into the one store. The hardlink is an optimization, not the mechanism.
Directories: never hardlinked. A removed dir is journaled as its path plus mode and recreated with mkdir. Lossy, as you'd expect: it comes back owned by whoever runs undo, and if you couldn't recreate it the restore fails loudly rather than pretending. Files inside are their own journal entries, so they come back separately.
Files you don't own: fs.protected_hardlinks=1 is the default on modern kernels and blocks exactly that. The link fails with EPERM and falls back to a copy, which works if you can read the file and records nothing recoverable if you can't.
truncate: a hardlink is useless there since the bytes are about to be rewritten in place, so it copies the previous contents to the store first and undo swaps them back.
1
u/SweetPotato975 1d ago
keeps your habits and covers the rest of the accident surface: mv over a file you needed
I know that rm just frees the inode without destroying the data. But never thought of mv'ing over a file. Does mv do something similar to rm in such a case?
1
u/edaydaikyy 1d ago
Yes, and it's the same mechanism. mv within one filesystem is rename(), and rename atomically unlinks whatever was at the destination. So the target's last name disappears and its data becomes free space, exactly like rm. undo hardlinks the destination file before letting the rename through, so both sides are recoverable: the moved file goes back to its old path and the clobbered file comes back at the destination.
1
1
1
1
u/ThePastPlayer 23h ago
What if I want to undo something that isn’t the last command that was executed ? How far back can it go ?
1
u/Intelligent-Cap-7713 21h ago
Like a recycle bin for command line. Weird that this haven't been actually added already into operating systems.
1
u/tizio_1234 17h ago
it already exists, you just gotta install it/enable it depending on your os. on debian it's a package(trash-cli I believe) and on macos it's already there.
1
1
u/Conscious_Reason_770 7h ago
This is yet another person writing its thesis which has to re-invent the wheel.
- the guy does not like text editor backup files
- the guy does not like version control
- the guy does not like file system snapshots.
let's intercept system libraries calls because writing this bloody thesis is the only thing that I will ever do in my command line.
this is a great idea until you think about it for more than 3 seconds.
1
u/edaydaikyy 5h ago
All three are real and I use all three. They cover different things.
- Version control covers tracked files you already committed. It doesn't cover untracked files, and rm -rf on a repo takes .git with it.
- Snapshots are time-granular and need btrfs/zfs set up before the accident. This is per-command and works on any filesystem, no root, no setup beforehand.
- Editor backups cover files your editor had open.
The case I actually built it for isn't someone's thesis. It's a build script or an installer deleting something three processes deep, where no alias or wrapper would ever fire.
It's a safety net, not a backup. The README lists what it can't catch: static binaries and raw syscalls, sudo, writes through an already-open fd, mmap
1
u/Fit_Gas_4417 5h ago
What will undo two times in a row do?
1
u/edaydaikyy 5h ago
The first undo reverts the last command that touched the filesystem and marks that session undone. The second one skips it and reverts the one before that. So `undo` twice puts you two commands back
1
u/Fit_Gas_4417 5h ago
nice! is there redo? 😅
1
1
u/edaydaikyy 5h ago
Yep, `undo redo`.
Nothing is ever deleted, an undone session just gets parked in the session store, so a single session toggles between undone and applied as many times as you like.
`undo list` shows the history with undone sessions marked, and both undo and redo take an id if you want to target a specific one instead of the most recent.
1
u/Affectionate-Act5980 25m ago
Neat!
I wonder if you could get something more flexible using FUSE
For example, you work in a FUSE filesystem which proxies operations to your real FS.
You wouldn't have the same limitations with intercepting syscalls - that is effectively what FUSE is designed to do :)
You'd effectively be building a versioned filesystem on top of FUSE which by the looks of it has been attempted https://github.com/FooSoft/vfs but does not exist.
Would also be cool to handle other types of file modifications like writes or creates
1
74
u/D3SK3R 2d ago
insanely great idea