Files
warpstore/uninstall.sh
mr.zeroandClaude Opus 5 c4a98e5d67 Let a kind be a list, and carry the asset's catalog path
Both changes are what the third adapter asked for, the way the second one asked
for resolve_for_host.

`pick_release` now takes several kinds in order of preference. A desktop store on
Apple Silicon wants `mac_universal`, will settle for `mac_arm64`, and takes
`mac_x64` only as a last resort because that one runs under Rosetta. Release
order still wins over kind order: the newest release with any acceptable asset
beats an older release with a better one.

Records also carry `asset_path`, the catalog-side path, because not every asset
is a download — an `html` build is a hosted directory, and the desktop store
needs its URL rather than a file name.

The machine-wide uninstaller learned the third engine. It also had to stop
assuming one store per directory name: the RetroArch and desktop engines share a
store root, so a home is now identified by which engine file is in it, and the
launcher name is derived from the home rather than guessed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 10:00:02 +02:00

135 lines
5.1 KiB
Bash
Executable File

#!/bin/sh
# Remove every WarpEngine store from this machine — the games, the store
# engines, this shared core and the launchers.
#
# curl -fsSL https://git.teletypegames.org/engines/warpstore/raw/branch/master/uninstall.sh | sh
#
# A single store is better removed by its own engine's uninstaller, which knows
# that host's extras (a Batocera Ports entry, say). This one is for taking the
# whole framework off a machine without having to remember what is installed:
# it finds every store home under the known roots, has each store's own engine
# purge what it installed, and then deletes the store.
#
# Three engines exist today — Batocera, RetroArch and desktop — and the last two
# share a store root, so a home is identified by which engine file is in it.
#
# DRY_RUN=1 print what would go, remove nothing
# KEEP_GAMES=1 leave the installed games alone, remove only the scripts
# FORCE=1 purge even while RetroArch is running
#
# POSIX sh on purpose: `| sh` has to work on a machine whose /bin/sh is dash.
set -eu
BATOCERA_STORE_ROOT="${BATOCERA_STORE_ROOT:-/userdata/system/batocera-store}"
BATOCERA_PORTS_DIR="${BATOCERA_PORTS_DIR:-/userdata/roms/ports}"
STORE_ROOT="${STORE_ROOT:-${XDG_DATA_HOME:-$HOME/.local/share}/warp-engine-store}"
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
DRY_RUN="${DRY_RUN:-0}"
KEEP_GAMES="${KEEP_GAMES:-0}"
FORCE="${FORCE:-0}"
say() { echo "[uninstall-all] $*"; }
run() {
if [ "$DRY_RUN" = "1" ]; then echo "[uninstall-all] would: $*"; else "$@"; fi
}
command -v python3 >/dev/null 2>&1 || {
echo "[uninstall-all] error: python3 is required to ask the engines what they installed" >&2
exit 1
}
FOUND=0
# `purge` is the engine's own "remove everything I installed": the shell has no
# way of knowing which ROMs, playlists, thumbnails or gamelist entries were a
# store's, and state.json does.
purge_store() { # purge_store <store home> <engine file> <home env var> <extra flag>
home="$1"; engine="$2"; homevar="$3"; extra="$4"
if [ "$KEEP_GAMES" = "1" ]; then
say "keeping the games of '$(basename "$home")' (KEEP_GAMES=1)"
return 0
fi
if [ ! -f "$home/$engine" ] || [ ! -f "$home/config.json" ]; then
say "warning: no engine in $home — its games have to go by hand"
return 0
fi
dry=""
[ "$DRY_RUN" = "1" ] && dry="--dry-run"
# shellcheck disable=SC2086
env "$homevar=$home" python3 "$home/$engine" --config "$home/config.json" $dry purge $extra || {
say "error: the engine in $home could not finish — stopping before anything else is removed."
say " Close RetroArch (or FORCE=1), fix what it reported, and run this again."
exit 1
}
}
# --- Batocera stores -------------------------------------------------------
if [ -d "$BATOCERA_STORE_ROOT" ]; then
for home in "$BATOCERA_STORE_ROOT"/*/; do
[ -f "$home/config.json" ] || continue
id="$(basename "$home")"
FOUND=$((FOUND + 1))
say "batocera store '$id' in $home"
purge_store "${home%/}" store.py BATOCERA_STORE_HOME ""
# The Ports entry's name is whatever the store repository chose, not
# store.name — but it always runs this store's launcher, so it is found by
# looking inside the scripts rather than by guessing a file name.
if [ -d "$BATOCERA_PORTS_DIR" ]; then
for entry in "$BATOCERA_PORTS_DIR"/*.sh; do
[ -f "$entry" ] || continue
if grep -q -F "${home%/}" "$entry" 2>/dev/null; then
run rm -f "$entry"
fi
done
fi
run rm -f "$BATOCERA_STORE_ROOT/$id-store"
run rm -rf "${home%/}"
done
[ "$DRY_RUN" = "1" ] || rmdir "$BATOCERA_STORE_ROOT" 2>/dev/null || true
fi
# --- RetroArch and desktop stores -----------------------------------------
# Both live under the same root, one directory per store id, told apart by the
# engine file inside. A store id can even carry both.
if [ -d "$STORE_ROOT" ]; then
extra=""
[ "$FORCE" = "1" ] && extra="--force"
for home in "$STORE_ROOT"/*/; do
[ -f "$home/config.json" ] || continue
name="$(basename "$home")"
home="${home%/}"
if [ -f "$home/retroarch_store.py" ]; then
id="${name%-retroarch}"
FOUND=$((FOUND + 1))
say "retroarch store '$id' in $home"
purge_store "$home" retroarch_store.py RETROARCH_STORE_HOME "$extra"
run rm -f "$BIN_DIR/$id-retroarch-store"
fi
if [ -f "$home/desktop_store.py" ]; then
id="${name%-desktop}"
FOUND=$((FOUND + 1))
say "desktop store '$id' in $home"
purge_store "$home" desktop_store.py DESKTOP_STORE_HOME ""
run rm -f "$BIN_DIR/$id-desktop-store"
fi
run rm -rf "$home"
done
[ "$DRY_RUN" = "1" ] || rmdir "$STORE_ROOT" 2>/dev/null || true
fi
if [ "$FOUND" = "0" ]; then
say "no WarpEngine store found. Looked in:"
say " $BATOCERA_STORE_ROOT"
say " $STORE_ROOT"
say "Point at them with BATOCERA_STORE_ROOT / STORE_ROOT if they live elsewhere."
exit 0
fi
if [ "$DRY_RUN" = "1" ]; then
say "dry run — nothing was removed ($FOUND store(s) found)."
else
say "done: $FOUND store(s) removed, engines and the shared core with them."
say "Restart EmulationStation or RetroArch to see the games go."
fi