#!/usr/bin/env sh # marbor uninstaller # Usage: sh uninstall.sh (run from the directory install.sh was run in, so # it can find marbor.db / the pidfile) # # Env vars: # INSTALL_DIR binary location to remove (default /usr/local/bin, must # match whatever install.sh used) # KEEP_DB=1 keep marbor.db without prompting # KEEP_DB=0 remove marbor.db without prompting # # When run non-interactively (e.g. piped via curl, where stdin isn't a # terminal) marbor.db is kept by default unless the env var above says # otherwise - an uninstall should never silently destroy data. set -e INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" BIN_NAME="marbor" BIN_PATH="$INSTALL_DIR/$BIN_NAME" WORKDIR="$(pwd)" PIDFILE="$WORKDIR/marbor.pid" UNIT_PATH="/etc/systemd/system/marbor.service" DB_FILE="$WORKDIR/marbor.db" LOG_FILE="$WORKDIR/marbor.log" echo "marbor uninstaller" echo "------------------------" # 1. Remove the systemd service, if one was installed. if [ -f "$UNIT_PATH" ]; then if command -v systemctl >/dev/null 2>&1; then echo "Removing systemd service ($UNIT_PATH)..." if [ "$(id -u)" = "0" ]; then systemctl stop marbor >/dev/null 2>&1 || true systemctl disable marbor >/dev/null 2>&1 || true rm -f "$UNIT_PATH" systemctl daemon-reload || true elif command -v sudo >/dev/null 2>&1; then sudo systemctl stop marbor >/dev/null 2>&1 || true sudo systemctl disable marbor >/dev/null 2>&1 || true sudo rm -f "$UNIT_PATH" sudo systemctl daemon-reload || true else echo " [!] Removing $UNIT_PATH requires root, and sudo is not available. Remove it manually." fi else echo " [!] Found $UNIT_PATH but systemctl is unavailable; remove the unit file manually." fi fi # 2. Stop a plain background (nohup) instance, if one is tracked. if [ -f "$PIDFILE" ]; then PID=$(cat "$PIDFILE" 2>/dev/null || true) if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then # PID-reuse hazard: on a long-lived host, the PID this pidfile named can # have since been recycled by the OS for an unrelated process. Check # /proc//cmdline (Linux only - no /proc at all on macOS, where this # check is skipped and the kill proceeds unconditionally, same as before # this fix) before signaling anything. Fail CLOSED, not open: if /proc # exists on this host but this PID's cmdline can't be read (e.g. a # hidepid=2 mount restricting /proc//* to its own user + root - the # exact privilege boundary this check exists to respect), that must NOT # be treated the same as "no /proc at all" and default to "confirmed" - # it means identity genuinely can't be verified, so skip the kill. if [ -d /proc ]; then IDENTITY_OK=0 if [ -r "/proc/$PID/cmdline" ] && tr '\0' '\n' < "/proc/$PID/cmdline" 2>/dev/null | head -1 | sed 's#.*/##' | grep -q marbor; then IDENTITY_OK=1 fi else IDENTITY_OK=1 fi if [ "$IDENTITY_OK" = "1" ]; then echo "Stopping background process (PID $PID)..." kill "$PID" 2>/dev/null || true i=0 while [ "$i" -lt 10 ] && kill -0 "$PID" 2>/dev/null; do sleep 0.5 i=$((i + 1)) done if kill -0 "$PID" 2>/dev/null; then echo " [!] PID $PID did not exit within 5s of SIGTERM - sending SIGKILL" kill -9 "$PID" 2>/dev/null || true fi else echo " [!] $PIDFILE names PID $PID, but its identity could not be confirmed as marbor (stale pidfile + PID reuse?) - skipping kill to avoid killing an unrelated process." fi fi rm -f "$PIDFILE" fi # 3. Remove the binary. if [ -f "$BIN_PATH" ]; then if [ -w "$INSTALL_DIR" ]; then rm -f "$BIN_PATH" echo "Removed binary: $BIN_PATH" elif command -v sudo >/dev/null 2>&1; then sudo rm -f "$BIN_PATH" echo "Removed binary: $BIN_PATH" else echo " [!] Cannot remove $BIN_PATH (no write permission, no sudo). Remove it manually." fi else echo "No binary found at $BIN_PATH (already removed, or INSTALL_DIR differs from install)." fi # 3b. Remove man pages installed by install.sh (docs/man/*.1, fetched from # the release into /usr/local/share/man/man1 - see install.sh's # install_man_pages). Same write-permission/sudo fallback as the binary # above; a missing man dir or missing pages is not an error, just a no-op. # # install_man_pages drops a # local marker file, .marbor-installed-manifest, listing exactly which # filenames IT placed in MAN_DIR - uninstall.sh has no source tree and no # release manifest of its own, so that marker is the only place this exact # list is ever recorded. Prefer it when present (removes exactly what a # Fix-4-or-later install created); the wildcard below survives only as a # fallback for an install that predates this fix and never wrote a marker. MAN_DIR="/usr/local/share/man/man1" MAN_MANIFEST="$MAN_DIR/.marbor-installed-manifest" # is_safe_manpage_name rejects anything but a plain filename (no path # separators, no leading dash that could be misread as an rm option, only # alnum/dot/dash/underscore) - the manifest is currently only ever written # by install.sh's own fixed-name man-page install step, but this validates # it as untrusted content anyway rather than assuming that always holds. is_safe_manpage_name() { case "$1" in ""|*/*|-*) return 1 ;; *[!A-Za-z0-9._-]*) return 1 ;; *) return 0 ;; esac } if [ -f "$MAN_MANIFEST" ]; then MAN_PAGES_TO_REMOVE="$(cat "$MAN_MANIFEST" 2>/dev/null || true)" if [ -n "$MAN_PAGES_TO_REMOVE" ]; then if [ -w "$MAN_DIR" ]; then for page in $MAN_PAGES_TO_REMOVE; do if is_safe_manpage_name "$page"; then rm -f "$MAN_DIR/$page" else echo " [!] Skipping suspicious manifest entry: $page" fi done rm -f "$MAN_MANIFEST" echo "Removed man pages listed in $MAN_MANIFEST" elif command -v sudo >/dev/null 2>&1; then for page in $MAN_PAGES_TO_REMOVE; do if is_safe_manpage_name "$page"; then sudo rm -f "$MAN_DIR/$page" else echo " [!] Skipping suspicious manifest entry: $page" fi done sudo rm -f "$MAN_MANIFEST" echo "Removed man pages listed in $MAN_MANIFEST" else echo " [!] Cannot remove man pages in $MAN_DIR (no write permission, no sudo). Remove them manually." fi fi elif ls "$MAN_DIR"/marbor*.1 >/dev/null 2>&1; then if [ -w "$MAN_DIR" ]; then rm -f "$MAN_DIR"/marbor*.1 echo "Removed man pages: $MAN_DIR/marbor*.1" elif command -v sudo >/dev/null 2>&1; then sudo rm -f "$MAN_DIR"/marbor*.1 echo "Removed man pages: $MAN_DIR/marbor*.1" else echo " [!] Cannot remove man pages in $MAN_DIR (no write permission, no sudo). Remove them manually." fi fi # 4. marbor.db holds real state (nodes, API keys, warm-state history) - ask # before deleting, and default to keeping it when not on a terminal. ask_keep() { # ask_keep