Files
Linux-Server-Management-Too…/lib/user-manager.sh
T
cschantz 45e115ec4b Fix SOURCE command safety issues (HIGH priority)
Added existence checks and error handling for all source commands
to prevent silent failures when dependencies are missing.

Library files (use 'return' for error):
- reference-db.sh: Added checks for 3 dependencies
- mysql-analyzer.sh: Added checks for 3 dependencies
- domain-discovery.sh: Added checks for 2 dependencies
- system-detect.sh: Added check for common-functions.sh
- plesk-helpers.sh: Added check for common-functions.sh
- user-manager.sh: Added checks for 2 dependencies

Executable scripts (use 'exit' for error):
- wordpress-cron-manager.sh: Added checks for 2 dependencies
- website-error-analyzer.sh: Added checks for 4 dependencies

Pattern: [ -f "file" ] && source "file" || { echo "ERROR" >&2; return/exit 1; }

This ensures scripts fail fast with clear error messages when
required dependencies are missing, rather than continuing with
undefined functions.
2026-01-02 17:26:21 -05:00

765 lines
26 KiB
Bash
Executable File

#!/bin/bash
#############################################################################
# User Manager Library
# Dynamic user listing and management for cPanel/Plesk/InterWorx
#############################################################################
# Source dependencies
if [ -z "$TOOLKIT_BASE_DIR" ]; then
_LIB_SRCDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[ -f "$_LIB_SRCDIR/common-functions.sh" ] && source "$_LIB_SRCDIR/common-functions.sh" || { echo "ERROR: common-functions.sh not found" >&2; return 1; }
[ -f "$_LIB_SRCDIR/system-detect.sh" ] && source "$_LIB_SRCDIR/system-detect.sh" || { echo "ERROR: system-detect.sh not found" >&2; return 1; }
fi
# Initialize temp session directory if not set
if [ -z "$TEMP_SESSION_DIR" ]; then
TEMP_SESSION_DIR="/tmp/server-toolkit-$$"
mkdir -p "$TEMP_SESSION_DIR" 2>/dev/null
fi
#############################################################################
# USER LISTING (Control Panel Specific)
#############################################################################
list_all_users() {
case "$SYS_CONTROL_PANEL" in
cpanel)
list_cpanel_users
;;
plesk)
list_plesk_users
;;
interworx)
list_interworx_users
;;
*)
list_system_users
;;
esac
}
# cPanel user listing
list_cpanel_users() {
local cpanel_users_dir="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}"
if [ -d "$cpanel_users_dir" ]; then
ls "$cpanel_users_dir" 2>/dev/null || true
else
# Fallback: parse /etc/trueuserdomains
awk -F: '{print $2}' /etc/trueuserdomains 2>/dev/null | sort -u || true
fi
}
# Plesk user listing
list_plesk_users() {
# Use plesk_list_users() if available (from plesk-helpers.sh)
if type plesk_list_users >/dev/null 2>&1; then
plesk_list_users
elif command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
# Fallback: Try MySQL query
mysql -Ns psa -e "SELECT login FROM sys_users WHERE type='user'" 2>/dev/null
else
# Last resort: list directories
find /var/www/vhosts -maxdepth 1 -type d -printf "%f\n" 2>/dev/null | \
grep -v "^system$\|^default$\|^chroot$\|^\.skel$\|^fs$\|^fs-passwd$" | \
grep -v "^\."
fi
}
# InterWorx user listing
list_interworx_users() {
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
/usr/local/interworx/bin/listaccounts.pex --output user 2>/dev/null
else
# Fallback: Parse Apache vhost configs for SuexecUserGroup directives
# Each InterWorx account has vhost files in /etc/httpd/conf.d/
if [ -d "/etc/httpd/conf.d" ]; then
grep -h "^[[:space:]]*SuexecUserGroup" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
awk '{print $2}' | sort -u
else
# Last resort: list /home directories (may include non-InterWorx users)
find /home -maxdepth 1 -type d ! -name "home" ! -name "interworx" -printf "%f\n" 2>/dev/null | sort
fi
fi
}
# System users (no control panel)
list_system_users() {
# List users with home directories in /home and valid shells
awk -F: '$6 ~ /^\/home\// && $7 !~ /nologin|false/ {print $1}' /etc/passwd
}
#############################################################################
# USER INFORMATION
#############################################################################
get_user_info() {
local username="$1"
case "$SYS_CONTROL_PANEL" in
cpanel)
get_cpanel_user_info "$username"
;;
plesk)
get_plesk_user_info "$username"
;;
interworx)
get_interworx_user_info "$username"
;;
*)
get_system_user_info "$username"
;;
esac
}
# cPanel user info
get_cpanel_user_info() {
local username="$1"
local user_file="${SYS_CPANEL_USERS_DIR:-/var/cpanel/users}/${username}"
if [ ! -f "$user_file" ]; then
echo "USER_EXISTS=no"
return 1
fi
# Parse cPanel user file
local primary_domain=$(grep "^DNS=" "$user_file" | cut -d= -f2)
local email=$(grep "^CONTACTEMAIL=" "$user_file" | cut -d= -f2)
# cPanel doesn't store HOMEDIR in user file - it's always /home/username
local home_dir="/home/${username}"
# Get addon/parked domains
local all_domains=$(grep "^DNS" "$user_file" | cut -d= -f2 | tr '\n' ' ')
# Get disk usage
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "PRIMARY_DOMAIN=$primary_domain"
echo "ALL_DOMAINS=$all_domains"
echo "EMAIL=$email"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
# Plesk user info
get_plesk_user_info() {
local username="$1"
if ! command_exists mysql || [ ! -f /etc/psa/.psa.shadow ]; then
echo "USER_EXISTS=no"
return 1
fi
local home_dir="/var/www/vhosts/${username}"
local primary_domain=$(mysql -Ns psa -e "SELECT name FROM domains WHERE id IN (SELECT domain_id FROM sys_users WHERE login='$username')" 2>/dev/null | head -1)
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "PRIMARY_DOMAIN=$primary_domain"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
# InterWorx user info
get_interworx_user_info() {
local username="$1"
local home_dir="/home/${username}"
if [ ! -d "$home_dir" ]; then
echo "USER_EXISTS=no"
return 1
fi
# Try to get primary domain from listaccounts.pex first
local primary_domain=""
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
primary_domain=$(/usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \
awk -v user="$username" '$1 == user {print $2; exit}')
fi
# Fallback: Parse vhost configs to find primary domain
if [ -z "$primary_domain" ]; then
primary_domain=$(grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
head -1 | sed 's|.*/vhost_||; s|\.conf$||')
fi
# Get all domains for this user from vhost configs
local all_domains=$(grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
sed 's|.*/vhost_||; s|\.conf$||' | tr '\n' ' ' | sed 's/[[:space:]]*$//')
# Get disk usage
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
# Try to get email from NodeWorx API (if available)
# Note: This requires nodeworx CLI which may need authentication
local email=""
if [ -x "/usr/local/interworx/bin/nodeworx.pex" ] && [ -n "$primary_domain" ]; then
email=$(nodeworx -u -n -c Siteworx -a listAccounts 2>/dev/null | \
grep "\"domain\" => \"$primary_domain\"" 2>/dev/null | head -1 | \
grep "\"email\"" 2>/dev/null | head -1 | sed 's/.*=> "\(.*\)".*/\1/')
fi
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "PRIMARY_DOMAIN=$primary_domain"
echo "ALL_DOMAINS=$all_domains"
echo "EMAIL=${email:-unknown}"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
# System user info (no control panel)
get_system_user_info() {
local username="$1"
local user_entry=$(getent passwd "$username")
if [ -z "$user_entry" ]; then
echo "USER_EXISTS=no"
return 1
fi
local home_dir=$(echo "$user_entry" | cut -d: -f6)
local disk_used=$(du -sh "$home_dir" 2>/dev/null | awk '{print $1}')
echo "USER_EXISTS=yes"
echo "USERNAME=$username"
echo "HOME_DIR=$home_dir"
echo "DISK_USED=$disk_used"
}
#############################################################################
# USER DOMAINS
#############################################################################
get_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
case "$SYS_CONTROL_PANEL" in
cpanel)
get_cpanel_user_domains "$username"
;;
plesk)
get_plesk_user_domains "$username"
;;
interworx)
get_interworx_user_domains "$username"
;;
*)
echo ""
;;
esac
}
get_cpanel_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
# Primary domain (format: domain: user)
grep ": ${username}$" /etc/trueuserdomains 2>/dev/null | cut -d: -f1 || true
# Addon domains
if [ -f "/etc/userdatadomains" ]; then
grep "==${username}$" /etc/userdatadomains 2>/dev/null | cut -d: -f1 || true
fi
}
get_plesk_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
# Try MySQL query first
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
local domains=$(mysql -Ns psa -e "SELECT d.name FROM domains d JOIN sys_users u ON d.id=u.domain_id WHERE u.login='$username'" 2>/dev/null)
if [ -n "$domains" ]; then
echo "$domains"
return 0
fi
fi
# Fallback: Use Plesk CLI if available
if [ -x "/usr/local/psa/bin/plesk" ]; then
/usr/local/psa/bin/plesk bin site --list 2>/dev/null | grep -i "$username" || true
fi
# Last resort: Check if vhosts directory exists for this user
if [ -d "/var/www/vhosts/$username" ]; then
echo "$username"
fi
}
get_interworx_user_domains() {
[ -z "$1" ] && return 1
local username="$1"
# Method 1: Use listaccounts.pex to get primary domain
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
/usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \
awk -v user="$username" '$1 == user {print $2}'
fi
# Method 2: Parse vhost configs to get ALL domains (primary + secondary/addon)
# InterWorx creates vhost_domain.conf for each domain, with SuexecUserGroup directive
if [ -d "/etc/httpd/conf.d" ]; then
grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
sed 's|.*/vhost_||; s|\.conf$||' | \
grep -vF "${username}." 2>/dev/null | \
sort -u
fi
}
#############################################################################
# USER DATABASES
#############################################################################
get_user_databases() {
local username="$1"
case "$SYS_CONTROL_PANEL" in
cpanel)
get_cpanel_user_databases "$username"
;;
plesk)
get_plesk_user_databases "$username"
;;
interworx)
get_interworx_user_databases "$username"
;;
*)
# Try to find databases matching username pattern
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${username}_"
;;
esac
}
get_cpanel_user_databases() {
local username="$1"
# cPanel databases typically follow pattern: username_dbname
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${username}_" 2>/dev/null || true
}
get_plesk_user_databases() {
local username="$1"
if command_exists mysql && [ -f /etc/psa/.psa.shadow ]; then
mysql -Ns psa -e "SELECT db.name FROM data_bases db JOIN domains d ON db.dom_id=d.id JOIN sys_users u ON d.id=u.domain_id WHERE u.login='$username'" 2>/dev/null
fi
}
get_interworx_user_databases() {
local username="$1"
# InterWorx uses the first 8 characters of the PRIMARY DOMAIN as database prefix
# NOT the username! (e.g., domain example.com → prefix: examplec_)
# Get primary domain for this user
local primary_domain=""
if [ -x "/usr/local/interworx/bin/listaccounts.pex" ]; then
primary_domain=$(/usr/local/interworx/bin/listaccounts.pex 2>/dev/null | \
awk -v user="$username" '$1 == user {print $2; exit}')
fi
# Fallback: try to find from vhost configs
if [ -z "$primary_domain" ]; then
primary_domain=$(grep -l "SuexecUserGroup ${username}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | \
head -1 | sed 's|.*/vhost_||; s|\.conf$||')
fi
if [ -z "$primary_domain" ]; then
# No domain found, try username pattern as last resort
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${username}_" || true
return
fi
# Get first 8 characters of domain (removing dots) as database prefix
local db_prefix=$(echo "$primary_domain" | sed 's/\.//g' | cut -c1-8)
# Query MySQL for databases with this prefix
mysql -e "SHOW DATABASES" 2>/dev/null | grep "^${db_prefix}_" || true
}
#############################################################################
# USER LOG FILES
#############################################################################
get_user_log_files() {
local username="$1"
local domains=$(get_user_domains "$username")
case "$SYS_CONTROL_PANEL" in
cpanel)
for domain in $domains; do
echo "${SYS_LOG_DIR}/${domain}"
echo "${SYS_LOG_DIR}/${domain}-ssl_log"
done
;;
plesk)
echo "/var/www/vhosts/${username}/statistics/logs/access_log"
echo "/var/www/vhosts/${username}/statistics/logs/error_log"
for domain in $domains; do
echo "/var/www/vhosts/${domain}/statistics/logs/access_log"
echo "/var/www/vhosts/${domain}/statistics/logs/error_log"
done
;;
interworx)
for domain in $domains; do
echo "/home/${username}/var/${domain}/logs/access_log"
echo "/home/${username}/var/${domain}/logs/error_log"
done
;;
esac | grep -v "^$" | sort -u
}
#############################################################################
# USER SELECTION MENU
#############################################################################
select_user_interactive() {
local prompt="${1:-Select a user}"
local users=($(list_all_users))
local total_users=${#users[@]}
if [ "${total_users:-0}" -eq 0 ]; then
print_error "No users found" >&2
return 1
fi
# Build user info cache to avoid repeated get_user_domains calls
declare -A user_primary_domain
declare -A user_domain_count
for user in "${users[@]}"; do
local domains=$(get_user_domains "$user" 2>/dev/null | grep -v "^$")
if [ -n "$domains" ]; then
user_domain_count["$user"]=$(echo "$domains" | wc -l)
user_primary_domain["$user"]=$(echo "$domains" | head -1)
else
user_domain_count["$user"]=0
user_primary_domain["$user"]="(no domains)"
fi
done
# Send all display output to stderr so it shows on screen (not captured by $(...))
{
echo ""
print_section "$prompt"
echo ""
echo "Found $total_users user(s) on this server"
echo "───────────────────────────────────────────────────────────────────────────────"
# Auto-show list if 10 or fewer users
if [ "${total_users:-0}" -le 10 ]; then
echo ""
for user in "${users[@]}"; do
echo -e " ${GREEN}$user${NC} - ${user_primary_domain[$user]} (${user_domain_count[$user]} domains)"
done
fi
echo ""
echo "───────────────────────────────────────────────────────────────────────────────"
echo ""
echo "Options:"
if [ "${total_users:-0}" -gt 10 ]; then
echo " L - List all $total_users users"
fi
echo " S [text] - Search/filter users (e.g., 's pick' or 's example.com')"
echo " [username] - Enter exact username"
echo " A - All users (system-wide scan)"
echo " 0 - Cancel/Go back"
echo ""
} >&2
read -p "Enter choice> " choice
case "$choice" in
[Aa]|ALL|all)
echo "ALL"
return 0
;;
[Ss]\ *|[Ss])
# Search mode
local search_term="${choice#[Ss] }" # Remove 'S ' prefix
search_term="${search_term#[Ss]}" # Remove 'S' if no space
if [ -z "$search_term" ]; then
# No search term provided, ask for it
read -p "Enter search term (username or domain)> " search_term >&2
fi
if [ -z "$search_term" ]; then
print_error "No search term provided" >&2
return 1
fi
# Search and build match array
local -a matched_users
local -a menu_items
for user in "${users[@]}"; do
# Case-insensitive partial match in username or domain
if [[ "${user,,}" == *"${search_term,,}"* ]] || [[ "${user_primary_domain[$user],,}" == *"${search_term,,}"* ]]; then
matched_users+=("$user")
menu_items+=("$user - ${user_primary_domain[$user]} (${user_domain_count[$user]} domains)")
fi
done
if [ ${#matched_users[@]} -eq 0 ]; then
print_error "No users found matching '$search_term'" >&2
return 1
elif [ ${#matched_users[@]} -eq 1 ]; then
# Single match - ask for confirmation
{
echo ""
echo "Found 1 match: ${matched_users[0]} - ${user_primary_domain[${matched_users[0]}]} (${user_domain_count[${matched_users[0]}]} domains)"
echo ""
} >&2
read -p "Use this user? (Y/n)> " confirm >&2
if [[ ! $confirm =~ ^[Nn]$ ]]; then
echo "${matched_users[0]}"
return 0
else
return 1
fi
else
# Multiple matches - show interactive arrow-key menu
{
echo ""
echo "Found ${#matched_users[@]} matches for '$search_term'"
echo "Use arrow keys (↑/↓) to navigate, Enter to select:"
echo ""
} >&2
PS3="Select user> "
select option in "${menu_items[@]}" "Cancel"; do
if [ "$option" = "Cancel" ]; then
return 1
elif [ -n "$option" ]; then
# Extract username from selected menu item (before the ' - ')
local selected_user="${matched_users[$((REPLY-1))]}"
echo "$selected_user"
return 0
fi
done >&2
fi
;;
[Ll]|LIST|list)
# Show full list
{
echo ""
echo "Complete user list ($total_users users):"
echo "───────────────────────────────────────────────────────────────────────────────"
for user in "${users[@]}"; do
echo -e " ${GREEN}$user${NC} - ${user_primary_domain[$user]} (${user_domain_count[$user]} domains)"
done
echo "───────────────────────────────────────────────────────────────────────────────"
echo ""
} >&2
# Ask again after showing list
read -p "Enter username (or A for all, 0 to cancel)> " choice
# Re-evaluate choice
if [[ " ${users[@]} " =~ " ${choice} " ]]; then
echo "$choice"
return 0
elif [ "$choice" = "A" ] || [ "$choice" = "a" ]; then
echo "ALL"
return 0
elif [ "$choice" = "0" ]; then
return 1
else
print_error "User '$choice' not found" >&2
return 1
fi
;;
0|cancel|back)
return 1
;;
"")
print_error "No input provided" >&2
return 1
;;
*)
# Check if it's an exact username match
if [[ " ${users[@]} " =~ " ${choice} " ]]; then
echo "$choice"
return 0
fi
# Not exact match
print_error "User '$choice' not found" >&2
if [ "${total_users:-0}" -gt 10 ]; then
echo " Tip: Type 'L' to list all users" >&2
fi
return 1
;;
esac
}
#############################################################################
# USER PROCESSES
#############################################################################
get_user_processes() {
local username="$1"
ps aux | grep "$username" 2>/dev/null | grep -v grep
}
get_user_top_processes() {
local username="$1"
local limit="${2:-10}"
ps aux | grep "$username" 2>/dev/null | grep -v grep | sort -k3 -rn | head -n "$limit"
}
#############################################################################
# DATABASE HELPER FUNCTIONS
#############################################################################
get_database_owner() {
local db_name="$1"
# Try to determine owner from database name prefix (common cPanel convention)
# Database names are typically: username_dbname
local prefix=$(echo "$db_name" | cut -d_ -f1)
# Check if this prefix matches a user
local users=$(list_all_users)
for user in $users; do
if [ "$user" = "$prefix" ]; then
echo "$user"
return 0
fi
done
# If no match, return unknown
echo "unknown"
}
get_database_domain() {
local db_name="$1"
local owner=$(get_database_owner "$db_name")
# Try to get primary domain for the owner
if [ "$owner" != "unknown" ]; then
get_user_domains "$owner" 2>/dev/null | head -1
else
echo ""
fi
}
#############################################################################
# WORDPRESS DETECTION
#############################################################################
find_user_wordpress_sites() {
local username="$1"
local home_dir=$(get_user_info "$username" | grep "^HOME_DIR=" | cut -d= -f2)
if [ -z "$home_dir" ] || [ ! -d "$home_dir" ]; then
return 1
fi
# Find wp-config.php files
find "$home_dir" -name "wp-config.php" -type f 2>/dev/null | while read wp_config; do
local wp_dir=$(dirname "$wp_config")
local domain=$(basename "$(dirname "$wp_dir")" 2>/dev/null)
# Try to get actual domain from wp-config
local site_url=$(grep "WP_SITEURL\|WP_HOME" "$wp_config" | head -1 | grep -oP "https?://\K[^/'\"]+" 2>/dev/null || true)
if [ -n "$site_url" ]; then
echo "${site_url}|${wp_dir}"
else
echo "${domain}|${wp_dir}"
fi
done
}
#############################################################################
# DISPLAY FUNCTIONS
#############################################################################
show_user_summary() {
local username="$1"
print_section "User Summary: $username"
# Get user info
local user_info=$(get_user_info "$username")
if echo "$user_info" | grep -q "USER_EXISTS=no"; then
print_error "User does not exist"
return 1
fi
# Parse info
local primary_domain=$(echo "$user_info" | grep "^PRIMARY_DOMAIN=" | cut -d= -f2)
local home_dir=$(echo "$user_info" | grep "^HOME_DIR=" | cut -d= -f2)
local disk_used=$(echo "$user_info" | grep "^DISK_USED=" | cut -d= -f2)
# Display
echo " Username: $username"
[ -n "$primary_domain" ] && echo " Primary Domain: $primary_domain"
echo " Home Directory: $home_dir"
echo " Disk Usage: $disk_used"
echo ""
# Domains
local domains=$(get_user_domains "$username")
local domain_count=$(echo "$domains" | grep -v "^$" | wc -l)
echo " Domains ($domain_count):"
echo "$domains" | sed 's/^/ - /'
echo ""
# Databases
local databases=$(get_user_databases "$username")
local db_count=$(echo "$databases" | grep -v "^$" | wc -l)
echo " Databases ($db_count):"
echo "$databases" | sed 's/^/ - /'
echo ""
}
show_all_users_summary() {
print_section "All Users Summary"
local users=($(list_all_users))
local total_users=${#users[@]}
echo " Total Users: $total_users"
echo ""
printf " ${BOLD}%-20s %-30s %10s %10s${NC}\n" "Username" "Primary Domain" "Domains" "Databases"
echo " ────────────────────────────────────────────────────────────────────"
for user in "${users[@]}"; do
local primary=$(get_user_domains "$user" | head -1)
local domain_count=$(get_user_domains "$user" | grep -v "^$" | wc -l)
local db_count=$(get_user_databases "$user" | grep -v "^$" | wc -l)
printf " %-20s %-30s %10s %10s\n" "$user" "$primary" "$domain_count" "$db_count"
done
echo ""
}
# Export all functions for use in other scripts
export -f list_all_users
export -f list_cpanel_users
export -f list_plesk_users
export -f list_interworx_users
export -f list_system_users
export -f get_user_info
export -f get_user_domains
export -f get_cpanel_user_domains
export -f get_plesk_user_domains
export -f get_interworx_user_domains
export -f get_user_databases
export -f get_user_log_files
export -f select_user_interactive