v0.8.34: harden uninstall-larry.sh into a first-class PHI-grade decommission
One-run `larry uninstall` / uninstall-larry.sh that: - stops detached larry.sh REPL + phi-presidio-sidecar + larry-tunnel (pgrep+kill by pattern, never kills itself/parent/uninstall-larry) - SECURELY deletes cleartext PHI (auto-phi.log, lookup.tsv, sessions/*.log.md) via shred -u -z -n 3, with overwrite-then-rm fallback on Windows/MobaXterm where shred is absent, honest per-platform "secure achieved?" reporting, and a find-less bash-glob fallback for session files - strips ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|LARRY_*|GITEA_TOKEN from shell rc with a timestamped backup (default), or prints them under --keep-rc - removes ~/larry, ~/.local/bin/larry, ~/bin/larry, ~/larry-anywhere (our shims only; foreign `larry` preserved), then self-removes a standalone checkout - prints a FINISH-AT-THE-SOURCE reminder: revoke API key + OAuth grant + PAT, plus a BAA/PHI-disclosure note - hard rm-rf-/ guards (empty/unset/root/$HOME/non-larry LARRY_HOME refused), scoped strictly to the built target list; DRY-RUN default; new --keep-rc and --no-shred flags Tested: full real run, dry-run scope, all rm-rf guards, --keep-data, no-shred(Windows) fallback, idempotency, standalone-checkout self-uninstall. MANIFEST regenerated so the self-update ships it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7606a535c9
commit
6b45543652
48
CHANGELOG.md
48
CHANGELOG.md
@ -4,6 +4,54 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
|
|||||||
Versioning is loose-semver; bumps trigger the in-process self-update on every
|
Versioning is loose-semver; bumps trigger the in-process self-update on every
|
||||||
running client via `LARRY_BASE_URL` + `MANIFEST`.
|
running client via `LARRY_BASE_URL` + `MANIFEST`.
|
||||||
|
|
||||||
|
## v0.8.34 — 2026-05-31
|
||||||
|
|
||||||
|
**Hardened `uninstall-larry.sh` into a first-class, healthcare-grade decommission
|
||||||
|
command — "update larry, then run one command."**
|
||||||
|
|
||||||
|
The v0.8.33 uninstaller removed the install footprint but did not meet the
|
||||||
|
operational decommission requirements for a PHI-handling deployment left on a
|
||||||
|
client box. This release makes `larry uninstall` (and the shipped
|
||||||
|
`uninstall-larry.sh` it delegates to) self-contained and idempotent: one run
|
||||||
|
stops everything, securely destroys cleartext PHI, scrubs creds, self-removes,
|
||||||
|
and tells you what you still must do at the source. The `larry uninstall`
|
||||||
|
early-dispatch in `larry.sh` is unchanged — it already delegates here.
|
||||||
|
|
||||||
|
### New / changed behavior
|
||||||
|
- **Stop everything (tolerant):** in addition to the precise pidfile kills (PHI
|
||||||
|
Presidio sidecar, reverse-SSH tunnel), it now `pgrep`+kills detached
|
||||||
|
`larry.sh` REPLs, `phi-presidio-sidecar`, and `larry-tunnel` keepalives by
|
||||||
|
command pattern. It NEVER kills itself, its parent, or any `uninstall-larry`
|
||||||
|
process (patterns + per-PID exclusion).
|
||||||
|
- **PHI hygiene (healthcare):** `log/auto-phi.log`, `sanitize/lookup.tsv`, and
|
||||||
|
`sessions/*.log.md` are SECURELY deleted FIRST — `shred -u -z -n 3` where
|
||||||
|
available; overwrite-then-rm fallback (zero + urandom passes) where `shred` is
|
||||||
|
absent (Windows/MobaXterm); truncate-then-rm if even `dd` is missing. It
|
||||||
|
prints HONESTLY per-file whether a real secure-delete was achieved, and warns
|
||||||
|
that overwrite-in-place is not guaranteed on SSD/CoW/Windows filesystems.
|
||||||
|
`find`-less hosts use a bash-glob fallback so session PHI is never skipped.
|
||||||
|
- **Shell-profile creds:** greps `.bashrc`/`.bash_profile`/`.profile` for
|
||||||
|
`ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|LARRY_*|GITEA_TOKEN` and, by
|
||||||
|
default, backs up the rc (timestamped `.larry-uninstall.<ts>.bak`) then strips
|
||||||
|
only those lines. `--keep-rc` prints them for manual removal instead.
|
||||||
|
- **More shim/copy locations:** removes `~/larry`, `~/.local/bin/larry`,
|
||||||
|
`~/bin/larry`, `$LARRY_BIN_DIR/larry`, and a scp'd `~/larry-anywhere` — but
|
||||||
|
only OUR shims (auto-generated header or a launcher/symlink into `$LARRY_HOME`);
|
||||||
|
a foreign `larry` is left untouched.
|
||||||
|
- **Self-uninstall last:** when run from a standalone larry-anywhere checkout
|
||||||
|
(not from inside `$LARRY_HOME`), removes that checkout and the script itself —
|
||||||
|
only if real work was done this run and the dir is a full bundle.
|
||||||
|
- **Post-uninstall reminder:** prints the explicit "FINISH AT THE SOURCE" block —
|
||||||
|
REVOKE the Anthropic API key AND the Claude-Code OAuth grant (local removal
|
||||||
|
does not invalidate egressed copies), revoke any Gitea PAT / rotate SSH creds,
|
||||||
|
plus a BAA/PHI-disclosure reminder for healthcare deployments.
|
||||||
|
- **Safety guards:** refuses to operate if `LARRY_HOME` is empty/unset, `/`,
|
||||||
|
`$HOME`, a single-component-at-root path (`/.larry`), or anything that doesn't
|
||||||
|
look like a Larry dir — so a misconfigured env can never `rm -rf /`. Removal is
|
||||||
|
scoped strictly to the built target list (no glob expansion, no parent touch).
|
||||||
|
- New flags: `--keep-rc`, `--no-shred` (alongside the existing `--yes`,
|
||||||
|
`--dry-run`, `--keep-data`). DRY-RUN remains the default.
|
||||||
|
|
||||||
## v0.8.33 — 2026-05-29
|
## v0.8.33 — 2026-05-29
|
||||||
|
|
||||||
**Two operator-requested features: a real uninstaller, and a deterministic-only
|
**Two operator-requested features: a real uninstaller, and a deterministic-only
|
||||||
|
|||||||
6
MANIFEST
6
MANIFEST
@ -28,12 +28,12 @@ larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
|
|||||||
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
|
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
|
||||||
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
|
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
|
||||||
install-larry.sh 0779dd74c0cecef174edbe7782da275be9bf6d2ec7e6ae88c1de46c666adc8b2
|
install-larry.sh 0779dd74c0cecef174edbe7782da275be9bf6d2ec7e6ae88c1de46c666adc8b2
|
||||||
uninstall-larry.sh 5d0e2fe1366c818dc207f70c85e38279218b58f5eae10a8a6b0a277a02f2e4d0
|
uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
VERSION d9d12c30d3607edd1bfd2ef0feb0738f917e113a4bc1570d5dbb2ea8426531a8
|
VERSION 437e52707aa48e9ee29b4469dc0790ad646390c08644c415a5171052487fd280
|
||||||
MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f
|
MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f
|
||||||
CHANGELOG.md 67ed1238990e96d481ffa804cbfcbf219958438d6285e6efe864a33b8df97a32
|
CHANGELOG.md 25b82ebf360bb345bb1d7008386532773e9bf55d67b331a7d572d63ff4ab87ae
|
||||||
|
|
||||||
# Agent personas (system-prompt overlays)
|
# Agent personas (system-prompt overlays)
|
||||||
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
|
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
|
||||||
|
|||||||
@ -1,109 +1,257 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# uninstall-larry.sh — cleanly remove everything install-larry.sh put down.
|
# uninstall-larry.sh — cleanly and SECURELY remove everything Larry-Anywhere
|
||||||
|
# (a.k.a. "Cloverleaf-Larry") put down, in ONE run. Healthcare-grade: PHI-bearing
|
||||||
|
# files are securely shredded (where the platform allows) BEFORE the dir is wiped.
|
||||||
#
|
#
|
||||||
# Reverses the installer EXACTLY: it removes only what Larry-Anywhere created
|
# Reverses the installer EXACTLY: it removes only what Larry-Anywhere created
|
||||||
# (its install dir, the `larry` PATH shim, the optional PHI venv, the jq
|
# (its install dir / $LARRY_HOME, the `larry` PATH shims, the optional PHI venv,
|
||||||
# fallback binary, and all runtime artifacts under $LARRY_HOME) and NOTHING the
|
# the jq fallback binary, all runtime/PHI artifacts) and NOTHING the installer
|
||||||
# installer didn't create. It does NOT touch your shell rc, your Cloverleaf
|
# didn't create. It does NOT touch your Cloverleaf sites or your $HCIROOT. It
|
||||||
# sites, your $HCIROOT, or any file outside the install footprint.
|
# DOES (by default) strip Larry-related credential exports it finds in your shell
|
||||||
|
# rc — with a timestamped backup first — because leaving plaintext secrets behind
|
||||||
|
# is the unsafe default for a healthcare deployment (override with --keep-rc).
|
||||||
#
|
#
|
||||||
# Default is a DRY-RUN preview. You must pass --yes (or confirm at the prompt)
|
# Default is a DRY-RUN preview. You must pass --yes (or confirm at the prompt)
|
||||||
# to actually delete. Every path removed is printed.
|
# to actually delete. Every path removed is printed, per-step success/failure.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# uninstall-larry.sh # dry-run: list exactly what WOULD be removed
|
# uninstall-larry.sh # dry-run: list exactly what WOULD happen
|
||||||
# uninstall-larry.sh --dry-run # (explicit) same as above
|
# uninstall-larry.sh --dry-run # (explicit) same as above
|
||||||
# uninstall-larry.sh --yes # remove without the interactive prompt
|
# uninstall-larry.sh --yes # remove without the interactive prompt
|
||||||
# uninstall-larry.sh --keep-data # remove program files but KEEP user data
|
# uninstall-larry.sh --keep-data # remove program files but KEEP user data
|
||||||
# # (sessions/, journal/, lessons/,
|
# # (sessions/, journal/, lessons/,
|
||||||
# # knowledge/, sanitize/, log/, creds)
|
# # knowledge/, sanitize/, log/, creds)
|
||||||
|
# uninstall-larry.sh --keep-rc # do NOT modify shell rc files; just PRINT
|
||||||
|
# # any Larry credential lines for manual
|
||||||
|
# # removal (no auto-strip)
|
||||||
|
# uninstall-larry.sh --no-shred # skip secure-delete; plain rm (NOT advised
|
||||||
|
# # for PHI hosts — kept for slow disks)
|
||||||
# uninstall-larry.sh --help
|
# uninstall-larry.sh --help
|
||||||
#
|
#
|
||||||
# Also reachable as a subcommand: larry uninstall [--dry-run|--yes|--keep-data]
|
# Also reachable as a subcommand: larry uninstall [flags]
|
||||||
#
|
#
|
||||||
# Env vars (mirror the installer so it targets the SAME footprint):
|
# Env vars (mirror the installer so it targets the SAME footprint):
|
||||||
# LARRY_HOME install location (default: $HOME/.larry)
|
# LARRY_HOME install location (default: $HOME/.larry)
|
||||||
# LARRY_BIN_DIR where the `larry` shim was symlinked (default: $HOME/bin)
|
# LARRY_BIN_DIR primary `larry` shim dir (default: $HOME/bin)
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
LARRY_BIN_DIR="${LARRY_BIN_DIR:-$HOME/bin}"
|
# Paths. NOTE on safety: we intentionally do NOT default LARRY_HOME to a bare
|
||||||
|
# expansion that could collapse to "/". If HOME is empty AND LARRY_HOME is unset,
|
||||||
|
# the guard below aborts rather than risk rm -rf on a dangerous path.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
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_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'
|
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" "$*"; }
|
say() { printf '%s%suninstall-larry>%s %s\n' "$C_CYAN" "$C_BOLD" "$C_RESET" "$*"; }
|
||||||
ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
|
ok() { printf ' %s\xe2\x9c\x93%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
|
||||||
warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; }
|
warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; }
|
||||||
|
step() { printf '%s\xe2\x80\xa2%s %s\n' "$C_BOLD" "$C_RESET" "$*"; }
|
||||||
die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; }
|
die() { printf '%serror:%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Platform detection (matches install-larry.sh / larry.sh). On Windows-bash
|
||||||
|
# (MobaXterm/Cygwin/MSYS/Git-Bash) `shred` is usually ABSENT and there is no
|
||||||
|
# Presidio venv — we degrade honestly.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
PLATFORM="unknown"
|
||||||
|
case "$(uname -s 2>/dev/null || echo unknown)" in
|
||||||
|
Linux*) PLATFORM="linux" ;;
|
||||||
|
Darwin*) PLATFORM="darwin" ;;
|
||||||
|
CYGWIN*|MINGW*|MSYS*) PLATFORM="windows-cygwin" ;; # MobaXterm lives here
|
||||||
|
esac
|
||||||
|
[ -n "${MSYSTEM:-}" ] && PLATFORM="windows-cygwin"
|
||||||
|
case "${OSTYPE:-}" in cygwin*|msys*) PLATFORM="windows-cygwin" ;; esac
|
||||||
|
|
||||||
|
HAVE_SHRED=0
|
||||||
|
command -v shred >/dev/null 2>&1 && HAVE_SHRED=1
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Args
|
# Args
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
DRY_RUN=1 # default: preview only
|
DRY_RUN=1 # default: preview only
|
||||||
ASSUME_YES=0
|
ASSUME_YES=0
|
||||||
KEEP_DATA=0
|
KEEP_DATA=0
|
||||||
|
KEEP_RC=0
|
||||||
|
NO_SHRED=0
|
||||||
for arg in "${@:-}"; do
|
for arg in "${@:-}"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--yes|-y) DRY_RUN=0; ASSUME_YES=1 ;;
|
--yes|-y) DRY_RUN=0; ASSUME_YES=1 ;;
|
||||||
--dry-run|-n) DRY_RUN=1 ;;
|
--dry-run|-n) DRY_RUN=1 ;;
|
||||||
--keep-data) KEEP_DATA=1 ;;
|
--keep-data) KEEP_DATA=1 ;;
|
||||||
--help|-h) sed -n '2,38p' "$0"; exit 0 ;;
|
--keep-rc) KEEP_RC=1 ;;
|
||||||
|
--no-shred) NO_SHRED=1 ;;
|
||||||
|
--help|-h) sed -n '2,49p' "$0"; exit 0 ;;
|
||||||
"") : ;;
|
"") : ;;
|
||||||
*) die "unknown flag: $arg (try --help)" ;;
|
*) die "unknown flag: $arg (try --help)" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Stop background processes Larry may have started, BEFORE deleting their files.
|
# HARD SAFETY GUARD — never operate on an empty/unset/root/HOME LARRY_HOME, so a
|
||||||
# Both PID files live under $LARRY_HOME. Best-effort: a stale/dead PID is fine.
|
# misconfigured env can never turn this into `rm -rf /` or wipe $HOME. This is
|
||||||
# We only kill a PID we can read from Larry's own pidfile — never a guessed one.
|
# the single most important guard in the script.
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
stop_bg_proc() {
|
_norm() { printf '%s' "$1" | sed 's:/*$::'; } # strip trailing slashes
|
||||||
# $1 = human label, $2 = pidfile path
|
# If HOME is empty/unset, LARRY_HOME defaulted to "/.larry" — a root-leaf path we
|
||||||
|
# must NOT operate on. Require an explicit, absolute, non-root LARRY_HOME.
|
||||||
|
if [ -z "${HOME:-}" ] && [ -z "${LARRY_HOME:-}" ]; then
|
||||||
|
die "refusing to operate: HOME is unset and LARRY_HOME was not given. Set LARRY_HOME explicitly."
|
||||||
|
fi
|
||||||
|
_LH_NORM="$(_norm "$LARRY_HOME")"
|
||||||
|
case "$_LH_NORM" in
|
||||||
|
""|"/"|"/root"|"/home"|"/Users"|"/usr"|"/etc"|"/var"|"/bin"|"/tmp"|"/.larry")
|
||||||
|
die "refusing to operate: LARRY_HOME resolves to a dangerous/empty path ('$LARRY_HOME'). Set LARRY_HOME explicitly." ;;
|
||||||
|
esac
|
||||||
|
# Must be an absolute path with at least two path components (e.g. /home/u/.larry),
|
||||||
|
# never a single-component-at-root like /.larry or /larry.
|
||||||
|
case "$_LH_NORM" in
|
||||||
|
/*/?*) : ;;
|
||||||
|
*) die "refusing to operate: LARRY_HOME ('$LARRY_HOME') must be an absolute path below a home/parent dir." ;;
|
||||||
|
esac
|
||||||
|
if [ -n "${HOME:-}" ] && [ "$_LH_NORM" = "$(_norm "$HOME")" ]; then
|
||||||
|
die "refusing to operate: LARRY_HOME equals \$HOME ('$LARRY_HOME'). That cannot be right."
|
||||||
|
fi
|
||||||
|
# Defense in depth: LARRY_HOME leaf must look like a Larry dir.
|
||||||
|
case "$_LH_NORM" in
|
||||||
|
*/.larry|*larry*) : ;;
|
||||||
|
*) die "refusing to operate: LARRY_HOME ('$LARRY_HOME') doesn't look like a Larry install dir." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Where this script physically lives (for the self-uninstall step). When run as
|
||||||
|
# $LARRY_HOME/uninstall-larry.sh it's inside the tree we delete; when run from a
|
||||||
|
# separate larry-anywhere checkout we also remove that checkout.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
SELF_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo "")"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 1. STOP EVERYTHING — kill cleanly, tolerate absence. Two layers:
|
||||||
|
# (a) pidfiles Larry wrote under $LARRY_HOME (precise — only a PID we own);
|
||||||
|
# (b) pgrep+kill by command pattern (catches detached REPLs / keepalive loops
|
||||||
|
# with no pidfile). We NEVER kill the uninstaller itself, its parent, or
|
||||||
|
# any process whose command contains 'uninstall-larry'.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
stop_bg_proc() { # $1 = label, $2 = pidfile
|
||||||
local label="$1" pidfile="$2" pid=""
|
local label="$1" pidfile="$2" pid=""
|
||||||
[ -f "$pidfile" ] || return 0
|
[ -f "$pidfile" ] || return 0
|
||||||
pid="$(tr -d '[:space:]' < "$pidfile" 2>/dev/null || echo "")"
|
pid="$(tr -d '[:space:]' < "$pidfile" 2>/dev/null || echo "")"
|
||||||
case "$pid" in
|
case "$pid" in ''|*[!0-9]*) return 0 ;; esac
|
||||||
''|*[!0-9]*) return 0 ;; # empty or non-numeric → nothing safe to do
|
|
||||||
esac
|
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
if [ "$DRY_RUN" = "1" ]; then
|
if [ "$DRY_RUN" = "1" ]; then
|
||||||
printf ' %s•%s would stop %s (PID %s)\n' "$C_YELLOW" "$C_RESET" "$label" "$pid"
|
printf ' %s\xe2\x80\xa2%s would stop %s (PID %s)\n' "$C_YELLOW" "$C_RESET" "$label" "$pid"
|
||||||
else
|
else
|
||||||
kill "$pid" 2>/dev/null && ok "stopped $label (PID $pid)" || warn "could not stop $label (PID $pid) — kill it manually"
|
kill "$pid" 2>/dev/null && ok "stopped $label (PID $pid)" \
|
||||||
|
|| warn "could not stop $label (PID $pid) — kill it manually"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
SELF_PID="$$"
|
||||||
# Build the removal list. We separate PROGRAM artifacts (always removed) from
|
SELF_PPID="$(ps -o ppid= -p "$$" 2>/dev/null | tr -d '[:space:]' || echo '')"
|
||||||
# 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
|
pkill_pattern() { # $1 = label, $2 = pgrep -f pattern
|
||||||
# (auto-generated header), so we never delete an unrelated `larry` someone else
|
local label="$1" pat="$2"
|
||||||
# put there.
|
command -v pgrep >/dev/null 2>&1 || return 0 # no pgrep → nothing safe to do
|
||||||
SHIM="$LARRY_BIN_DIR/larry"
|
local pids="" pid cmd
|
||||||
if [ -f "$SHIM" ] && grep -q 'Auto-generated by install-larry.sh' "$SHIM" 2>/dev/null; then
|
pids="$(pgrep -f "$pat" 2>/dev/null || true)"
|
||||||
|
for pid in $pids; do
|
||||||
|
[ -n "$pid" ] || continue
|
||||||
|
case "$pid" in *[!0-9]*) continue ;; esac
|
||||||
|
[ "$pid" = "$SELF_PID" ] && continue
|
||||||
|
[ -n "$SELF_PPID" ] && [ "$pid" = "$SELF_PPID" ] && continue
|
||||||
|
cmd="$(ps -o args= -p "$pid" 2>/dev/null || echo '')"
|
||||||
|
case "$cmd" in *uninstall-larry*) continue ;; esac # never kill ourselves
|
||||||
|
if [ "$DRY_RUN" = "1" ]; then
|
||||||
|
printf ' %s\xe2\x80\xa2%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
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
say "1) Stopping Larry processes (best-effort, tolerates absence)"
|
||||||
|
stop_bg_proc "PHI Presidio sidecar" "$LARRY_HOME/.phi-sidecar.pid"
|
||||||
|
stop_bg_proc "reverse SSH tunnel" "$LARRY_HOME/tunnel.pid"
|
||||||
|
# Patterns require a path-sep/space before 'larry.sh' so 'uninstall-larry.sh'
|
||||||
|
# (this script) is NOT matched; the per-PID uninstall-larry exclusion is backup.
|
||||||
|
pkill_pattern "larry.sh REPL" '[/ ]larry\.sh'
|
||||||
|
pkill_pattern "phi-presidio-sidecar" 'phi-presidio-sidecar'
|
||||||
|
pkill_pattern "larry-tunnel keepalive" 'larry-tunnel'
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 2/3. Build removal lists, separating:
|
||||||
|
# - PHI_TARGETS : cleartext-PHI files → SECURE delete first (healthcare)
|
||||||
|
# - PROGRAM_TARGETS: program footprint + the rest of $LARRY_HOME
|
||||||
|
# - DATA_TARGETS : user data preserved under --keep-data
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
PHI_TARGETS=()
|
||||||
|
PROGRAM_TARGETS=()
|
||||||
|
DATA_TARGETS=()
|
||||||
|
|
||||||
|
collect_phi() {
|
||||||
|
[ -d "$LARRY_HOME" ] || return 0
|
||||||
|
local f
|
||||||
|
for f in "$LARRY_HOME/log/auto-phi.log" "$LARRY_HOME/sanitize/lookup.tsv"; do
|
||||||
|
[ -f "$f" ] && PHI_TARGETS+=("$f")
|
||||||
|
done
|
||||||
|
if [ -d "$LARRY_HOME/sessions" ]; then
|
||||||
|
if command -v find >/dev/null 2>&1; then
|
||||||
|
while IFS= read -r f; do [ -n "$f" ] && PHI_TARGETS+=("$f"); done \
|
||||||
|
< <(find "$LARRY_HOME/sessions" -type f -name '*.log.md' 2>/dev/null)
|
||||||
|
else
|
||||||
|
# find absent (stripped MobaXterm): glob fallback so session PHI is never
|
||||||
|
# silently skipped. nullglob so a no-match doesn't add a literal pattern.
|
||||||
|
local _ng; _ng="$(shopt -p nullglob 2>/dev/null || true)"
|
||||||
|
shopt -s nullglob 2>/dev/null || true
|
||||||
|
for f in "$LARRY_HOME"/sessions/*.log.md "$LARRY_HOME"/sessions/**/*.log.md; do
|
||||||
|
[ -f "$f" ] && PHI_TARGETS+=("$f")
|
||||||
|
done
|
||||||
|
eval "$_ng" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
[ "$KEEP_DATA" = "0" ] && collect_phi
|
||||||
|
|
||||||
|
# Shims / copies. We only remove a `larry` file if it is OUR shim (auto-generated
|
||||||
|
# header) OR a symlink/launcher pointing into $LARRY_HOME.
|
||||||
|
is_our_shim() { # $1 = path
|
||||||
|
local p="$1"
|
||||||
|
[ -e "$p" ] || return 1
|
||||||
|
grep -q 'Auto-generated by install-larry.sh' "$p" 2>/dev/null && return 0
|
||||||
|
grep -q "$LARRY_HOME" "$p" 2>/dev/null && return 0
|
||||||
|
if [ -L "$p" ]; then
|
||||||
|
case "$(readlink "$p" 2>/dev/null)" in *"$LARRY_HOME"*) return 0 ;; esac
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
_seen_shims=""
|
||||||
|
for SHIM in "$LARRY_BIN_DIR/larry" \
|
||||||
|
"${HOME:-}/larry" "${HOME:-}/.local/bin/larry" "${HOME:-}/bin/larry"; do
|
||||||
|
[ -n "$SHIM" ] || continue
|
||||||
|
case "$_seen_shims" in *"|$SHIM|"*) continue ;; esac # dedupe (LARRY_BIN_DIR may == $HOME/bin)
|
||||||
|
_seen_shims="$_seen_shims|$SHIM|"
|
||||||
|
if is_our_shim "$SHIM"; then
|
||||||
PROGRAM_TARGETS+=("$SHIM")
|
PROGRAM_TARGETS+=("$SHIM")
|
||||||
elif [ -e "$SHIM" ]; then
|
elif [ -e "$SHIM" ]; then
|
||||||
warn "$SHIM exists but is NOT our auto-generated shim — leaving it untouched."
|
warn "$SHIM exists but is NOT our shim/launcher — leaving it untouched."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# A scp'd source folder (install-from-folder path).
|
||||||
|
if [ -n "${HOME:-}" ] && [ -d "$HOME/larry-anywhere" ]; then
|
||||||
|
PROGRAM_TARGETS+=("$HOME/larry-anywhere")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The entire install dir is ours. Rather than enumerate every dotfile, we list
|
# The install dir itself.
|
||||||
# $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 [ -d "$LARRY_HOME" ]; then
|
||||||
if [ "$KEEP_DATA" = "0" ]; then
|
if [ "$KEEP_DATA" = "0" ]; then
|
||||||
PROGRAM_TARGETS+=("$LARRY_HOME")
|
PROGRAM_TARGETS+=("$LARRY_HOME")
|
||||||
else
|
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 \
|
for p in \
|
||||||
larry.sh larry-tunnel.sh larry-rollback.sh larry-auth.sh uninstall-larry.sh \
|
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 \
|
install-larry.sh VERSION MANUAL.md MANIFEST CHANGELOG.md README.md \
|
||||||
@ -115,7 +263,6 @@ if [ -d "$LARRY_HOME" ]; then
|
|||||||
.phi-sidecar.pid tunnel.pid tunnel.url tunnel.log .history; do
|
.phi-sidecar.pid tunnel.pid tunnel.url tunnel.log .history; do
|
||||||
[ -e "$LARRY_HOME/$p" ] && PROGRAM_TARGETS+=("$LARRY_HOME/$p")
|
[ -e "$LARRY_HOME/$p" ] && PROGRAM_TARGETS+=("$LARRY_HOME/$p")
|
||||||
done
|
done
|
||||||
# User data preserved under --keep-data (listed for transparency).
|
|
||||||
for d in sessions journal lessons knowledge sanitize regression log \
|
for d in sessions journal lessons knowledge sanitize regression log \
|
||||||
.api-key .env .oauth.json .ssh-creds .ssh-sockets .ssh-hosts.tsv \
|
.api-key .env .oauth.json .ssh-creds .ssh-sockets .ssh-hosts.tsv \
|
||||||
known_hosts .headers-sync-offset .headers-sync-state; do
|
known_hosts .headers-sync-offset .headers-sync-state; do
|
||||||
@ -124,18 +271,34 @@ if [ -d "$LARRY_HOME" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${#PROGRAM_TARGETS[@]}" -eq 0 ]; then
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
say "nothing to remove — no Larry-Anywhere install footprint found."
|
# 5. Shell-profile credential lines. SAFE, PREDICTABLE behavior:
|
||||||
say " (looked for LARRY_HOME=$LARRY_HOME and shim $SHIM)"
|
# default = back up the rc (timestamped .larry-uninstall.bak) then STRIP the
|
||||||
exit 0
|
# matching lines; --keep-rc = print only, change nothing.
|
||||||
fi
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
RC_FILES=()
|
||||||
|
for rc in "${HOME:-}/.bashrc" "${HOME:-}/.bash_profile" "${HOME:-}/.profile"; do
|
||||||
|
[ -n "$rc" ] && [ -f "$rc" ] && RC_FILES+=("$rc")
|
||||||
|
done
|
||||||
|
CRED_RE='ANTHROPIC_API_KEY|CLAUDE_CODE_OAUTH_TOKEN|LARRY_[A-Z_]*|GITEA_TOKEN'
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Preview
|
# Preview
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
say "Larry-Anywhere uninstall — install footprint to remove:"
|
if [ "${#PROGRAM_TARGETS[@]}" -eq 0 ] && [ "${#PHI_TARGETS[@]}" -eq 0 ]; then
|
||||||
|
say "nothing to remove — no Larry-Anywhere install footprint found."
|
||||||
|
say " (looked for LARRY_HOME=$LARRY_HOME and shims under \$HOME)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "2/3) Files to remove (PHI files securely shredded first):"
|
||||||
echo ""
|
echo ""
|
||||||
printf '%sProgram files (always removed):%s\n' "$C_BOLD" "$C_RESET"
|
if [ "${#PHI_TARGETS[@]}" -gt 0 ]; then
|
||||||
|
printf '%sCleartext-PHI files (SECURE delete):%s\n' "$C_BOLD" "$C_RESET"
|
||||||
|
for t in "${PHI_TARGETS[@]}"; do printf ' %sshred%s %s\n' "$C_RED" "$C_RESET" "$t"; done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
printf '%sProgram/footprint (removed):%s\n' "$C_BOLD" "$C_RESET"
|
||||||
|
if [ "${#PROGRAM_TARGETS[@]}" -eq 0 ]; then printf ' %s(none)%s\n' "$C_DIM" "$C_RESET"; fi
|
||||||
for t in "${PROGRAM_TARGETS[@]}"; do
|
for t in "${PROGRAM_TARGETS[@]}"; do
|
||||||
if [ -d "$t" ]; then printf ' %s(dir) %s%s\n' "$C_DIM" "$t" "$C_RESET"
|
if [ -d "$t" ]; then printf ' %s(dir) %s%s\n' "$C_DIM" "$t" "$C_RESET"
|
||||||
else printf ' %s\n' "$t"; fi
|
else printf ' %s\n' "$t"; fi
|
||||||
@ -143,64 +306,218 @@ done
|
|||||||
if [ "$KEEP_DATA" = "1" ]; then
|
if [ "$KEEP_DATA" = "1" ]; then
|
||||||
echo ""
|
echo ""
|
||||||
printf '%sUser data PRESERVED (--keep-data):%s\n' "$C_BOLD" "$C_RESET"
|
printf '%sUser data PRESERVED (--keep-data):%s\n' "$C_BOLD" "$C_RESET"
|
||||||
if [ "${#DATA_TARGETS[@]}" -eq 0 ]; then
|
if [ "${#DATA_TARGETS[@]}" -eq 0 ]; then printf ' %s(none found)%s\n' "$C_DIM" "$C_RESET"
|
||||||
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
|
||||||
else
|
|
||||||
for d in "${DATA_TARGETS[@]}"; do printf ' %skept%s %s\n' "$C_GREEN" "$C_RESET" "$d"; done
|
|
||||||
fi
|
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).
|
# Secure-delete capability honesty (esp. Windows/MobaXterm).
|
||||||
stop_bg_proc "PHI Presidio sidecar" "$LARRY_HOME/.phi-sidecar.pid"
|
echo ""
|
||||||
stop_bg_proc "reverse SSH tunnel" "$LARRY_HOME/tunnel.pid"
|
if [ "$NO_SHRED" = "1" ]; then
|
||||||
|
warn "secure-delete DISABLED (--no-shred): PHI files will be removed with plain rm."
|
||||||
|
elif [ "$HAVE_SHRED" = "1" ]; then
|
||||||
|
say "secure-delete: 'shred -u' available -> PHI files will be cryptographically shredded."
|
||||||
|
else
|
||||||
|
say "secure-delete: 'shred' NOT found on this platform ($PLATFORM) -> falling back to"
|
||||||
|
say " best-effort overwrite-then-remove. NOTE: on Windows/MobaXterm and on copy-on-write"
|
||||||
|
say " or SSD/flash filesystems, overwrite-in-place does NOT guarantee the original blocks"
|
||||||
|
say " are gone. This will be reported per-file as 'overwrite (best-effort)'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Shell rc preview
|
||||||
|
echo ""
|
||||||
|
say "5) Shell-profile credential lines:"
|
||||||
|
RC_HITS=0
|
||||||
|
for rc in "${RC_FILES[@]:-}"; do
|
||||||
|
[ -n "$rc" ] || continue
|
||||||
|
hits="$(grep -nE "$CRED_RE" "$rc" 2>/dev/null || true)"
|
||||||
|
if [ -n "$hits" ]; then
|
||||||
|
RC_HITS=1
|
||||||
|
printf ' %s%s%s:\n' "$C_BOLD" "$rc" "$C_RESET"
|
||||||
|
printf '%s\n' "$hits" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$RC_HITS" = "0" ]; then
|
||||||
|
printf ' %s(no Larry credential exports found in .bashrc/.bash_profile/.profile)%s\n' "$C_DIM" "$C_RESET"
|
||||||
|
elif [ "$KEEP_RC" = "1" ]; then
|
||||||
|
say " --keep-rc: the lines above will be PRINTED ONLY — remove them by hand."
|
||||||
|
else
|
||||||
|
say " default: a timestamped backup will be written, then these lines auto-stripped."
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ "$DRY_RUN" = "1" ]; then
|
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."
|
say "${C_BOLD}DRY-RUN${C_RESET} — nothing was changed. Re-run with ${C_CYAN}--yes${C_RESET} to execute the above."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Confirm (skipped with --yes... but --yes already set ASSUME_YES; this is the
|
# Confirm
|
||||||
# belt-and-suspenders path if someone sets DRY_RUN=0 by other means).
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
if [ "$ASSUME_YES" != "1" ]; then
|
if [ "$ASSUME_YES" != "1" ]; then
|
||||||
printf '%sProceed with removal? This cannot be undone. [y/N]: %s' "$C_YELLOW" "$C_RESET"
|
printf '%sProceed with uninstall? This cannot be undone. [y/N]: %s' "$C_YELLOW" "$C_RESET"
|
||||||
ans=""
|
ans=""
|
||||||
if [ -t 0 ]; then read -r ans </dev/tty || ans=""; else read -r ans || ans=""; fi
|
if [ -t 0 ]; then read -r ans </dev/tty || ans=""; else read -r ans || ans=""; fi
|
||||||
case "$ans" in
|
case "$ans" in y|Y|yes|YES) : ;; *) say "aborted — nothing changed."; exit 0 ;; esac
|
||||||
y|Y|yes|YES) : ;;
|
fi
|
||||||
*) say "aborted — nothing removed."; exit 0 ;;
|
echo ""
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 3. SECURE-DELETE the cleartext-PHI files FIRST, with honest per-file reporting.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
secure_delete() { # $1 = file. echoes the method actually achieved.
|
||||||
|
local f="$1"
|
||||||
|
[ -f "$f" ] || { echo "absent"; return 0; }
|
||||||
|
if [ "$NO_SHRED" = "1" ]; then rm -f "$f" 2>/dev/null && echo "plain-rm" || echo "FAILED"; return 0; fi
|
||||||
|
if [ "$HAVE_SHRED" = "1" ]; then
|
||||||
|
if shred -u -z -n 3 "$f" 2>/dev/null; then echo "shred"; return 0; fi
|
||||||
|
if shred -u "$f" 2>/dev/null; then echo "shred"; return 0; fi
|
||||||
|
fi
|
||||||
|
# Fallback: overwrite-in-place then remove. Best-effort on Windows/CoW/SSD.
|
||||||
|
local sz
|
||||||
|
sz="$(wc -c < "$f" 2>/dev/null || echo 0)"
|
||||||
|
if [ "${sz:-0}" -gt 0 ] 2>/dev/null; then
|
||||||
|
if command -v dd >/dev/null 2>&1; then
|
||||||
|
dd if=/dev/zero of="$f" bs=1 count="$sz" conv=notrunc 2>/dev/null || true
|
||||||
|
[ -r /dev/urandom ] && dd if=/dev/urandom of="$f" bs=1 count="$sz" conv=notrunc 2>/dev/null || true
|
||||||
|
else
|
||||||
|
: > "$f" 2>/dev/null || true # truncate at least
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$f" 2>/dev/null && echo "overwrite" || echo "FAILED"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${#PHI_TARGETS[@]}" -gt 0 ]; then
|
||||||
|
step "Securely deleting cleartext-PHI files"
|
||||||
|
phi_shredded=0; phi_besteffort=0; phi_failed=0
|
||||||
|
for f in "${PHI_TARGETS[@]}"; do
|
||||||
|
method="$(secure_delete "$f")"
|
||||||
|
case "$method" in
|
||||||
|
shred) ok "shredded (secure): $f"; phi_shredded=$((phi_shredded+1)) ;;
|
||||||
|
overwrite) warn "overwrite-then-rm (best-effort, NOT guaranteed on this FS): $f"; phi_besteffort=$((phi_besteffort+1)) ;;
|
||||||
|
plain-rm) warn "plain rm (--no-shred): $f"; phi_besteffort=$((phi_besteffort+1)) ;;
|
||||||
|
absent) : ;;
|
||||||
|
*) warn "FAILED to remove PHI file (remove by hand!): $f"; phi_failed=$((phi_failed+1)) ;;
|
||||||
esac
|
esac
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
if [ "$HAVE_SHRED" = "1" ] && [ "$NO_SHRED" = "0" ]; then
|
||||||
|
say "PHI secure-delete: $phi_shredded shredded, $phi_besteffort best-effort, $phi_failed failed."
|
||||||
|
else
|
||||||
|
say "PHI delete on $PLATFORM: secure shred UNAVAILABLE — $phi_besteffort file(s) removed best-effort,"
|
||||||
|
say " $phi_failed failed. Treat the underlying disk as potentially still containing PHI remnants;"
|
||||||
|
say " follow your data-handling policy for media sanitization if required."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Remove. Print every path as it goes. rm -rf is scoped strictly to the list we
|
# 2/4. Remove the rest of the footprint (program files, shims, copies, the dir).
|
||||||
# built above — we never expand globs or touch a parent dir.
|
# Scoped strictly to the built list — no glob expansion, no parent touch.
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
removed=0
|
removed=0; failed=0
|
||||||
|
if [ "${#PROGRAM_TARGETS[@]}" -gt 0 ]; then
|
||||||
|
step "Removing program files, shims and the install dir"
|
||||||
for t in "${PROGRAM_TARGETS[@]}"; do
|
for t in "${PROGRAM_TARGETS[@]}"; do
|
||||||
if rm -rf "$t" 2>/dev/null; then
|
if rm -rf "$t" 2>/dev/null; then ok "removed $t"; removed=$((removed+1))
|
||||||
ok "removed $t"
|
else warn "could not remove $t (check permissions; remove by hand)"; failed=$((failed+1)); fi
|
||||||
removed=$((removed+1))
|
done
|
||||||
|
say "removed $removed item(s); $failed failed."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 5. Strip Larry credential lines from shell rc (default), with backup. Skipped
|
||||||
|
# under --keep-rc (already printed for manual removal in the preview).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
if [ "$KEEP_RC" = "0" ] && [ "${#RC_FILES[@]}" -gt 0 ]; then
|
||||||
|
step "Stripping Larry credential exports from shell rc (backup first)"
|
||||||
|
ts="$(date +%Y%m%d-%H%M%S 2>/dev/null || echo bak)"
|
||||||
|
for rc in "${RC_FILES[@]}"; do
|
||||||
|
if grep -qE "$CRED_RE" "$rc" 2>/dev/null; then
|
||||||
|
cp -p "$rc" "$rc.larry-uninstall.$ts.bak" 2>/dev/null \
|
||||||
|
&& ok "backed up $rc -> $rc.larry-uninstall.$ts.bak" \
|
||||||
|
|| warn "could not back up $rc — leaving it UNCHANGED for safety"
|
||||||
|
if [ -f "$rc.larry-uninstall.$ts.bak" ]; then
|
||||||
|
if grep -vE "$CRED_RE" "$rc" > "$rc.larry-tmp" 2>/dev/null && mv "$rc.larry-tmp" "$rc" 2>/dev/null; then
|
||||||
|
ok "stripped Larry credential lines from $rc"
|
||||||
else
|
else
|
||||||
warn "could not remove $t (check permissions; remove by hand)"
|
rm -f "$rc.larry-tmp" 2>/dev/null || true
|
||||||
|
warn "could not rewrite $rc — remove the credential lines by hand"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
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
|
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
|
# 6. SELF-UNINSTALL LAST. If this script ran from a separate larry-anywhere
|
||||||
*":$LARRY_BIN_DIR:"*)
|
# checkout (not inside $LARRY_HOME, which is already gone), remove that too —
|
||||||
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." ;;
|
# but ONLY if we actually performed an uninstall this run AND the dir holds a
|
||||||
|
# full bundle. Then remove this script file itself.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
step "Self-uninstall (removing the larry-anywhere install dir / this script)"
|
||||||
|
DID_WORK=0
|
||||||
|
[ "$removed" -gt 0 ] && DID_WORK=1
|
||||||
|
[ "${#PHI_TARGETS[@]}" -gt 0 ] && DID_WORK=1
|
||||||
|
if [ -n "$SELF_DIR" ] && [ -d "$SELF_DIR" ]; then
|
||||||
|
case "$(_norm "$SELF_DIR")" in
|
||||||
|
"$_LH_NORM") : ;; # was inside LARRY_HOME, already removed above
|
||||||
|
*/larry-anywhere|*cloverleaf-larry*|*/.larry)
|
||||||
|
if [ "$DID_WORK" = "1" ] && { [ -f "$SELF_DIR/larry.sh" ] || [ -f "$SELF_DIR/install-larry.sh" ]; }; then
|
||||||
|
rm -rf "$SELF_DIR" 2>/dev/null && ok "removed self checkout: $SELF_DIR" \
|
||||||
|
|| warn "could not remove $SELF_DIR — remove by hand"
|
||||||
|
else
|
||||||
|
warn "left $SELF_DIR in place (nothing uninstalled this run, or not a full bundle)."
|
||||||
|
fi ;;
|
||||||
|
*) warn "running from $SELF_DIR — not a recognized Larry dir, NOT self-removing it." ;;
|
||||||
esac
|
esac
|
||||||
say "uninstall complete."
|
fi
|
||||||
|
# Remove this script file itself if it still exists.
|
||||||
|
if [ -f "$0" ]; then rm -f "$0" 2>/dev/null && ok "removed $0" || true; fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# FINAL "confirm gone" check
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
step "Confirming removal"
|
||||||
|
gone=1
|
||||||
|
if [ -e "$LARRY_HOME" ] && [ "$KEEP_DATA" = "0" ]; then warn "$LARRY_HOME still exists"; gone=0; else ok "$LARRY_HOME removed"; fi
|
||||||
|
if command -v larry >/dev/null 2>&1; then
|
||||||
|
warn "a 'larry' command is still on PATH ($(command -v larry)) — if it isn't ours, that's fine"
|
||||||
|
else
|
||||||
|
ok "no 'larry' command on PATH"
|
||||||
|
fi
|
||||||
|
[ "$gone" = "1" ] && say "${C_GREEN}uninstall complete — local footprint gone.${C_RESET}" \
|
||||||
|
|| say "${C_YELLOW}uninstall finished with leftovers — see warnings above.${C_RESET}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# 7. POST-UNINSTALL REMINDER — non-negotiable for a healthcare deployment.
|
||||||
|
# Local removal does NOT invalidate credentials that may have egressed.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
printf '%s%s================ IMPORTANT: FINISH AT THE SOURCE ================%s\n' "$C_YELLOW" "$C_BOLD" "$C_RESET"
|
||||||
|
cat <<'EOF'
|
||||||
|
Removing the files here does NOT revoke the credentials. Any copy that already
|
||||||
|
egressed (e.g. exported in another shell, scp'd, or cached elsewhere) remains
|
||||||
|
live until you revoke it AT ANTHROPIC. Do BOTH now:
|
||||||
|
|
||||||
|
1. Anthropic Console -> Settings -> API Keys:
|
||||||
|
DELETE/REVOKE the API key minted for this machine (do not just disable).
|
||||||
|
https://console.anthropic.com
|
||||||
|
|
||||||
|
2. Anthropic Console -> account security / Connected apps / sessions:
|
||||||
|
REVOKE the Claude-Code OAuth authorization grant and "sign out everywhere"
|
||||||
|
(a .oauth.json refresh token can still mint access tokens until revoked).
|
||||||
|
|
||||||
|
3. Revoke any Gitea PAT (LARRY_GITEA_TOKEN / GITEA_TOKEN) that was set, and
|
||||||
|
rotate any SSH credential this box held to your Mac / hop host.
|
||||||
|
|
||||||
|
HEALTHCARE / PHI / BAA reminder:
|
||||||
|
This agent ran on an HL7 integration engine and had a path to PHI. It may have
|
||||||
|
transmitted PHI to a third party (Anthropic) and stored cleartext PHI locally
|
||||||
|
(now deleted). Review the engagement BAA and, absent clear evidence no PHI
|
||||||
|
ever flowed, treat client notification / PHI-disclosure as a decision you must
|
||||||
|
make deliberately — do not assume local deletion closes the obligation.
|
||||||
|
EOF
|
||||||
|
printf '%s%s=================================================================%s\n' "$C_YELLOW" "$C_BOLD" "$C_RESET"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user