#!/usr/bin/env bash

: <<'END'
dhremove
DigiHub Uninstaller

Version 0.2

Steve de Bode - W0FFS - July 2026

Input:  interactive by default
        -y, --yes                  assume yes for removal prompt
        --no-reboot                do not prompt for reboot
        --keep-packages, --no-packages
                                   do not remove packages listed in ~/.dhinstalled
        --keep-profile, --no-profile
                                   do not restore or modify ~/.profile
        --keep-dhinfo, --no-dhinfo do not remove ~/.dhinfo or ~/.hamdb.cnf files
        --keep-venv, --no-venv     do not remove ~/.digihub-venv
        --keep-files, --no-files   do not remove files listed in /usr/local/bin/.digihub.manifest,
                                   or the separately-tracked ardopcf/ttyd binaries
        --keep-service, --no-service
                                   do not stop or remove the GPS monitor, DigiHub web
                                   interface, Direwolf, CAT control, Winlink (Pat),
                                   ARDOP TNC, AX.25 node/BBS, APRS WebChat, web
                                   console, WSJT-X/JS8Call/qSSTV VNC, HamDB update
                                   timer, or DigiHub update check timer systemd
                                   services
        --files-only               remove only DigiHub files listed in /usr/local/bin/.digihub.manifest
        -h, --help                 show this help text
Output: interactive by default
END

set -euo pipefail

if [[ -n "${SUDO_USER:-}" && "$SUDO_USER" != "root" ]]; then
  HomePath="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
else
  HomePath="${HOME}"
fi

venv_dir="${HomePath}/.digihub-venv"
MANIFEST="/usr/local/bin/.digihub.manifest"
LIBDIR="/usr/local/lib/digihub"
GPSMONITOR_ENV_DIR="/etc/digihub"
WebEnvFile="/etc/digihub/web.env"
DirewolfConfFile="/etc/digihub/direwolf.conf"
RigctldEnvFile="/etc/digihub/rigctld.env"
WebchatEnvFile="/etc/digihub/webchat.env"
TigervncConfigDir="${HomePath}/.config/tigervnc"
SudoersFile="/etc/sudoers.d/digihub-dhmode"

ASSUME_YES=0
PROMPT_REBOOT=1
REMOVE_PACKAGES=1
REMOVE_PROFILE=1
REMOVE_DHINFO=1
REMOVE_VENV=1
REMOVE_MANIFEST_FILES=1
REMOVE_SERVICE=1

usage() {
  sed -n '/^: <<'\''END'\''/,/^END$/p' "$0" | sed '1d;$d'
}

run_sudo() {
  if [[ "${EUID}" -eq 0 ]]; then
    "$@"
  else
    sudo "$@"
  fi
}

confirm_remove() {
  local response=""

  if (( ASSUME_YES == 1 )); then
    printf '\nRemoving DigiHub.\n'
    return 0
  fi

  while true; do
    printf '\nThis action will completely remove DigiHub, continue (Y/n)? '
    read -n1 -r response </dev/tty
    case "$response" in
      Y|y)
        printf '\nRemoving DigiHub.\n'
        return 0
        ;;
      N|n)
        printf '\nUninstall aborted.\n\n'
        exit 0
        ;;
      *)
        printf '\nInvalid response, please select Y/n.\n'
        ;;
    esac
  done
}

restore_profile() {
  (( REMOVE_PROFILE == 1 )) || return 0

  if [[ -f "$HomePath/.profile.dh" ]]; then
    printf 'Restoring original profile: %s/.profile\n' "$HomePath"
    mv -f -- "$HomePath/.profile.dh" "$HomePath/.profile" >/dev/null 2>&1 || true
  elif [[ -f "$HomePath/.profile" ]]; then
    printf 'Cleaning DigiHub entries from profile: %s/.profile\n' "$HomePath"
    local tmp
    tmp="$HomePath/.profile.tmp.$$"
    grep -vF -e 'DigiHub' -e 'sysinfo' "$HomePath/.profile" > "$tmp" 2>/dev/null || true
    mv -f -- "$tmp" "$HomePath/.profile" >/dev/null 2>&1 || true
  fi

  if [[ -f "$HomePath/.profile" ]]; then
    perl -i.bak -0777 -pe 's{\s+\z}{}m' "$HomePath/.profile" >/dev/null 2>&1 || true
    printf '\n' >> "$HomePath/.profile" 2>/dev/null || true
    rm -f -- "$HomePath/.profile.bak"* >/dev/null 2>&1 || true
  fi
}

remove_manifest_files() {
  (( REMOVE_MANIFEST_FILES == 1 )) || return 0

  local man_pages_removed=0

  if [[ -r "$MANIFEST" ]]; then
    printf 'Removing files listed in %s...\n' "$MANIFEST"

    while IFS= read -r p; do
      [[ -n "${p//[[:space:]]/}" ]] || continue

      case "$p" in
        /usr/local/bin/*|/usr/local/lib/digihub/*|/usr/local/share/man/man1/*) ;;
        *) continue ;;
      esac

      [[ "$p" != "/usr/local/bin" && "$p" != "/usr/local/lib/digihub" && "$p" != "/usr/local/share/man/man1" ]] || continue
      [[ "$p" != *"*"* && "$p" != *"?"* && "$p" != *"["* ]] || continue

      if run_sudo test -f "$p" || run_sudo test -L "$p"; then
        printf 'Removing file: %s\n' "$p"
        run_sudo rm -f -- "$p" || true
        case "$p" in
          /usr/local/share/man/man1/*) man_pages_removed=1 ;;
        esac
      fi
    done < "$MANIFEST"

    printf 'Removing manifest: %s\n' "$MANIFEST"
    run_sudo rm -f -- "$MANIFEST" || true
  else
    printf 'Manifest not found or not readable: %s\n' "$MANIFEST"
  fi

  if run_sudo test -d /usr/local/bin/__pycache__; then
    printf 'Removing Python cache: /usr/local/bin/__pycache__\n'
    run_sudo rm -rf -- /usr/local/bin/__pycache__ || true
  fi

  if run_sudo test -d "$LIBDIR"; then
    printf 'Removing empty DigiHub library subdirectories...\n'
    run_sudo find "$LIBDIR" -mindepth 1 -depth -type d -empty -exec rmdir {} \; >/dev/null 2>&1 || true
    printf 'Removing DigiHub library directory if empty: %s\n' "$LIBDIR"
    run_sudo rmdir --ignore-fail-on-non-empty "$LIBDIR" || true
  fi

  if (( man_pages_removed == 1 )) && command -v mandb >/dev/null 2>&1; then
    run_sudo mandb -q >/dev/null 2>&1 || true
  fi
}

# ardopcf is a single binary fetched directly from PlotLost at
# install time (see install.sh): not a .deb (so not in ~/.dhinstalled)
# and not part of this repo (so not in the DigiHub file manifest, where
# dhupdate would otherwise treat it as "no longer upstream" and delete
# it again on the next update). Removed here directly instead.
remove_ardopcf_binary() {
  (( REMOVE_MANIFEST_FILES == 1 )) || return 0

  if run_sudo test -f /usr/local/bin/ardopcf; then
    printf 'Removing ardopcf: /usr/local/bin/ardopcf\n'
    run_sudo rm -f -- /usr/local/bin/ardopcf || true
  fi
}

# ttyd is a single binary fetched directly from PlotLost at
# install time (see install.sh), same reasoning as ardopcf above: not
# a .deb and not part of this repo, so removed here directly instead
# of through the manifest.
remove_ttyd_binary() {
  (( REMOVE_MANIFEST_FILES == 1 )) || return 0

  if run_sudo test -f /usr/local/bin/ttyd; then
    printf 'Removing ttyd: /usr/local/bin/ttyd\n'
    run_sudo rm -f -- /usr/local/bin/ttyd || true
  fi
}

# Stop, disable, and remove a systemd unit (and any companion unit files,
# e.g. a .timer's .service) installed by DigiHub.
# Usage: remove_systemd_unit "<label>" <primary-unit-file> [companion-unit-file ...]
remove_systemd_unit() {
  local label="$1" primary="$2"
  shift 2
  local files=("$primary" "$@") f existing=0

  if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files "$primary" >/dev/null 2>&1; then
    printf 'Stopping %s...\n' "$label"
    run_sudo systemctl disable --now "$primary" >/dev/null 2>&1 || true
  fi

  for f in "${files[@]}"; do
    [[ -f "/etc/systemd/system/$f" ]] && existing=1
  done

  if (( existing == 1 )); then
    printf 'Removing %s unit files...\n' "$label"
    for f in "${files[@]}"; do
      run_sudo rm -f -- "/etc/systemd/system/$f" || true
    done
    run_sudo systemctl daemon-reload >/dev/null 2>&1 || true
  fi
}

remove_gps_monitor() {
  (( REMOVE_SERVICE == 1 )) || return 0

  remove_systemd_unit "GPS monitor service" "dhgpsmonitor.service"

  if [[ -f "$GPSMONITOR_ENV_DIR/gpsmonitor.env" ]]; then
    printf 'Removing GPS monitor configuration: %s/gpsmonitor.env\n' "$GPSMONITOR_ENV_DIR"
    run_sudo rm -f -- "$GPSMONITOR_ENV_DIR/gpsmonitor.env" || true
  fi
}

remove_dhweb_service() {
  (( REMOVE_SERVICE == 1 )) || return 0

  remove_systemd_unit "DigiHub web interface" "dhweb.service"

  if [[ -f "$WebEnvFile" ]]; then
    printf 'Removing DigiHub web interface configuration: %s\n' "$WebEnvFile"
    run_sudo rm -f -- "$WebEnvFile" || true
  fi

  if [[ -f "$SudoersFile" ]]; then
    printf 'Removing DigiHub web interface sudoers rule: %s\n' "$SudoersFile"
    run_sudo rm -f -- "$SudoersFile" || true
  fi
}

remove_direwolf_service() {
  (( REMOVE_SERVICE == 1 )) || return 0

  remove_systemd_unit "Direwolf service" "dhdirewolf.service"

  if [[ -f "$DirewolfConfFile" ]]; then
    printf 'Removing Direwolf configuration: %s\n' "$DirewolfConfFile"
    run_sudo rm -f -- "$DirewolfConfFile" || true
  fi
}

remove_rigctld_service() {
  (( REMOVE_SERVICE == 1 )) || return 0

  remove_systemd_unit "CAT control service" "dhrigctld.service"

  if [[ -f "$RigctldEnvFile" ]]; then
    printf 'Removing CAT control configuration: %s\n' "$RigctldEnvFile"
    run_sudo rm -f -- "$RigctldEnvFile" || true
  fi
}

remove_pat_service() {
  (( REMOVE_SERVICE == 1 )) || return 0

  # Deliberately does NOT touch $HOME/.config/pat: that is the user's
  # actual Winlink client state (messages, address book, connect
  # aliases), not something DigiHub generated, and must survive removal.
  remove_systemd_unit "Winlink (Pat) service" "dhpat.service"
}

remove_ardop_service() {
  (( REMOVE_SERVICE == 1 )) || return 0
  remove_systemd_unit "ARDOP TNC service" "dhardop.service"
}

remove_webchat_service() {
  (( REMOVE_SERVICE == 1 )) || return 0

  remove_systemd_unit "APRS WebChat service" "dhwebchat.service"

  if [[ -f "$WebchatEnvFile" ]]; then
    printf 'Removing APRS WebChat configuration: %s\n' "$WebchatEnvFile"
    run_sudo rm -f -- "$WebchatEnvFile" || true
  fi
}

remove_console_service() {
  (( REMOVE_SERVICE == 1 )) || return 0
  remove_systemd_unit "Web console service" "dhconsole.service"
}

remove_vnc_service() {
  local label="$1" unit="$2"
  (( REMOVE_SERVICE == 1 )) || return 0
  remove_systemd_unit "$label" "$unit"
}

remove_wsjtx_service() {
  remove_vnc_service "WSJT-X VNC service" "dhwsjtx.service"
}

remove_js8call_service() {
  remove_vnc_service "JS8Call VNC service" "dhjs8call.service"
}

remove_qsstv_service() {
  remove_vnc_service "qSSTV VNC service" "dhqsstv.service"
}

# xstartup/passwd are DigiHub-generated (install.sh); other files
# tigervncserver may create under this directory at runtime (logs,
# auto-generated TLS certs) are left alone, same reasoning as not
# touching $HOME/.config/aprsd or $HOME/.config/pat wholesale.
remove_vnc_config() {
  (( REMOVE_SERVICE == 1 )) || return 0

  local f
  for f in "$TigervncConfigDir/xstartup" "$TigervncConfigDir/passwd"; do
    if [[ -f "$f" ]]; then
      printf 'Removing VNC configuration: %s\n' "$f"
      rm -f -- "$f" || true
    fi
  done
}

remove_node_service() {
  (( REMOVE_SERVICE == 1 )) || return 0

  remove_systemd_unit "AX.25 node/BBS service" "dhnode.service"

  # axports, ax25d.conf, and uronode.perms are entirely DigiHub-generated
  # (see nodeconf.py); safe to remove outright. uronode.conf is only
  # patched (HostName/NodeId/FlexID) on top of the uronode package's own
  # shipped defaults, so it's left for the package itself to manage.
  local f
  for f in /etc/ax25/axports /etc/ax25/ax25d.conf /etc/ax25/uronode.perms; do
    if [[ -f "$f" ]]; then
      printf 'Removing AX.25 node configuration: %s\n' "$f"
      run_sudo rm -f -- "$f" || true
    fi
  done
}

remove_hamdb_timer() {
  (( REMOVE_SERVICE == 1 )) || return 0
  remove_systemd_unit "HamDB update timer" "dhhamdbupdate.timer" "dhhamdbupdate.service"
}

remove_update_check_timer() {
  (( REMOVE_SERVICE == 1 )) || return 0
  remove_systemd_unit "DigiHub update check timer" "dhupdatecheck.timer" "dhupdatecheck.service"
}

# /etc/digihub is shared by the GPS monitor and web interface configs;
# remove it only once both have removed their own files, and only if empty.
remove_digihub_etc_dir() {
  (( REMOVE_SERVICE == 1 )) || return 0
  if run_sudo test -d "$GPSMONITOR_ENV_DIR"; then
    run_sudo rmdir --ignore-fail-on-non-empty "$GPSMONITOR_ENV_DIR" || true
  fi
}

remove_packages() {
  (( REMOVE_PACKAGES == 1 )) || return 0

  if [[ ! -f "$HomePath/.dhinstalled" ]]; then
    printf 'Warning: %s not found; no DigiHub package list was available.\n' "$HomePath/.dhinstalled" >&2
    return 0
  fi

  printf 'Removing packages listed in %s...\n' "$HomePath/.dhinstalled"

  while IFS= read -r pkg; do
    [[ -n "${pkg//[[:space:]]/}" ]] || continue
    [[ "$pkg" =~ ^[A-Za-z0-9][A-Za-z0-9+._-]*$ ]] || continue

    printf 'Checking package: %s\n' "$pkg"

    if dpkg -s "$pkg" >/dev/null 2>&1; then
      printf 'Purging package: %s\n' "$pkg"

      run_sudo env DEBIAN_FRONTEND=noninteractive apt-get \
        -o Dpkg::Options::="--force-confdef" \
        -o Dpkg::Options::="--force-confold" \
        -y purge "$pkg"
    else
      printf 'Package not installed: %s\n' "$pkg"
    fi
  done < "$HomePath/.dhinstalled"

  printf 'Removing orphaned dependencies...\n'
  run_sudo env DEBIAN_FRONTEND=noninteractive apt-get -y autoremove --purge || true

  printf 'Cleaning apt package cache...\n'
  run_sudo apt-get clean || true

  printf 'Removing package installation record: %s\n' "$HomePath/.dhinstalled"
  rm -f -- "$HomePath/.dhinstalled" || true
}

remove_user_files() {
  if (( REMOVE_DHINFO == 1 )); then
    printf 'Removing DigiHub user info files...\n'
    rm -f -- "$HomePath/.dhinfo" "$HomePath/.dhinfo.last" "$HomePath/.hamdb.cnf" "$HomePath/.digihub-update-available" "$HomePath/.digihub-dhinfo-missing" >/dev/null 2>&1 || true
  fi

  if (( REMOVE_VENV == 1 )); then
    if [[ -d "$venv_dir" ]]; then
      printf 'Removing Python virtual environment: %s\n' "$venv_dir"
      run_sudo rm -rf -- "$venv_dir"
    else
      printf 'Python virtual environment not found: %s\n' "$venv_dir"
    fi
  fi
}

prompt_reboot() {
  (( PROMPT_REBOOT == 1 )) || return 0

  local response=""

  while true; do
    printf '\nReboot now (Y/n)? '
    read -n1 -r response </dev/tty
    case "$response" in
      Y|y)
        printf '\nRebooting\n'
        run_sudo reboot
        break
        ;;
      N|n)
        printf '\nA reboot is required to completely remove DigiHub.\n\n'
        break
        ;;
      *)
        printf '\nInvalid response, please select Y/n.\n'
        ;;
    esac
  done
}

while (( $# > 0 )); do
  case "$1" in
    -y|--yes)
      ASSUME_YES=1
      ;;
    --no-reboot)
      PROMPT_REBOOT=0
      ;;
    --keep-packages|--no-packages)
      REMOVE_PACKAGES=0
      ;;
    --keep-profile|--no-profile)
      REMOVE_PROFILE=0
      ;;
    --keep-dhinfo|--no-dhinfo)
      REMOVE_DHINFO=0
      ;;
    --keep-venv|--no-venv)
      REMOVE_VENV=0
      ;;
    --keep-files|--no-files)
      REMOVE_MANIFEST_FILES=0
      ;;
    --keep-service|--no-service)
      REMOVE_SERVICE=0
      ;;
    --files-only)
      REMOVE_PACKAGES=0
      REMOVE_PROFILE=0
      REMOVE_DHINFO=0
      REMOVE_VENV=0
      REMOVE_MANIFEST_FILES=1
      REMOVE_SERVICE=0
      PROMPT_REBOOT=0
      ASSUME_YES=1
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      printf 'Unknown option: %s\n' "$1" >&2
      usage >&2
      exit 2
      ;;
  esac
  shift
done

confirm_remove
deactivate >/dev/null 2>&1 || true

printf 'Restoring profile...\n'
restore_profile

printf 'Removing manifest files...\n'
remove_manifest_files

printf 'Removing ardopcf...\n'
remove_ardopcf_binary

printf 'Removing ttyd...\n'
remove_ttyd_binary

printf 'Removing GPS monitor service...\n'
remove_gps_monitor

printf 'Removing DigiHub web interface service...\n'
remove_dhweb_service

printf 'Removing Direwolf service...\n'
remove_direwolf_service

printf 'Removing CAT control service...\n'
remove_rigctld_service

printf 'Removing Winlink (Pat) service...\n'
remove_pat_service

printf 'Removing ARDOP TNC service...\n'
remove_ardop_service

printf 'Removing AX.25 node/BBS service...\n'
remove_node_service

printf 'Removing APRS WebChat service...\n'
remove_webchat_service

printf 'Removing web console service...\n'
remove_console_service

printf 'Removing WSJT-X VNC service...\n'
remove_wsjtx_service

printf 'Removing JS8Call VNC service...\n'
remove_js8call_service

printf 'Removing qSSTV VNC service...\n'
remove_qsstv_service

remove_vnc_config

printf 'Removing HamDB update timer...\n'
remove_hamdb_timer

printf 'Removing DigiHub update check timer...\n'
remove_update_check_timer

remove_digihub_etc_dir

printf 'Removing packages...\n'
remove_packages

printf 'Removing user files...\n'
remove_user_files

printf 'DigiHub removal complete.\n'
prompt_reboot