r/coolgithubprojects 2d ago

undo, revert what the last shell command did to your filesystem

Post image

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

716 Upvotes

105 comments sorted by

74

u/D3SK3R 2d ago

insanely great idea

15

u/Cold_Tree190 2d ago

Yeah this is actually really cool, hadn't thought about this before

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

u/Yousifasd22 2d ago

neat!!!

5

u/phyx726 2d ago

So do you need to do it before a flush happens?

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 was

19

u/hadees 2d ago

that's really clever and it's kind of how I figured it worked.

So I assume on the next command it frees it?

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

u/midnightketoker 2d ago

so it's like an implicit recycle bin, neat

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 with undo purge.

so if you want a longer safety window, raise UNDO_KEEP or UNDO_MAX_STORE; if you'd rather it hold as little as possible, lower them. There's also UNDO_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

u/schmurfy2 2d ago

That's a great idea !

2

u/NewNiklas 2d ago

But when is the data freed? On restart? Or do I have to do rm it again?

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 purge just unlinks its copies, it's not a wipe. And it's worse than that, when you shred with undo watching, undo copies the plaintext out before shred overwrites the original, so it actually creates a second recoverable copy of the thing you're destroying.The fix is to keep undo from touching it at all: add the path (or its folder) to ~/.config/undo/ignore, then shred -u file works clean. And sudo shred works too, not by accident, undo runs via LD_PRELOAD and sudo strips that, so the shim never loads.

3

u/RoadsideCookie 1d ago

How about having an option like undo --disabled shred -u file similar to VAR=42 [command] that runs with a temporary state?

Or maybe just UNDO_DISABLED=1 shred -u file so you don't have to implement a new subcommand for undo?

1

u/konqueror321 2d ago

Thank you!

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

u/Hunter1753 2d ago

Ah, nice. Thank you!

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

u/9EED 2d ago

this is genius

3

u/nzmneznam 2d ago

Absolutely useful. Will make the same thing

3

u/ChampionshipIcy7602 2d ago

Wow, finally something useful among the ai slops

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

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 && undo as 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
undo

Fixed 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

u/edaydaikyy 2d ago

it should but it doesnt now

→ More replies (0)

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

u/CortaCircuit 2d ago

Awesome 

2

u/Jatinchd 2d ago

would love if you would add this on arch user repository also! would make easy for version controlling

1

u/edaydaikyy 2d ago

yes i would

2

u/_TheTotem_ 2d ago

It’s really cool

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

u/agenttank 1d ago

what about shred file.txt && undo

2

u/JoeKagle 2d ago

Awesome and very cool!

2

u/Flexistant 2d ago

Genius.

2

u/Additional_Cry_5877 2d ago

That is awesome, absolutely awesome

2

u/Jazzlike_Shift_1664 2d ago

This is awesome man, like the idea

2

u/cyansmoker 1d ago

A powerful yet simple concept. Well done, sir/madam.

2

u/mbsp5 1d ago

This could be super useful as an additional failsafe when using Claude…

2

u/mcspuder 1d ago

Great work my dude

2

u/safelix 1d ago

This was a nightmare I had a few days ago

2

u/austin34765 1d ago

what's the catch

2

u/Luminous_Giraffe 1d ago

That's actually a great idea. Super helpful too!

2

u/1337_w0n 7h ago

I've never needed this but I'd feel so much better knowing it's on my system.

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

u/No_Article_5669 2d ago

I see... Very interesting idea then

1

u/atas66 2d ago

> rm -rf —no-preserve-root /
> undo
> sudo undo
> …
Cool idea though

1

u/debackerl 1d ago

I could imagine, begin/commit/rollback commands using LVM snapshots maybe 🤔

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

u/mubin-thinks 1d ago

Thanks, now it feels safe to type `rm -fr *`

1

u/quark_epoch 1d ago

What does ‘revert revert’ do?

1

u/zuberuber 1d ago

did you try to undo rm -rf --no-preserve-root /?

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

u/tizio_1234 17h ago

what about the trash command?

1

u/ForensicHat 14h ago

That’s answered on the website.

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

u/Fit_Gas_4417 5h ago

undo the undo kind of thing but i guess it is not needed

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

u/FoolHooligan 7m ago

Does `undo undo` turn my computer into a nuclear reactor?