feat(#122 part 1): uninstaller --purge leaves no trace - #183
Merged
Merged
Conversation
Owner
Author
|
Heads-up on merge order: #182 and this PR both add a line to the Merge either first and ping me; I'll rebase the other. |
A full uninstall removed the launcher tree and stopped there, so "get this
Deck back to clean" still meant a hand-written sweep. Three things always
survived: the BYOK CurseForge key outside the install root, the evsieve build
container plus its podman storage, and the Steam shortcut — which the script
merely told you to remove yourself.
--purge covers all three. It is mutually exclusive with --keep-data (checked
AFTER the whole argv is parsed, so flag order cannot decide it) and honours
--dry-run throughout. Without --purge nothing changes: a plain full uninstall
still spares the API key on purpose, so a reinstall need not re-prompt.
remove-from-steam.py — the inverse of add-to-steam.py
-----------------------------------------------------
shortcuts.vdf holds EVERY non-Steam shortcut the user has: emulators, other
launchers, work tools. A byte-splice that guesses wrong does not fail loudly,
it silently destroys all of them. So this is a real typed VDF parser, not a
regex, with four guards:
1. Re-serializing the unmodified parse must reproduce the input byte for
byte, or we do not understand the file and refuse to write it.
2. A timestamped backup is written before any rewrite.
3. Only provably-ours entries are removed (launcher path, or our exact app
name, or an exe under TARGET_DIR) — matching is deliberately narrow so a
user's own Minecraft shortcut survives.
4. Steam must not be running: it keeps shortcuts.vdf in memory and rewrites
it on exit, so an edit made now is silently reverted. We refuse (exit 2)
rather than report a success that will evaporate — and we never restart
Steam for the user; `steam -shutdown` over SSH has wedged Game Mode.
Remaining entries are renumbered contiguously, and the SteamGridDB artwork
add-to-steam.py downloaded is removed by appid.
Container removal blast radius
------------------------------
The issue floated `podman system reset`. That wipes every container, image and
volume the user owns. On a Deck those are probably only ours, but "probably" is
not a basis for deleting someone else's containers, so this removes the named
mcss-evsieve-build box and its debian:12 image and nothing else (PRINCIPLES
reports zero containers AND zero images left, via `podman unshare rm` for the
mapped-root UIDs a plain rm cannot touch.
Fail-open (PRINCIPLES #5): if remove-from-steam.py cannot be found beside the
uninstaller or fetched, we print the manual instruction the old script always
printed instead of aborting the uninstall.
tests/test_uninstall_purge.sh — 19 tests, in the CI baseline. Weighted toward
what must SURVIVE, since that is the unrecoverable direction: foreign shortcuts
and their artwork, worlds under --keep-data, the API key without --purge, and
the whole tree on a rejected or dry-run invocation.
Mutation-tested (PRINCIPLES #4), nine mutations. Seven were caught first pass;
two survived and exposed real gaps, now closed:
- dry-run was only asserted against shortcuts.vdf, not the artwork, so a
dry run that deleted the user's library art would have passed (T4.8);
- nothing exercised the round-trip guard's refusal, only the property it
checks (T4.9).
Verified end to end against a sandboxed fake $HOME + Steam tree: our entry and
artwork removed, two foreign shortcuts renumbered 0/1 with artwork intact,
backup written, second run a clean no-op, and the Steam-running guard exercised
against a real process named steam (killed by tracked PID, never by name).
NOT yet hardware-validated — needs the destructive ~20 min Deck sitting in the
v1.2.4 plan, which is the only way to exercise real podman/distrobox removal
and a real Steam library.
Part 1 of #122.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dru6wVc2ZcjiTpa6p7yJDh
Found on the Deck, 2026-08-01, by reading the --purge --dry-run output against a real install (PRINCIPLES #3 — the reason the rule exists). The store-reclaim check counts `podman ps -aq` / `podman images -q` to decide whether ~/.local/share/containers is now empty and safe to remove. In a dry run nothing has actually been removed, so those counts still included OUR OWN box and image. On a Deck whose only container is ours the dry run therefore printed Kept ... — you have other podman containers/images while the real run would find 0/0 and delete the store. A dry run that predicts the opposite of the real outcome is worse than one that predicts nothing: it is exactly the output an operator reads to decide whether it is safe to proceed. Discount our own box and image from the counts when DRY_RUN. Also track whether `podman rmi` actually succeeded — a refused rmi (another container still references the image) means the image is still there and must count as a leftover, not as removed. Tests 19 -> 22, CI baseline bumped. Neither podman nor distrobox exists in CI, so both are faked on PATH: T6.1 dry run predicts REMOVING the store when only ours remain T6.2 dry run predicts KEEPING it when a foreign container exists T6.3 a populated store survives a REAL purge (the blast-radius rule) Mutation-verified: reverting to the pre-fix arithmetic turns T6.1 red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dru6wVc2ZcjiTpa6p7yJDh
aradanmn
force-pushed
the
fix/122-uninstaller-purge
branch
from
August 1, 2026 12:11
cac81e3 to
4bbae5c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of #122. Part 2 (routing
$HOMEartifacts to.workdir/) is #182 —independent, no file overlap, either can merge first.
Problem
A full uninstall removed the launcher tree and stopped. Three things always
survived: the BYOK CurseForge key (it lives outside the install root), the
evsieve build container plus its podman storage, and the Steam shortcut — which
the script merely told you to remove yourself.
--purgeCovers all three. Mutually exclusive with
--keep-data, checked after thewhole argv is parsed so flag order cannot decide it, and honours
--dry-runthroughout. Without
--purgenothing changes — a plain full uninstall stillspares the API key on purpose, so a reinstall need not re-prompt.
remove-from-steam.py— the inverse ofadd-to-steam.pyshortcuts.vdfholds every non-Steam shortcut the user has: emulators,other launchers, work tools. A byte-splice that guesses wrong doesn't fail
loudly, it silently destroys all of them. So this is a real typed VDF parser,
not a regex, with four guards:
the input byte for byte, or we don't understand the file and refuse to write.
under
TARGET_DIR. A user's own Minecraft shortcut survives.shortcuts.vdfin memory andrewrites it on exit, so an edit made now is silently reverted. We refuse
(exit 2) rather than report a success that will evaporate. We never restart
Steam for the user:
steam -shutdownover SSH has wedged Game Mode before.Remaining entries are renumbered contiguously; the SteamGridDB artwork
add-to-steam.pydownloaded is removed by appid.Container removal — a deliberate narrowing of the issue
The issue floated
podman system reset. That wipes every container, imageand volume the user owns. On a Deck those are probably only ours, but
"probably" is not a basis for deleting someone else's containers.
So: remove the named
mcss-evsieve-buildbox and itsdebian:12image, andnothing else (PRINCIPLES #7). The shared
~/.local/share/containersstore isreclaimed only when podman reports zero containers and zero images left —
via
podman unshare rm, for the mapped-root UIDs a plainrmcan't touch.Otherwise we say we kept it and why.
Say the word if you'd rather have the nuclear option behind an extra flag.
Tests
tests/test_uninstall_purge.sh— 19 tests, in the CI baseline. Weighted towardwhat must survive, since that's the unrecoverable direction: foreign
shortcuts and their artwork, worlds under
--keep-data, the API key without--purge, and the whole tree on a rejected or dry-run invocation.Mutation-tested — nine mutations. Seven caught first pass; two survived
and exposed real gaps, now closed:
Verification
End to end against a sandboxed fake
$HOME+ Steam tree: our entry and artworkremoved, two foreign shortcuts renumbered 0/1 with artwork intact, backup
written, second run a clean no-op, unparseable file refused and left byte-identical,
and the Steam-running guard exercised against a real process named
steam(killed by tracked PID, never by name).
shellcheckclean, no new warnings.--keep-dataregression-tested.Not done
Not hardware-validated. Needs the destructive ~20 min Deck sitting from the
v1.2.4 plan — the only way to exercise real podman/distrobox removal and a real
Steam library. Worth pairing with the
--dry-runoutput first.🤖 Generated with Claude Code
https://claude.ai/code/session_01Dru6wVc2ZcjiTpa6p7yJDh