1. make file in remover.sh 2. then run file as rquired #!/usr/bin/env bash set -euo pipefail # Binaries UAPI="/usr/local/cpanel/bin/uapi" CPAPI2="/usr/local/cpanel/bin/cpapi2" WHMAPI1="/usr/local/cpanel/bin/whmapi1" WHOOWNS="/usr/local/cpanel/scripts/whoowns" # Paths OUTPUT_DIR="/root/scripts/output" # creator logs live here REMOVED_DIR="/root/scripts/removed" # removal logs go here require_root(){ [[ $EUID -eq 0 ]] || { echo "[ERR] Run as root"; exit 1; }; } have_tools(){ command -v "$UAPI" >/dev/null || { echo "[ERR] uapi missing"; exit 1; } command -v "$CPAPI2" >/dev/null || { echo "[ERR] cpapi2 missing"; exit 1; } command -v "$WHMAPI1" >/dev/null|| { echo "[ERR] whmapi1 missing"; exit 1; } } now_str(){ date +"%Y%m%d-%H%M%S"; } # ---------- pick record by FILENAME only ---------- pick_record_file(){ local q="${1:-}" [[ -d "$OUTPUT_DIR" ]] || { echo "[ERR] Not found: $OUTPUT_DIR"; exit 1; } if [[ -z "$q" ]]; then printf "Enter filename fragment (e.g., prohealth): " > /dev/tty; IFS= read -r q < /dev/tty; fi [[ -f "$q" ]] && { echo "$q"; return; } [[ -f "$OUTPUT_DIR/$q" ]] && { echo "$OUTPUT_DIR/$q"; return; } shopt -s nullglob; local all=( "$OUTPUT_DIR"/*.txt ); shopt -u nullglob (( ${#all[@]} )) || { echo "[ERR] No records in $OUTPUT_DIR"; exit 1; } local low="${q,,}" matches=() f base for f in "${all[@]}"; do base="${f##*/}"; [[ "${base,,}" == *"$low"* ]] && matches+=( "$f" ); done (( ${#matches[@]} )) || { echo "[ERR] No filename contains: $q"; exit 1; } (( ${#matches[@]} == 1 )) && { echo "${matches[0]}"; return; } echo "Multiple records matched:" >&2 local i=1 for f in "${matches[@]}"; do local fn ts name db user fn="${f##*/}" ts=$(date -r "$f" +"%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "?") name=$(sed -n 's/^[[:space:]]*Name:[[:space:]]*//p' "$f" | head -n1) db=$(sed -n 's/^[[:space:]]*DB:[[:space:]]*//p' "$f" | head -n1) user=$(sed -n 's/^[[:space:]]*User:[[:space:]]*//p' "$f" | head -n1) printf " [%d] %s\n file: %s\n name: %s\n db: %s\n user: %s\n" "$i" "$ts" "$fn" "${name:-}" "${db:-}" "${user:-}" >&2 ((i++)) done printf "Pick one [1..%d] (default 1): " $((i-1)) > /dev/tty local pick; IFS= read -r pick < /dev/tty; pick="${pick:-1}" [[ "$pick" =~ ^[0-9]+$ ]] && (( pick>=1 && pick/dev/null | awk '{print $1}'; } refresh_all_domains(){ DOMJSON=$("$UAPI" --user="$CPUSER" --output=json DomainInfo list_domains 2>/dev/null | tr -d '\n') || DOMJSON="" } domain_type(){ local d="$1" echo "$DOMJSON" | grep -Fq "\"main_domain\":\"$d\"" && { echo "main"; return; } echo "$DOMJSON" | grep -Eo '"addon_domains":[^]]*' | grep -Fq "\"$d\"" && { echo "addon"; return; } echo "$DOMJSON" | grep -Eo '"sub_domains":[^]]*' | grep -Fq "\"$d\"" && { echo "sub"; return; } echo "$DOMJSON" | grep -Eo '"parked_domains":[^]]*'| grep -Fq "\"$d\"" && { echo "alias"; return; } echo "absent" } # ---------- confirm & file options ---------- confirm_plan(){ { echo "==========================================================" echo "Removal Plan" echo "----------------------------------------------------------" echo "Record file : $RECORD_FILE" echo "CP user : $CPUSER" echo "Domain : ${DOMAIN:-}" echo "Docroot : ${DOCROOT:-}" echo "DB : ${DBNAME:-}" echo "DB User : ${DBUSER:-}" echo "==========================================================" echo } >&2 DELETE_FILES="n"; TARGET_DELETE="" if [[ -n "${DOMAIN:-}" && "$DOMAIN" != "" && -n "${DOCROOT:-}" ]]; then if [[ "$DOCROOT" == "/home/$CPUSER/public_html/"* ]]; then if [[ "$DOCROOT" == */public ]]; then local base; base="$(dirname "$DOCROOT")" { echo "File Deletion Options" echo " 1) Delete WHOLE app folder : $base" echo " 2) Delete JUST the docroot : $DOCROOT" echo " 3) Keep files (do nothing)" printf "Choose [1/2/3, default 3]: " } > /dev/tty local ans; IFS= read -r ans < /dev/tty case "$ans" in 1) DELETE_FILES="b"; TARGET_DELETE="$base" ;; 2) DELETE_FILES="j"; TARGET_DELETE="$DOCROOT" ;; *) DELETE_FILES="n" ;; esac else printf "Delete docroot '%s'? [y/N]: " "$DOCROOT" > /dev/tty local ans; IFS= read -r ans < /dev/tty [[ "${ans,,}" =~ ^y(es)?$ ]] && { DELETE_FILES="j"; TARGET_DELETE="$DOCROOT"; } fi else echo "[WARN] Docroot '$DOCROOT' not under /home/$CPUSER/public_html; keeping files." >&2 fi fi printf "Proceed with removals? [Y/n]: " > /dev/tty local OK; IFS= read -r OK < /dev/tty; OK="${OK:-Y}" [[ "${OK,,}" =~ ^y(es)?$ ]] || { echo "Aborted." >&2; exit 1; } } # ---------- actions & verification ---------- DOMAIN_PRIMARY_STATUS="skipped" DOMAIN_WWW_STATUS="skipped" MYSQL_USER_STATUS="skipped" MYSQL_DB_STATUS="skipped" FILES_STATUS="kept" whm_delete_domain(){ # Use WHM API1 delete_domain to remove any domain (sub/addon/alias) owned by the user local dom="$1" "$WHMAPI1" delete_domain domain="$dom" >/dev/null 2>&1 || return 1 return 0 } remove_exact_domain(){ local dom="$1" # Safety: only proceed if recorded user owns this exact domain (if whoowns reports) local owner; owner="$(who_owns "$dom" || true)" if [[ -n "$owner" && "$owner" != "$CPUSER" ]]; then echo "skipped (owner is '$owner', not '$CPUSER')" return fi refresh_all_domains local kind; kind="$(domain_type "$dom")" # Preferred: WHM delete_domain for everything; then verify if whm_delete_domain "$dom"; then refresh_all_domains if [[ "$(domain_type "$dom")" == "absent" ]]; then [[ "$kind" == "absent" ]] && kind="unknown" echo "removed ($kind via whmapi1)" return fi fi # Fallbacks (main domain cannot be deleted here) case "$kind" in addon) "$UAPI" --user="$CPUSER" AddonDomain deladdondomain domain="$dom" >/dev/null 2>&1 || true ;; alias) "$CPAPI2" --user="$CPUSER" Park unpark domain="$dom" >/dev/null 2>&1 || true ;; sub) # Some builds lack UAPI sub delete; we already tried WHM above. : ;; main) echo "skipped (main domain of account)"; return ;; absent) echo "not-found"; return ;; esac # Verify after fallback refresh_all_domains if [[ "$(domain_type "$dom")" == "absent" ]]; then echo "removed ($kind via fallback)" else echo "failed ($kind still present)" fi } remove_domains(){ if [[ -z "${DOMAIN:-}" || "$DOMAIN" == "" ]]; then DOMAIN_PRIMARY_STATUS="no-domain"; DOMAIN_WWW_STATUS="no-domain"; return fi DOMAIN_PRIMARY_STATUS="$(remove_exact_domain "$DOMAIN")" # Also try www. if present; harmless if absent local wdom="www.$DOMAIN" DOMAIN_WWW_STATUS="$(remove_exact_domain "$wdom")" } remove_mysql(){ if [[ -n "${DBUSER:-}" ]]; then if "$UAPI" --user="$CPUSER" Mysql delete_user name="$DBUSER" >/dev/null 2>&1; then MYSQL_USER_STATUS="removed"; else MYSQL_USER_STATUS="not-found-or-failed"; fi fi if [[ -n "${DBNAME:-}" ]]; then if "$UAPI" --user="$CPUSER" Mysql delete_database name="$DBNAME" >/dev/null 2>&1; then MYSQL_DB_STATUS="removed"; else MYSQL_DB_STATUS="not-found-or-failed"; fi fi } remove_files(){ if [[ "${DELETE_FILES}" != "n" && -n "${TARGET_DELETE:-}" ]]; then if [[ -e "$TARGET_DELETE" ]]; then rm -rf --one-file-system "$TARGET_DELETE" && FILES_STATUS="deleted: $TARGET_DELETE" || FILES_STATUS="delete-failed: $TARGET_DELETE" else FILES_STATUS="missing: $TARGET_DELETE" fi else FILES_STATUS="kept" fi } write_log(){ mkdir -p "$REMOVED_DIR"; umask 077 local ts; ts="$(now_str)" local stamp="${REMOVED_DIR}/removed_${CPUSER}_$([[ -n "${DOMAIN:-}" && "$DOMAIN" != "" ]] && echo "${DOMAIN//\//_}" || echo "dbonly")_${ts}.txt" { echo "==========================================================" echo "Removal Summary" echo "----------------------------------------------------------" echo "Timestamp : $ts" echo "Record file : $RECORD_FILE" echo "CP user : $CPUSER" echo echo "Domain : ${DOMAIN:-}" echo "Docroot : ${DOCROOT:-}" echo "DB : ${DBNAME:-}" echo "DB User : ${DBUSER:-}" echo echo "Actions" echo " Domain : $DOMAIN_PRIMARY_STATUS" echo " www.Domain : $DOMAIN_WWW_STATUS" echo " MySQLUser : $MYSQL_USER_STATUS" echo " MySQLDB : $MYSQL_DB_STATUS" echo " Files : $FILES_STATUS" echo "==========================================================" } >"$stamp" echo echo "----- Removal Summary (saved to $stamp) -----" echo "Domain: $DOMAIN_PRIMARY_STATUS | www: $DOMAIN_WWW_STATUS" echo "MySQLUser: $MYSQL_USER_STATUS | MySQLDB: $MYSQL_DB_STATUS" echo "Files: $FILES_STATUS" echo "--------------------------------------------" } main(){ require_root; have_tools RECORD_FILE="$(pick_record_file "${1:-}")" parse_record "$RECORD_FILE" confirm_plan remove_domains remove_mysql remove_files write_log } main "$@"