#!/usr/bin/env bash # uninstall-larry.sh — cleanly remove everything install-larry.sh put down. # # Reverses the installer EXACTLY: it removes only what Larry-Anywhere created # (its install dir, the `larry` PATH shim, the optional PHI venv, the jq # fallback binary, and all runtime artifacts under $LARRY_HOME) and NOTHING the # installer didn't create. It does NOT touch your shell rc, your Cloverleaf # sites, your $HCIROOT, or any file outside the install footprint. # # Default is a DRY-RUN preview. You must pass --yes (or confirm at the prompt) # to actually delete. Every path removed is printed. # # Usage: # uninstall-larry.sh # dry-run: list exactly what WOULD be removed # uninstall-larry.sh --dry-run # (explicit) same as above # uninstall-larry.sh --yes # remove without the interactive prompt # uninstall-larry.sh --keep-data # remove program files but KEEP user data # # (sessions/, journal/, lessons/, # # knowledge/, sanitize/, log/, creds) # uninstall-larry.sh --help # # Also reachable as a subcommand: larry uninstall [--dry-run|--yes|--keep-data] # # Env vars (mirror the installer so it targets the SAME footprint): # LARRY_HOME install location (default: $HOME/.larry) # LARRY_BIN_DIR where the `larry` shim was symlinked (default: $HOME/bin) set -eu LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_BIN_DIR="${LARRY_BIN_DIR:-$HOME/bin}" C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_GREEN=$'\033[32m' C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'; C_CYAN=$'\033[36m'; C_DIM=$'\033[2m' say() { printf '%s%suninstall-larry>%s %s\n' "$C_CYAN" "$C_BOLD" "$C_RESET" "$*"; } ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$*"; } warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; } die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; } # ───────────────────────────────────────────────────────────────────────────── # Args # ───────────────────────────────────────────────────────────────────────────── DRY_RUN=1 # default: preview only ASSUME_YES=0 KEEP_DATA=0 for arg in "${@:-}"; do case "$arg" in --yes|-y) DRY_RUN=0; ASSUME_YES=1 ;; --dry-run|-n) DRY_RUN=1 ;; --keep-data) KEEP_DATA=1 ;; --help|-h) sed -n '2,38p' "$0"; exit 0 ;; "") : ;; *) die "unknown flag: $arg (try --help)" ;; esac done # ───────────────────────────────────────────────────────────────────────────── # Stop background processes Larry may have started, BEFORE deleting their files. # Both PID files live under $LARRY_HOME. Best-effort: a stale/dead PID is fine. # We only kill a PID we can read from Larry's own pidfile — never a guessed one. # ───────────────────────────────────────────────────────────────────────────── stop_bg_proc() { # $1 = human label, $2 = pidfile path local label="$1" pidfile="$2" pid="" [ -f "$pidfile" ] || return 0 pid="$(tr -d '[:space:]' < "$pidfile" 2>/dev/null || echo "")" case "$pid" in ''|*[!0-9]*) return 0 ;; # empty or non-numeric → nothing safe to do esac if kill -0 "$pid" 2>/dev/null; then if [ "$DRY_RUN" = "1" ]; then printf ' %s•%s would stop %s (PID %s)\n' "$C_YELLOW" "$C_RESET" "$label" "$pid" else kill "$pid" 2>/dev/null && ok "stopped $label (PID $pid)" || warn "could not stop $label (PID $pid) — kill it manually" fi fi } # ───────────────────────────────────────────────────────────────────────────── # Build the removal list. We separate PROGRAM artifacts (always removed) from # USER DATA (removed unless --keep-data). Everything here was created by the # installer or by Larry at runtime — we never list anything else. # ───────────────────────────────────────────────────────────────────────────── PROGRAM_TARGETS=() # files/dirs that ARE the install footprint DATA_TARGETS=() # user-generated data under $LARRY_HOME # The shim the installer drops onto PATH. We only remove it if it is OUR shim # (auto-generated header), so we never delete an unrelated `larry` someone else # put there. SHIM="$LARRY_BIN_DIR/larry" if [ -f "$SHIM" ] && grep -q 'Auto-generated by install-larry.sh' "$SHIM" 2>/dev/null; then PROGRAM_TARGETS+=("$SHIM") elif [ -e "$SHIM" ]; then warn "$SHIM exists but is NOT our auto-generated shim — leaving it untouched." fi # The entire install dir is ours. Rather than enumerate every dotfile, we list # $LARRY_HOME itself for the full uninstall. With --keep-data we instead remove # the program files inside it and preserve the user-data subtrees. if [ -d "$LARRY_HOME" ]; then if [ "$KEEP_DATA" = "0" ]; then PROGRAM_TARGETS+=("$LARRY_HOME") else # Program files (shipped bundle + caches + state markers + the jq fallback # + the optional PHI venv). Anything not in this list under $LARRY_HOME is # treated as user data and preserved. for p in \ larry.sh larry-tunnel.sh larry-rollback.sh larry-auth.sh uninstall-larry.sh \ install-larry.sh VERSION MANUAL.md MANIFEST CHANGELOG.md README.md \ inbound-systems.tsv agents lib bin phi-venv \ .last-sync-version .origin .oauth-optin-warned \ .phi-notice-shown .b64-py3-notice-shown \ .last-curl-rc .last-curl-stderr .last-curl-headers \ .last-stream-headers .last-stream-curlerr \ .phi-sidecar.pid tunnel.pid tunnel.url tunnel.log .history; do [ -e "$LARRY_HOME/$p" ] && PROGRAM_TARGETS+=("$LARRY_HOME/$p") done # User data preserved under --keep-data (listed for transparency). for d in sessions journal lessons knowledge sanitize regression log \ .api-key .env .oauth.json .ssh-creds .ssh-sockets .ssh-hosts.tsv \ known_hosts .headers-sync-offset .headers-sync-state; do [ -e "$LARRY_HOME/$d" ] && DATA_TARGETS+=("$LARRY_HOME/$d") done fi fi if [ "${#PROGRAM_TARGETS[@]}" -eq 0 ]; then say "nothing to remove — no Larry-Anywhere install footprint found." say " (looked for LARRY_HOME=$LARRY_HOME and shim $SHIM)" exit 0 fi # ───────────────────────────────────────────────────────────────────────────── # Preview # ───────────────────────────────────────────────────────────────────────────── say "Larry-Anywhere uninstall — install footprint to remove:" echo "" printf '%sProgram files (always removed):%s\n' "$C_BOLD" "$C_RESET" for t in "${PROGRAM_TARGETS[@]}"; do if [ -d "$t" ]; then printf ' %s(dir) %s%s\n' "$C_DIM" "$t" "$C_RESET" else printf ' %s\n' "$t"; fi done if [ "$KEEP_DATA" = "1" ]; then echo "" printf '%sUser data PRESERVED (--keep-data):%s\n' "$C_BOLD" "$C_RESET" if [ "${#DATA_TARGETS[@]}" -eq 0 ]; then printf ' %s(none found)%s\n' "$C_DIM" "$C_RESET" else for d in "${DATA_TARGETS[@]}"; do printf ' %skept%s %s\n' "$C_GREEN" "$C_RESET" "$d"; done fi fi echo "" say "NOT touched: your shell rc (any PATH export you added stays — remove it by hand)," say " your Cloverleaf sites / \$HCIROOT, and anything outside the list above." echo "" # Stop background procs (preview prints "would stop …"; real run kills them). stop_bg_proc "PHI Presidio sidecar" "$LARRY_HOME/.phi-sidecar.pid" stop_bg_proc "reverse SSH tunnel" "$LARRY_HOME/tunnel.pid" echo "" if [ "$DRY_RUN" = "1" ]; then say "${C_BOLD}DRY-RUN${C_RESET} — nothing was removed. Re-run with ${C_CYAN}--yes${C_RESET} to delete the above." exit 0 fi # ───────────────────────────────────────────────────────────────────────────── # Confirm (skipped with --yes... but --yes already set ASSUME_YES; this is the # belt-and-suspenders path if someone sets DRY_RUN=0 by other means). # ───────────────────────────────────────────────────────────────────────────── if [ "$ASSUME_YES" != "1" ]; then printf '%sProceed with removal? This cannot be undone. [y/N]: %s' "$C_YELLOW" "$C_RESET" ans="" if [ -t 0 ]; then read -r ans /dev/null; then ok "removed $t" removed=$((removed+1)) else warn "could not remove $t (check permissions; remove by hand)" fi done echo "" say "removed $removed item(s)." if [ "$KEEP_DATA" = "1" ] && [ "${#DATA_TARGETS[@]}" -gt 0 ]; then say "your data is preserved under $LARRY_HOME — delete it manually if you want a full wipe." fi # Reminder: the installer never EDITS your shell rc; it only WARNS you to add a # PATH export. If you added one, it's still there — this is the one manual step. case ":$PATH:" in *":$LARRY_BIN_DIR:"*) say "reminder: you may have added 'export PATH=\"$LARRY_BIN_DIR:\$PATH\"' to your shell rc. Remove that line by hand if $LARRY_BIN_DIR is now empty." ;; esac say "uninstall complete."