decom.sh, or the receipt from a machine with no disk left
decom.sh wipes a server's disks and proves every byte reads zero. The proof is a receipt, and after an in-place wipe the receipt lives only in RAM. It can now be fetched over HTTP from the machine, or sent somewhere before the machine goes dark.
decom.sh takes a Linux server out of service. It lists what lived there, writes a checklist of what to rotate, and wipes the disks. The wipe ends with a receipt: every step, its outcome, and the line that matters most, how many bytes were read back and whether all of them were zero.
The receipt is the point of the whole exercise. Without it, “I wiped the server” is a sentence. With it, it’s a record.
Here is the problem. The best place to run the wipe is the provider’s rescue system, where the disks being wiped aren’t the ones the machine is running from. Not every provider has one. When there’s no rescue system, decom.sh wipes the running root disk in place. It copies itself and every tool it needs into RAM first, so the wipe keeps going after the disk is gone. The receipt lands in RAM too.
And then you have to get it out.
🔺 1. Why SSH isn’t enough
You’d think you could just log in and copy it. The session you started the wipe from may still be alive, since that process was already in memory. A new login is a different story. For each new connection, OpenSSH’s sshd runs a fresh program from disk, and after an in-place wipe the disk is zeros.
In the lab, I tried a new login right after an in-place wipe. The client offered its key, and the server hung up: Connection closed by 127.0.0.1 port 24622. A web request to the same machine a moment later still got the receipt, which is the rest of this post.
So the receipt was on the console, on the session that happened to stay open, and nowhere else. If the session dropped, the console of a cloud server is a web page you have to remember to scroll back through.
🔺
2. Fetching it: --serve
--serve ADDR:PORT runs a tiny web server on the machine for the receipt. It’s busybox’s httpd, the same static busybox the wipe already copies into RAM, so it keeps running when the disk underneath it is gone.
- It serves four files from RAM:
RECEIPT.txt, its SHA-256, the verdict, and the live log. The log means you can watch the wipe’s progress from a browser while it runs. - The files sit under a random token: in the lab,
http://10.0.2.15:8099/followed by 32 random hex characters. Asking for/, for the token directory, or for a wrong token gets nothing. A scanner hitting the port learns that a port is open. - The full URL is printed before the confirmation prompt, so you have it even if every connection drops afterward. It’s printed again at the end, with the receipt’s SHA-256.
- It stays up for 24 hours by default (
--serve-forchanges that), or until the machine powers off.
It’s plain HTTP. busybox’s server doesn’t do TLS. The receipt holds the host name, the disks’ serial numbers and the paths of the files it checked. It holds no secrets, but I’d still bind it to a private address, a VPN or a mesh, not the public one. The SHA-256 printed on the console lets you check that the copy you fetched is the copy the machine wrote.
decom.sh doesn’t touch the firewall. If the machine’s firewall might block the port, the warning and the receipt both say so, and opening it is your call.
🔺
3. Sending it: --send-receipt
The other direction is simpler to use: --send-receipt URL uploads the receipt once, when the wipe ends, to an address you choose. Anything that accepts a POST will do, such as an ntfy topic or a small upload endpoint of your own. I tested it with a receiver of my own; more on that below.
Getting a file off a machine whose disk is zeros takes a few things lined up in advance:
curlis copied into RAM with its libraries, along with the certificate bundle it needs to check HTTPS.- The destination’s host name is looked up before the wipe starts, while the machine can still read
/etc/resolv.conf, and the answer is pinned for the upload. --send-receipt-headeradds a header such as an access token. Its value never appears in the log, the receipt or the console. Only the header’s name does.- It makes up to four tries, with timeouts and a growing pause between them. If all four fail, the receipt says so, and the wipe’s verdict doesn’t change. A failed upload doesn’t make the disk any less zeroed.
One detail took a decision. The receipt can’t contain the result of its own upload, because the upload has to send the finished receipt. So it sends the receipt as written, then adds one line after the receipt’s closing line in the copy that stays on the machine:
send-receipt ok HTTP 200 from POST http://10.0.2.2:24690/receipts/S9 (query not shown)
(try 1 of 4; headers: Authorization); the body was the receipt above this line
The receipt’s own sent to: line says that’s where to look.
🔺 4. How it was tested
Every scenario runs in a throwaway virtual machine on my workstation. After each one, the workstation checks the VM’s disk image itself, without trusting anything the script said.
- An in-place wipe with
--serve. While the wipe ran, the workstation fetched the live log 15 times. Afterward it fetched the receipt, and the receipt’s SHA-256 matched both the one printed on the console and the one served next to it. Asking for the bare address or the token directory got an empty page, and a wrong token got a 404. A new SSH login failed, and an HTTP fetch right after it worked. Read from the workstation, the disk image was all zeros. --send-receiptto a small receiver on the workstation, with anAuthorizationheader holding a random test value. The header arrived, and the body was the receipt. The test value appeared nowhere on the virtual machine: not on the console, not in the log, not in the receipt.- The same, with the receiver switched off. The receipt recorded that the upload failed after four tries. The verdict was still PASS.
- Bad options. An address that isn’t on the machine, port 0, port 99999, a name that doesn’t resolve,
ftp://andfile://URLs, and a URL with a password in it were each refused before the confirmation prompt, and the disk was untouched.
Not tested yet: an upload over HTTPS (the lab receiver speaks plain HTTP), the fallback for machines without systemd, and a firewall that actually blocks the port.
decom.sh is one bash script, MIT licensed, at git.itys.net/mjh/decom. The manual is at decom.itys.net, behind a front page done up as a late-night infomercial. The receipt section of the manual has a real receipt from the lab, word for word.